事实上,任何UIKit或AppKit视图都可以被包装以兼容wift,这是非常有用的,因为这在某种程度上提供了一个“逃生舱口”,每当SwiftUI还不支持特定类型的控件或UI元素时。
然而,如果我们需要依靠大量的(没有更好的词)“历史遗留”视图,必须不断地为他们其中的每个都写包装可能会开始变得有点乏味,尤其是对于简单的观点,不需要任何复杂的逻辑,或视图,我们只是想在一个地方使用。
例如,假设我们想要使用UIActivityIndicatorView在一个基于SwiftUI的iOS应用程序中显示一个加载旋转器。为了做到这一点,我们必须编写一个包装器,看起来像这样:
struct ActivityIndicator: UIViewRepresentable {
func makeUIView(context: Context) -> UIActivityIndicatorView {
UIActivityIndicatorView(style: .medium)
}
func updateUIView(_ view: UIActivityIndicatorView, context: Context) {
view.startAnimating()
}
}
当写一个包装,符合UIViewRepresentation(或在Mac上的NSViewRepresentation)不是一个巨大的任务-它不是很好,如果我们可以只是包装任何遗留视图内联,就在我们需要使用它?
让我们通过写一个泛型类型来实现能被用来包装任何UIView。让我们称它为Wrap,并让它有两个闭包,每个对应一个UIViewRepresentation要求的方法-像这样:
struct Wrap<Wrapped: UIView>: UIViewRepresentable {
typealias Updater = (Wrapped, Context) -> Void
var makeView: () -> Wrapped
var update: (Wrapped, Context) -> Void
init(_ makeView: @escaping @autoclosure () -> Wrapped,
updater update: @escaping Updater) {
self.makeView = makeView
self.update = update
}
func makeUIView(context: Context) -> Wrapped {
makeView()
}
func updateUIView(_ view: Wrapped, context: Context) {
update(view, context)
}
}
请注意上面@autoclosure的用法,它将使我们能够继续遵循UIViewRepresentation的约定并惰性地创建我们的视图,而不需要在调用站点上使用任何额外的语法。
然而,当更新我们的视图时,新的Wrap类型当前要求我们始终同时处理视图本身和当前上下文。 虽然获得上下文参数可能对一些场景比较重要,让我们让它可选,通过引入了两个方便的api,会让我们要么接受我们的view作为唯一参数,或者选择完全舍弃更新,当我们的视图是完全静态时;
extension Wrap {
init(_ makeView: @escaping @autoclosure () -> Wrapped,
updater update: @escaping (Wrapped) -> Void) {
self.makeView = makeView
self.update = { view, _ in update(view) }
}
init(_ makeView: @escaping @autoclosure () -> Wrapped) {
self.makeView = makeView
self.update = { _, _ in }
}
}
有了以上这些,我们现在可以轻松地将任何UIView完全内联,同时也可以在底层状态发生变化时更新它——像这样:
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
ZStack {
...
Wrap(UIActivityIndicatorView()) {
if self.viewModel.isLoading {
$0.startAnimating()
} else {
$0.stopAnimating()
}
}
}
}
}
非常好!当然,这并不意味着我们应该完全放弃为某些视图构建适当的包装器,但对于更简单的视图,上面的包装类型非常方便