SwiftUI中能否在.onChange闭包内部添加视图修饰符
结论
不可以在.onChange修饰符的闭包内部直接添加/调用视图修饰符,你给出的简化示例写法是完全无效的。
无效原因
SwiftUI中的视图是值类型结构体,当你在.onChange闭包中调用类似content.foregroundColor(.red)、self.body.statusBarStyle(...)的代码时,本质是生成了一个全新的、未被挂载到当前渲染视图树的临时视图实例。这个临时实例不会被SwiftUI纳入渲染流程,闭包执行完就会被直接销毁,完全不会对界面上已经展示的实际视图产生任何修改。
正确实现方案
SwiftUI的视图更新是状态驱动的:你不需要手动在监听回调里给视图加修饰符,只需要把修饰符的参数和状态值做绑定,当状态变化时SwiftUI会自动重新计算视图树,自动应用更新后的修饰符效果。
针对你需要动态切换状态栏样式的场景,直接把statusBarStyle修饰符挂在视图层级上,参数根据publishedValue动态计算即可,不需要写在.onChange闭包中:
struct ContentView: View { @ObservedObject var viewModel: TestViewModel var body: some View { ZStack { Rectangle().foregroundColor(.mint) VStack(alignment: .center, spacing: 25) { Text("Test text \(viewModel.publishedValue)") Button("Increment") { viewModel.publishedValue += 1 } } } .ignoresSafeArea() // 直接根据状态值返回对应状态栏样式,状态变更时自动触发更新 .statusBarStyle(viewModel.publishedValue % 2 == 0 ? .lightContent : .darkContent) } }
自定义状态栏修饰符适配动态更新
你当前使用的自定义statusBarStyle实现仅在视图首次加载时执行样式设置,状态变化时不会触发更新,需要对实现做小幅调整:
- 给
HostingWindowFinder增加样式参数,在updateUIView方法中同步触发样式更新回调 - 给
HostingViewController的style属性添加didSet监听,样式变化时主动调用setNeedsStatusBarAppearanceUpdate刷新状态栏 - 回调逻辑中优先判断当前根控制器是否已经是自定义的
HostingViewController,存在则直接更新样式,不要重复创建控制器实例
调整后的完整修饰符相关代码如下:
extension View { func statusBarStyle(_ style: UIStatusBarStyle) -> some View { return self.background(HostingWindowFinder(style: style, callback: { window, targetStyle in guard let rootViewController = window?.rootViewController else { return } if let hostingController = rootViewController as? HostingViewController { hostingController.style = targetStyle } else { let hostingController = HostingViewController(rootViewController: rootViewController, style: targetStyle) window?.rootViewController = hostingController } })) } } fileprivate class HostingViewController: UIViewController { private var rootViewController: UIViewController? var style: UIStatusBarStyle = .default { didSet { setNeedsStatusBarAppearanceUpdate() } } init(rootViewController: UIViewController, style: UIStatusBarStyle) { self.rootViewController = rootViewController self.style = style super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { super.init(coder: coder) } override func viewDidLoad() { super.viewDidLoad() guard let child = rootViewController else { return } addChild(child) view.addSubview(child.view) child.didMove(toParent: self) } override var preferredStatusBarStyle: UIStatusBarStyle { return style } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { setNeedsStatusBarAppearanceUpdate() } } fileprivate struct HostingWindowFinder: UIViewRepresentable { let style: UIStatusBarStyle var callback: (UIWindow?, UIStatusBarStyle) -> () func makeUIView(context: Context) -> UIView { let view = UIView() DispatchQueue.main.async { [weak view] in self.callback(view?.window, self.style) } return view } func updateUIView(_ uiView: UIView, context: Context) { DispatchQueue.main.async { [weak uiView] in self.callback(uiView?.window, self.style) } } }
内容的提问来源于stack exchange,提问作者Iiro Alhonen
相关产品推荐
相关产品推荐

