为啥在 SwiftUI Lifecycle 中将 rootViewController 设置为 UIHostingController 时会出错?

Posted

技术标签:

【中文标题】为啥在 SwiftUI Lifecycle 中将 rootViewController 设置为 UIHostingController 时会出错?【英文标题】:Why do I get an error when setting rootViewController to UIHostingController in the SwiftUI Lifecycle?为什么在 SwiftUI Lifecycle 中将 rootViewController 设置为 UIHostingController 时会出错? 【发布时间】:2021-12-30 00:55:52 【问题描述】:

我正在尝试在最新的 SwiftUI 生命周期中设置状态栏样式。这是我执行此操作的逻辑的代码,改编自 here。

private class HostingController<Content: View>: UIHostingController<Content> 
  override var preferredStatusBarStyle: UIStatusBarStyle 
    return UIApplication.statusBarStyleHierarchy.last ?? UIApplication.defaultStatusBarStyle
  


/// By wrapping views in a StatusBarControllerView, they will become the app's main / primary view.
/// This will enable setting the statusBarStyle.
struct StatusBarControllerView<Content: View>: View 
  var content: Content

  init(@ViewBuilder content: () -> (Content)) 
    self.content = content()
  

  var body:some View 
    EmptyView()
      .onAppear 
        UIApplication.shared.setHostingController(rootView: AnyView(content))
      
  


extension View 
  /// Sets the status bar style color for this view.
  func statusBar(style: UIStatusBarStyle) -> some View 
    UIApplication.statusBarStyleHierarchy.append(style)

    return self
      // Once this view appears, set the style to the new style.
      .onAppear 
        UIApplication.hostingController?.setNeedsStatusBarAppearanceUpdate()
      
      // Once it disappears, set it to the previous style.
      .onDisappear 
        UIApplication.statusBarStyleHierarchy.removeLast()
        UIApplication.hostingController?.setNeedsStatusBarAppearanceUpdate()
    
  


private extension UIApplication 
  static var hostingController: HostingController<AnyView>?

  static var statusBarStyleHierarchy: [UIStatusBarStyle] = []
  static let defaultStatusBarStyle: UIStatusBarStyle = .lightContent

  /// Sets the App to start at rootView
  func setHostingController(rootView: AnyView) 
    let hostingController = HostingController(rootView: AnyView(rootView))
    windows.first?.rootViewController = hostingController
    UIApplication.hostingController = hostingController
  

然后我将 StatusBarControllerView 放在我的根 App 中,如下所示:

@main
struct App: SwiftUI.App 
  var body: some Scene 
    WindowGroup 
      StatusBarControllerView 
        Text("Content goes here")
      
    
  

虽然它在视觉上运行良好,但我现在在控制台中收到以下错误:

Unbalanced calls to begin/end appearance transitions for <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x13ee0d9d0>.

有谁知道如何修改提供的代码以避免出现此错误消息?在private extension UIApplication中设置根视图控制器时出现问题: windows.first?.rootViewController = hostingController

2021 年 1 月 3 日澄清

我尝试以这种方式更改状态栏颜色而不是通过编辑 plist 的原因是因为我希望能够在我的应用程序的任何视图中动态更改状态栏颜色,而不是在应用程序范围内,如下图:

struct MyView: View 
  var body: some View 
    MySubview()
      .statusBar(style: .darkContent)
  

【问题讨论】:

欢迎来到 SO - 请使用 tour 并阅读 How to Ask 以改进、编辑和格式化您的问题。如果没有Minimal Reproducible Example,就无法帮助您解决问题。 我现在已经编辑了我的问题以回应 3 个不喜欢。具体来说,我将我的代码添加到问题本身(而不是链接它),并通过将其变成问题来改进标题。如果您对我的问题仍有不满意的方面,请在此处发表评论,以便我做出您想要的改进。 【参考方案1】:

在深入研究之前,我看到一些答案让我相信这没有必要,可以通过更改您的 plist 来解决:here 和 here。

假设您确实想要一个主机控制器,我认为问题在于您以两种不同的方式设置视图。一个是从应用程序主体调用的空视图(通过StatusBarControllerView),另一个是通过窗口 rootViewController 分配控制权的宿主 UIView。这可以通过将EmptyView() 更改为一些可见视图来证明;它没有出现,因为 HostingController 正在替换它。 Swift 能够替换它,但会抛出你看到的错误,让你知道这正在发生。 如果您确实希望 HostingController 成为主视图,您可以通过将 StatusBarControllerView 更改为 UIViewRepresentable 来直接设置它:

struct StatusBarControllerView<Content: View>: UIViewRepresentable 
    var content: () -> (Content)
    
    func makeUIView(context: Context) -> some UIView 
        UIApplication.shared.setHostingController(rootView: AnyView(content()))
        return UIApplication.hostingController?.view ?? UIView()
    
    
    func updateUIView(_ uiView: UIViewType, context: Context) 

并删除windows.first?.rootViewController = hostingController 行,使其仅设置一次。

您还可以根据this answer 在场景委托中设置 HostingController。但是,这需要将场景委托和应用委托添加到您的项目 (here’s a decent tutorial on that)。

【讨论】:

非常感谢您研究透彻的回答!但是,虽然它确实解决了错误,但它现在不更新状态栏样式,而且我的导航视图颜色不再延伸到状态栏区域。状态栏不再变化的原因可能是因为我只是从hostingController 中获取view,而不是整个hosting Controller。要解决此问题,除非您有其他建议,否则我似乎需要像您提到的那样添加一个场景委托,或者我只需要忍受控制台错误,直到 Apple 发布内置的 SwiftUI 解决方案。 是的,这看起来很准确,抱歉我没有测试。正如您所说,仅使用视图可能不允许它充当完整的托管控制器。如果更改 plist 不起作用,那么使用应用委托和场景委托解决方案可能是最好的选择。

以上是关于为啥在 SwiftUI Lifecycle 中将 rootViewController 设置为 UIHostingController 时会出错?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 为啥不能在视图之间传递发布者?

在 SwiftUI 中将 JSON 数据作为对象加载

如何在swiftui中将vstack移动到屏幕顶部?

在 SwiftUI 中将部分文本加粗

如何在swiftUI中将背景颜色添加到列表中

如何在 SwiftUI 中将 TabView 与 NavigationView 一起使用?