5

我使用以下代码片段(在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 的视图修饰符,此代码就可以很好地工作,但是如果我想使用这些修饰符中的任何一个(例如Taskfor ex.),那么我需要使用#available指令,这是我不想选择的选项,因为我的代码库很大,有很多部分应该采用新的 iOS 15 修饰符,并且通过#available在代码中的任何地方使用它会使它看起来像一盘千层面。

如何使这段代码以一种干净的方式编译而不使用#available检查?

4

4 回答 4

1

没有'if #available'就没有办法做到这一点,但是有一种方法可以以一种有点干净的方式来构建它。

在包装视图上定义您自己的视图修改器:

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)
}

关于它的博客文章: 在旧 iOS 版本中使用仅限 iOS-15 的视图修饰符

于 2021-10-09T10:51:49.103 回答
0

到目前为止,我想出的最佳解决方案是为视图添加简单的修改扩展功能并使用它。如果仅在一个地方需要对修饰符进行可用性检查,这将很有用。如果在多个地方需要,则创建新的修饰符函数。

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
        }
    }
于 2022-02-21T09:24:37.960 回答
-1

没有意义,因为即使您确实向后移植了一个名为的修饰符task(通常这是解决此问题的方法),您也无法在其中使用 async/await 的所有魔力,而这正是它的设计目的。如果您有充分的理由不以 iOS 15 为目标(我不知道有什么好的理由),那么只需继续onAppear正常使用标准调度队列异步或在@StateObject.

于 2021-08-24T11:14:13.110 回答
-1

对于您要解决的问题,该修饰符没有合乎逻辑的用例!您不知道,您的应用会在每次渲染中检查您关于 iOS15 可用性的条件多少次!也许1000次!疯狂的控制数量,这完全是个坏主意!而是对这样的每个场景使用不同的视图,它只会检查一次:

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

}
于 2021-08-25T04:01:31.653 回答