【问题标题】:Change the root view of UIHostingController in SwiftUI在 SwiftUI 中更改 UIHostingController 的根视图
【发布时间】:2021-05-25 19:09:28
【问题描述】:

对于一个新的SwiftUI iOS 应用,我在SceneDelegate 中执行以下操作

if let windowScene = scene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    if Auth().token == nil {
        window.rootViewController = UIHostingController(rootView: StartRegistrationView())
    } else {
        window.rootViewController = UIHostingController(rootView: MainTabbedView())
    }
    self.window = window
    window.makeKeyAndVisible()
}

当用户尚未注册或登录时,他们将进入注册流程。

用户注册后,如何切换 RootView 以转到我的 TabView?我似乎无法使用SwiftUI 找到任何解决方案。

我应该改用Environment 对象并监听用户Auth 状态的变化吗?

【问题讨论】:

    标签: ios swift swiftui


    【解决方案1】:

    声明一个 AppRootView,如下所示:

    struct AppRootView: View {
    
        @ObservedObject private var auth: Auth
        var body: some View {
            Group {
                if auth.token != nil {
                    MainTabbedView()
                } else {
                    StartRegistrationView()
                }
            }
        }
    }
    

    然后在 SceneDelegate 中将其设置为根视图:

    window.rootViewController = UIHostingController(rootView: AppRootView(auth: $auth))

    您必须将视图绑定到您的 Auth() ,方法是像我上面所做的那样传递它,或者在您的环境中设置它。 SwiftUI 的美妙之处在于,只要令牌不为零,视图就会重绘,您的用户会在 MainTabbedView 中找到它们自己。

    【讨论】:

    • 关于:属性类型“Auth”的任何想法与其包装类型“ObservedObject”的“wrappedValue”属性不匹配
    • 你的 Auth 类型是否实现了 ObservableObject?
    • 是的,它必须是一个 ObservableObject。
    • 截至今天,将输出包装在 Group 中会生成编译器警告。相反,我们可以使用类型擦除的包装器AnyView,例如AnyView(MainTabbedView())(不需要Group
    • 惊人的方法,但有没有办法让这种观点的变化动起来?我自己做不到。
    【解决方案2】:
    let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
    
    if let windowScenedelegate = scene?.delegate as? SceneDelegate {
       let window = UIWindow(windowScene: scene!)
       window.rootViewController = UIHostingController(rootView:ContentView())
       windowScenedelegate.window = window
       window.makeKeyAndVisible()
    }
    

    通过使用它,我们可以通过实现上述代码在任何按钮单击中更改 rootView。

    【讨论】:

    • 这部分工作。在重新启动应用程序之前,新视图上的按钮根本不会响应。有没有办法解决这个问题?
    【解决方案3】:

    很好的答案 LuLugaga,如果你不想使用@Observablebject 就更新所以不会一直更新,你可以使用Subject,一旦你更新令牌字符串,RootView 就会更新。

    struct RootView: View {
    
        var loginViewModel: LoginViewModel = LoginViewModel()
    
        @State var tokenString = ""
    
        var body: some View {
            Group {
                tokenString.count > 0 ? AnyView(ContentView(model: playerViewModel)) :  AnyView(LoginView(loginViewModel: loginViewModel))
            }.onReceive(loginViewModel.tokenString) {
                self.tokenString = $0
            }
        }
    }
    
    
    class LoginViewModel {
    
        let tokenString = PassthroughSubject<String, Never>()
    
        var token: String {
            get { return "" }
        set {
            self.tokenString.send(newValue)
        }
    }
    

    【讨论】:

      【解决方案4】:

      你可以创建一个路由器类

      import Foundation
      import UIKit
      import SwiftUI
      
      class Router {
          
          class var window: UIWindow? {
              if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
                  if let sceneDelegate = scene.delegate as? SceneDelegate {
                      let window = UIWindow(windowScene: scene)
                      sceneDelegate.window = window
                      window.makeKeyAndVisible()
                      return window
                  }
              }
              return nil
          }
          
          static func showMain() {
              window?.rootViewController = UIHostingController(rootView: ContentView())
          }
          
      }
      

      用法:

      Router.showMain()
      

      有了这个,你可以在任何给定的时间决定你想要哪个窗口作为你的根。

      【讨论】:

        【解决方案5】:

        对于更改 rootview 时的某些动画,请在 sceneDelegate 中使用以下代码:

            window.rootViewController = UIHostingController(rootView: HomeView())
        
            // A mask of options indicating how you want to perform the animations.
            let options: UIView.AnimationOptions = .transitionCrossDissolve
        
            // The duration of the transition animation, measured in seconds.
            let duration: TimeInterval = 0.3
        
            // Creates a transition animation.
            UIView.transition(with: window, duration: duration, options: options, animations: {}, completion:
            { completed in
                // maybe do something on completion here
            })
        

        【讨论】:

        • 在单击按钮时更改 rootview 控制器时,新视图上的按钮变得无响应。有没有办法解决这个问题?
        • 无响应与这种方法无关。我相信这可能是由于按钮的 zindex 造成的。尝试更改按钮的索引。 ".zindex(1)"
        【解决方案6】:

        在较新的 Xcode 中,有一些 SwiftUI 模板更改,下面是您的初始视图的加载方式。参考https://developer.apple.com/documentation/swiftui/app

        @main
        struct AuthCheckApp: App {
            var body: some Scene {
                WindowGroup {
                    WelcomeView()
                }
            }
        }
        

        所以在这种情况下,第一个视图是WelcomeView,这个视图负责导航到正确的视图,它可能是登录,主页

        struct WelcomeView: View {
            
            @ObservedObject private var auth = Auth()
            
            var body: some View {
                if auth.token != nil {
                    HomeView()
                } else {
                    SignUpView(auth: auth)
                }
            }
        }
        

        Auth 是一个确认ObservableObject 协议的类,并且有一个名为token 的已发布属性。因此,当此令牌具有值时,它将在上述情况下加载 HomeView,如果为 nil,它将打开 SignUpView

        class Auth: ObservableObject {
            @Published var token: String?
        }
        
        
        struct SignUpView: View {
            
            let auth: Auth
            
            var body: some View {
                VStack {
                    Text("Hello, please click below button to login")
                        .padding()
                    Button("Login") {
                        print("Loogin Tapped")
                        auth.token = "TOKEN"
                    }
                }
            }
        }
        
        struct HomeView: View {
            var body: some View {
                Text("Welcome Parth!")
                    .padding()
                    .background(Color.red)
            }
        }
        

        如果您有 api 依赖项并且需要等待令牌,那么这种方法将很有帮助,然后您可以使用 WelcomeScreen 作为一些启动或动画。

        【讨论】:

          猜你喜欢
          • 2022-10-14
          • 2023-03-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-23
          相关资源
          最近更新 更多