简体   繁体   English

以编程方式导航到 SwiftUI 中的新视图

[英]Programmatically navigate to new view in SwiftUI

Descriptive example:描述性示例:

login screen, user taps "Login" button, request is performed, UI shows waiting indicator, then after successful response I'd like to automatically navigate user to the next screen.登录屏幕,用户点击“登录”按钮,执行请求,UI 显示等待指示器,然后在成功响应后我想自动将用户导航到下一个屏幕。

How can I achieve such automatic transition in SwiftUI?如何在 SwiftUI 中实现这种自动转换?

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.您应该在数据模型中处理登录过程,并使用@EnvironmentObject等绑定将isLoggedin传递给您的视图。

Note: In Xcode Version 11.0 beta 4 , to conform to protocol 'BindableObject' the willChange property has to be added注意:在 Xcode版本 11.0 beta 4 中,为了符合协议“BindableObject” ,必须添加willChange属性

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)
    // }
  }
}

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:为了将来参考,由于许多用户报告收到错误“函数声明不透明的返回类型”,要从@MoRezaFarahani 实现上述代码需要以下语法:

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这适用于 Xcode 11.4 和 Swift 5

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将导航链接与 $isActive 绑定变量一起使用

To expound what others have elaborated above based on changes on combine as of Swift Version 5.2 it could be simplified using publishers.为了阐述其他人根据Swift Version 5.2 combine 更改在上面详细说明的内容,可以使用发布者进行简化。

  1. Create a class names UserAuth as shown below don't forget to import import Combine .创建一个名为UserAuth的类,如下所示不要忘记导入import Combine
class UserAuth: ObservableObject {
        @Published var isLoggedin:Bool = false

        func login() {
            self.isLoggedin = true
        }
    }
  1. Update SceneDelegate.Swift with更新SceneDelegate.Swift

    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.成功认证后的仪表板,如果认证userAuth.isLoggedin = true那么它将被加载。

     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()
            }
    }
  }

Here is an extension on UINavigationController that has simple push/pop with SwiftUI views that gets the right animations.这是UINavigationController上的一个扩展,它具有简单的推送/弹出功能,带有 SwiftUI 视图,可以获得正确的动画。 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.NavigationLinkisActive绑定一起使用是正确的做法,但它不灵活或不可扩展。 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 .这是一个将它用于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()))

Now you need to just simply create an instance of the new View you want to navigate to and put that in NavigationButton:现在你只需要简单地创建一个你想要导航到的新视图的实例并将它的放在 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.如果您返回 true onTrigger 意味着您已成功登录用户。

I followed Gene's answer but there are two issues with it that I fixed below.我遵循了 Gene 的回答,但我在下面解决了两个问题。 The first is that the variable isLoggedIn must have the property @Published in order to work as intended.首先是变量 isLoggedIn 必须具有 @Published 属性才能按预期工作。 The second is how to actually use environmental objects.二是如何实际使用环境对象。

For the first, update UserAuth.isLoggedIn to the below:首先,将 UserAuth.isLoggedIn 更新为以下内容:

@Published var isLoggedin = false {
didSet {
  didChange.send(self)
}

The second is how to actually use Environmental objects.第二是如何实际使用环境对象。 This isn't really wrong in Gene's answer, I just noticed a lot of questions about it in the comments and I don't have enough karma to respond to them.这在 Gene 的回答中并没有错,我只是在评论中注意到了很多关于它的问题,我没有足够的业力来回应他们。 Add this to your SceneDelegate view:将此添加到您的 SceneDelegate 视图:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
    var userAuth = UserAuth()
    
    // Create the SwiftUI view that provides the window contents.
    let contentView = ContentView().environmentObject(userAuth)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM