Programmatically navigate to new view in SwiftUI

后端 未结 6 505
梦如初夏
梦如初夏 2020-12-01 20:52

Descriptive example:

login screen, user taps \"Login\" button, request is performed, UI shows waiting indicator, then after successful response I\'d like to automati

相关标签:
6条回答
  • 2020-12-01 21:29

    Now you need to just simply create an instance of the new View you want to navigate to and put that in NavigationButton:

    NavigationButton(destination: NextView(), isDetail: true, onTrigger: { () -> Bool in
        return self.done
    }) {
        Text("Login")
    }
    

    If you return true onTrigger means you successfully signed user in.

    0 讨论(0)
  • 2020-12-01 21:35

    For future reference, as a number of users have reported getting the error "Function declares an opaque return type", to implement the above code from @MoRezaFarahani requires the following syntax:

    struct ContentView: View {
    
        @EnvironmentObject var userAuth: UserAuth 
    
        var body: some View {
            if !userAuth.isLoggedin {
                return AnyView(LoginView())
            } else {
                return AnyView(NextView())
            }
    
        }
    }
    

    This is working with Xcode 11.4 and Swift 5

    0 讨论(0)
  • 2020-12-01 21:41

    Here is an extension on UINavigationController that has simple push/pop with SwiftUI views that gets the right animations. The problem I had with most custom navigations above was that the push/pop animations were off. Using NavigationLink with an isActive binding is the correct way of doing it, but it's not flexible or scalable. So below extension did the trick for me:

    /**
     * Since SwiftUI doesn't have a scalable programmatic navigation, this could be used as
     * replacement. It just adds push/pop methods that host SwiftUI views in UIHostingController.
     */
    extension UINavigationController: UINavigationControllerDelegate {
    
        convenience init(rootView: AnyView) {
            let hostingView = UIHostingController(rootView: rootView)
            self.init(rootViewController: hostingView)
    
            // Doing this to hide the nav bar since I am expecting SwiftUI
            // views to be wrapped in NavigationViews in case they need nav.
            self.delegate = self
        }
    
        public func pushView(view:AnyView) {
            let hostingView = UIHostingController(rootView: view)
            self.pushViewController(hostingView, animated: true)
        }
    
        public func popView() {
            self.popViewController(animated: true)
        }
    
        public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
            navigationController.navigationBar.isHidden = true
        }
    }
    

    Here is one quick example using this for the window.rootViewController.

    var appNavigationController = UINavigationController.init(rootView: rootView)
    window.rootViewController = appNavigationController
    window.makeKeyAndVisible()
    
    // Now you can use appNavigationController like any UINavigationController, but with SwiftUI views i.e. 
    appNavigationController.pushView(view: AnyView(MySwiftUILoginView()))
    
    0 讨论(0)
  • 2020-12-01 21:47

    You can replace the next view with your login view after a successful login. For example:

    struct LoginView: View {
        var body: some View {
            ...
        }
    }
    
    struct NextView: View {
        var body: some View {
            ...
        }
    }
    
    // Your starting view
    struct ContentView: View {
    
        @EnvironmentObject var userAuth: UserAuth 
    
        var body: some View {
            if !userAuth.isLoggedin {
                LoginView()
            } else {
                NextView()
            }
    
        }
    }
    

    You should handle your login process in your data model and use bindings such as @EnvironmentObject to pass isLoggedin to your view.

    Note: In Xcode Version 11.0 beta 4, to conform to protocol 'BindableObject' the willChange property has to be added

    import Combine
    
    class UserAuth: ObservableObject {
    
      let didChange = PassthroughSubject<UserAuth,Never>()
    
      // required to conform to protocol 'ObservableObject' 
      let willChange = PassthroughSubject<UserAuth,Never>()
    
      func login() {
        // login request... on success:
        self.isLoggedin = true
      }
    
      var isLoggedin = false {
        didSet {
          didChange.send(self)
        }
    
        // willSet {
        //       willChange.send(self)
        // }
      }
    }
    
    0 讨论(0)
  • 2020-12-01 21:48

    To expound what others have elaborated above based on changes on combine as of Swift Version 5.2 it could be simplified using publishers.

    1. Create a class names UserAuth as shown below don't forget to import import Combine.
    class UserAuth: ObservableObject {
            @Published var isLoggedin:Bool = false
    
            func login() {
                self.isLoggedin = true
            }
        }
    
    1. Update SceneDelegate.Swift with

      let contentView = ContentView().environmentObject(UserAuth())

    2. Your authentication view

       struct LoginView: View {
          @EnvironmentObject  var  userAuth: UserAuth
          var body: some View {
              ...
          if ... {
          self.userAuth.login()
          } else {
          ...
          }
       }
      }
      
      
    3. Your dashboard after successful authentication, if the authentication userAuth.isLoggedin = true then it will be loaded.

         struct NextView: View {
           var body: some View {
           ...
           }
         }
      
    4. Lastly, the initial view to be loaded once the application is launched.

    struct ContentView: View {
        @EnvironmentObject var userAuth: UserAuth 
        var body: some View {
            if !userAuth.isLoggedin {
                    LoginView()
                } else {
                    NextView()
                }
        }
      }
    
    0 讨论(0)
  • 2020-12-01 21:49
    struct LoginView: View {
        
        @State var isActive = false
        @State var attemptingLogin = false
        
        var body: some View {
            ZStack {
                NavigationLink(destination: HomePage(), isActive: $isActive) {
                    Button(action: {
                        attlempinglogin = true
                        // Your login function will most likely have a closure in 
                        // which you change the state of isActive to true in order 
                        // to trigger a transition
                        loginFunction() { response in
                            if response == .success {
                                self.isActive = true
                            } else {
                                self.attemptingLogin = false
                            }
                        }
                    }) {
                        Text("login")
                    }
                }
                
                WaitingIndicator()
                    .opacity(attemptingLogin ? 1.0 : 0.0)
            }
        }
    }
    

    Use Navigation link with the $isActive binding variable

    0 讨论(0)
提交回复
热议问题