我正在尝试对不同的 UIViewController 使用通用包装器(在 obj-c 中应用程序的遗留部分中实现的屏幕),并且遇到了一个问题,当包装的 UIViewController 来自已发布的属性时,SwiftUI 不会重绘视图我的视图模型,当它来自本地静态属性与变化状态相结合时,它将重新绘制视图。
包装视图:
struct WrappedViewController: UIViewControllerRepresentable {
let viewController: UIViewController
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Self.Context) -> UIViewController {
viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Self.Context) {}
}
具有已发布属性的视图模型以公开当前选定的视图控制器:
class ViewModel: ObservableObject {
@Published
var selectedViewController: UIViewController
private static let redVC = {
let controller = UIViewController()
controller.view.backgroundColor = .red
return controller
}()
private static let blueVC = {
let controller = UIViewController()
controller.view.backgroundColor = .blue
return controller
}()
init() {
self.selectedViewController = Self.redVC
}
func toggle() {
if selectedViewController == Self.redVC {
selectedViewController = Self.blueVC
} else {
selectedViewController = Self.redVC
}
}
}
查看切换不会重新加载 UI 的位置:
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
Button("Switch") { viewModel.toggle() }
WrappedViewController(viewController: viewModel.selectedViewController)
}
}
如果我删除具有已发布属性的视图模型层,它会按预期工作:
struct ContentView: View {
@State private var viewSwitch: Bool = true
let blueView: UIViewController = {
let blueViewController = UIViewController()
blueViewController.view.backgroundColor = .blue
return blueViewController
}()
let redView: UIViewController = {
let redViewController = UIViewController()
redViewController.view.backgroundColor = .red
return redViewController
}()
var body: some View {
Button("Switch") { viewSwitch.toggle() }
if viewSwitch {
WrappedViewController(viewController: blueView)
} else {
WrappedViewController(viewController: redView)
}
}
}
但是我需要将视图控制器包装在视图模型中。我的具体用例是一个具有多个视图控制器和选项卡栏的屏幕,其中选项卡被定义为视图模型内的视图控制器并在构造时传递。
关于为什么 SwiftUI 不重新渲染屏幕有什么想法吗?我可以看到已发布的属性更改触发了层次结构的重新评估(命中了“body”中的断点),但屏幕并未重新渲染。
我发现,当单击按钮并更新已发布的属性时,会使用新的(正确的)视图控制器创建一个新的
WrappedViewController
对象,但随后 makeUIViewController
不会被调用。相反,只有 updateUIViewController
被调用,并且它传递了我从第一个视图的 UIViewController
返回的 makeUIViewController
实例:)
从那里,我想出了一个丑陋但有效的解决方案,其中我想要的视图控制器作为可表示的输入,我需要将其包装为子项:
struct WrappedViewController: UIViewControllerRepresentable {
let viewController: UIViewController
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
uiViewController.children.forEach { childController in
childController.willMove(toParent: nil)
childController.view.removeFromSuperview()
childController.removeFromParent()
}
viewController.willMove(toParent: uiViewController)
uiViewController.addChild(viewController)
uiViewController.view.addSubview(viewController.view)
viewController.view.constrainToSuperview()
}
}
这是有道理的 -
UIViewControllerRepresentable
可能会尝试以与创建视图相同的方式创建视图控制器 - 提供构建器函数和更新函数,SwiftUI 基础设施将决定是否要构造一个新的视图控制器实例或重复使用上一个。