SwiftUI:在不同iOS版本之间使用视图修改器而不使用#available

62lalag4  于 2022-12-26  发布在  Swift
关注(0)|答案(5)|浏览(147)

我使用以下代码片段(在Xcode 13 Beta 5中,部署目标设置为14.0)根据iOS版本有条件地应用视图修改器:

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .modifyFor(iOS14: {
                $0.onAppear {
                    //do some stuff
                }
            }, iOS15: {
                $0.task { //<---- Error: 'task(priority:_:)' is only available in iOS 15.0 or newer
                    //do some stuff
                }
            })
    }
}

struct CompatibleView<Input: View,
                      Output14: View,
                      Output15: View>: View {
    var content: Input
    var iOS14modifier: ((Input) -> Output14)?
    var iOS15modifier: ((Input) -> Output15)?
    
   @ViewBuilder var body: some View {
        if #available(iOS 15, *) {
            if let modifier = iOS15modifier {
                 modifier(content)
            }
            else { content }
        }
        else {
            if let modifier = iOS14modifier {
                 modifier(content)
            }
            else { content }
        }
    }
}

extension View {
    func modifyFor<T: View, U: View>(iOS14: ((Self) -> T)? = nil,
                                     iOS15: ((Self) -> U)? = nil) -> some View {
         CompatibleView(content: self,
                                  iOS14modifier: iOS14,
                                  iOS15modifier: iOS15)
    }
}

只要我不使用iOS 15的视图修改器,这段代码就能很好地工作,但如果我想使用其中任何一个修改器(例如Task),那么我需要使用#available指令,这是一个我不想选择的选项,因为我的代码库很大,有许多部分应该采用新的iOS 15修饰符,并通过在代码中到处使用#available将使它看起来像一盘千层面。
如何使这段代码以一种干净的方式编译,而不使用#available检查?

oug3syen

oug3syen1#

到目前为止,我认为最好的解决方案是为视图添加简单的修改扩展函数并使用它。如果修改量的可用性检查只在一个地方需要,这是有用的。如果需要在多个地方,然后创建新的修改量函数。

public extension View {
    func modify<Content>(@ViewBuilder _ transform: (Self) -> Content) -> Content {
        transform(self)
    }
}

使用它将是:

Text("Good")
    .modify {
        if #available(iOS 15.0, *) {
            $0.badge(2)
        } else {
            // Fallback on earlier versions
        }
    }

编辑:

@ViewBuilder
func modify<Content: View>(@ViewBuilder _ transform: (Self) -> Content?) -> some View {
    if let view = transform(self), !(view is EmptyView) {
        view
    } else {
        self
    }
}

这允许我们在不需要的情况下不定义回退,并且视图将保持不可触及。

Text("Good")
    .modify {
        if #available(iOS 15.0, *) {
            $0.badge(2)
        }
    }
gxwragnw

gxwragnw2#

没有'if #available'就没有办法做到这一点,但是有一种方法可以用一种比较干净的方式来构造它。
在 Package 视图上定义您自己的视图修改器:

struct Backport<Content> {
    let content: Content
}

extension View {
    var backport: Backport<Self> { Backport(content: self) }
}

extension Backport where Content: View {
    @ViewBuilder func badge(_ count: Int) -> some View {
        if #available(iOS 15, *) {
            content.badge(count)
        } else {
            content
        }
    }
}

然后,您可以按如下方式使用它们:

TabView {
    Color.yellow
        .tabItem {
            Label("Example", systemImage: "hand.raised")
        }
        .backport.badge(5)
}

关于它的博客文章:Using iOS-15-only View modifiers in older iOS versions

4si2a6ki

4si2a6ki3#

您可以使用@ViewBuilder在View上创建简单的扩展

fileprivate extension View {
        @ViewBuilder
        var tabBarTintColor: some View {
            if #available(iOS 16, *) {
                self.tint(.red)
            } else {
                self.accentColor(.red)
            }
        }
    }

要使用它,只需将其与现有视图链接即可

TabView() 
.tabBarTintColor
nkkqxpd9

nkkqxpd94#

这没有意义,因为即使您向后移植了名为task的修饰符(这个问题通常是这样解决的)你将无法使用async/await的所有魔力,而这正是它的设计目的。如果你有一个很好的理由不针对iOS 15(我不知道有什么好的)然后继续正常使用onAppear,在@StateObject中使用标准的调度队列async或合并。

yvfmudvl

yvfmudvl5#

对于您试图解决的问题,该修改器没有合理的用例!您不知道,您的应用会在每个渲染中检查您的iOS 15可用性条件多少次!也许1000次!控制数量太多,这是完全糟糕的主意!相反,对每个场景使用不同的视图,它只会检查一次:

WindowGroup {
    
    if #available(iOS 15, *) {
        
        ContentView_For_iOS15()
        
    }
    else {
        
        ContentView_For_No_iOS15()
        
    }

}

相关问题