簡體   English   中英

以編程方式導航到 SwiftUI 中的新視圖

[英]Programmatically navigate to new view in SwiftUI

描述性示例:

登錄屏幕,用戶點擊“登錄”按鈕,執行請求,UI 顯示等待指示器,然后在成功響應后我想自動將用戶導航到下一個屏幕。

如何在 SwiftUI 中實現這種自動轉換?

成功登錄后,您可以將下一個視圖替換為您的登錄視圖。 例如:

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

    }
}

您應該在數據模型中處理登錄過程,並使用@EnvironmentObject等綁定將isLoggedin傳遞給您的視圖。

注意:在 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)
    // }
  }
}

為了將來參考,由於許多用戶報告收到錯誤“函數聲明不透明的返回類型”,要從@MoRezaFarahani 實現上述代碼需要以下語法:

struct ContentView: View {

    @EnvironmentObject var userAuth: UserAuth 

    var body: some View {
        if !userAuth.isLoggedin {
            return AnyView(LoginView())
        } else {
            return AnyView(NextView())
        }

    }
}

這適用於 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)
        }
    }
}

將導航鏈接與 $isActive 綁定變量一起使用

為了闡述其他人根據Swift Version 5.2 combine 更改在上面詳細說明的內容,可以使用發布者進行簡化。

  1. 創建一個名為UserAuth的類,如下所示不要忘記導入import Combine
class UserAuth: ObservableObject {
        @Published var isLoggedin:Bool = false

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

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

  2. 您的身份驗證視圖

     struct LoginView: View { @EnvironmentObject var userAuth: UserAuth var body: some View { ... if ... { self.userAuth.login() } else { ... } } }
  3. 成功認證后的儀表板,如果認證userAuth.isLoggedin = true那么它將被加載。

     struct NextView: View { var body: some View { ... } }
  4. 最后,應用程序啟動后要加載的初始視圖。

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

這是UINavigationController上的一個擴展,它具有簡單的推送/彈出功能,帶有 SwiftUI 視圖,可以獲得正確的動畫。 我在上面的大多數自定義導航中遇到的問題是推送/彈出動畫關閉。 NavigationLinkisActive綁定一起使用是正確的做法,但它不靈活或不可擴展。 所以下面的擴展對我有用:

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

這是一個將它用於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()))

現在你只需要簡單地創建一個你想要導航到的新視圖的實例並將它的放在 NavigationButton 中:

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

如果您返回 true onTrigger 意味着您已成功登錄用戶。

我遵循了 Gene 的回答,但我在下面解決了兩個問題。 首先是變量 isLoggedIn 必須具有 @Published 屬性才能按預期工作。 二是如何實際使用環境對象。

首先,將 UserAuth.isLoggedIn 更新為以下內容:

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

第二是如何實際使用環境對象。 這在 Gene 的回答中並沒有錯,我只是在評論中注意到了很多關於它的問題,我沒有足夠的業力來回應他們。 將此添加到您的 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