简体   繁体   English

如何使用 SwiftUI 以编程方式转换视图?

[英]How to transition Views programmatically using SwiftUI?

I want to show the user another view when the login is successful, otherwise stay on that view.我想在登录成功时向用户显示另一个视图,否则留在该视图上。 I've done that with UIKit by performing a segue.我已经通过执行转场使用 UIKit 做到了这一点。 Is there such an alternative in SwiftUI? SwiftUI 中有这样的选择吗?

The NavigationButton solution does not work as I need to validate the user input before transitioning to the other view. NavigationButton 解决方案不起作用,因为我需要在转换到另一个视图之前验证用户输入。

Button(action: {
    let authService = AuthorizationService()
    let result = authService.isAuthorized(username: self.username, password: self.password)
    if(result == true) {
        print("Login successful.")
        // TODO: ADD LOGIC
        *** HERE I WANT TO PERFORM THE SEGUE ***

        presentation(MainView)
    } else {
        print("Login failed.")
    }
}) {
    Text("Login")
}

Xcode 11 beta 5. Xcode 11 测试版 5。

NavigationDestinationLink and NavigationButton have been deprecated and replaced by NavigationLink . NavigationDestinationLinkNavigationButton已被弃用并由NavigationLink取代。

Here's a full working example of programatically pushing a view to a NavigationView.这是以编程方式将视图推送到 NavigationView 的完整工作示例。

import SwiftUI
import Combine


enum MyAppPage {
    case Menu
    case SecondPage
}

final class MyAppEnvironmentData: ObservableObject {
    @Published var currentPage : MyAppPage? = .Menu
}

struct NavigationTest: View {

    var body: some View {
        NavigationView {
            PageOne()
        }
    }
}


struct PageOne: View {
    @EnvironmentObject var env : MyAppEnvironmentData


    var body: some View {
        let navlink = NavigationLink(destination: PageTwo(),
                       tag: .SecondPage,
                       selection: $env.currentPage,
                       label: { EmptyView() })

        return VStack {
            Text("Page One").font(.largeTitle).padding()

            navlink
            .frame(width:0, height:0)

            Button("Button") {
                self.env.currentPage = .SecondPage
            }
            .padding()
            .border(Color.primary)

        }
    }

}


struct PageTwo: View {

    @EnvironmentObject var env : MyAppEnvironmentData

    var body: some View {
        VStack {
            Text("Page Two").font(.largeTitle).padding()

            Text("Go Back")
            .padding()
            .border(Color.primary)
            .onTapGesture {
                self.env.currentPage = .Menu
            }
        }.navigationBarBackButtonHidden(true)
    }
}

#if DEBUG
struct NavigationTest_Previews: PreviewProvider {
    static var previews: some View {
        NavigationTest().environmentObject(MyAppEnvironmentData())
    }
}
#endif

Note that the NavigationLink entity has to be present inside the View body.请注意, NavigationLink实体必须存在于视图主体内。 If you have a button that triggers the link, you'll use the label of the NavigationLink.如果您有触发链接的按钮,您将使用 NavigationLink 的标签。 In this case, the NavigationLink is hidden by setting its frame to 0,0, which is kind of a hack but I'm not aware of a better method at this point.在这种情况下,NavigationLink 通过将其框架设置为 0,0 来隐藏,这是一种黑客行为,但我目前不知道更好的方法。 .hidden() doesn't have the same effect. .hidden() 没有同样的效果。

You could do it like bellow, based on this response (it's packed like a Playground for easy testing:根据响应,您可以像下面那样做(它像 Playground 一样打包以便于测试:

import SwiftUI
import Combine
import PlaygroundSupport


struct ContentView: View {
    var body: some View {
        NavigationView {
            MainView().navigationBarTitle(Text("Main View"))
        }
    }
}

struct MainView: View {
    let afterLoginView = DynamicNavigationDestinationLink(id: \String.self) { message in
        AfterLoginView(msg: message)
    }

    var body: some View {
        Button(action: {
            print("Do the login logic here")
            self.afterLoginView.presentedData?.value = "Login successful"
        }) {
            Text("Login")
        }
    }
}

struct AfterLoginView: View {
    let msg: String

    var body: some View {
        Text(msg)
    }
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())

Although this will work, I think that, from an architectural perspective, you try to push an "imperative programming" paradigm into SwiftUI's reactive logic.虽然这会奏效,但我认为,从架构的角度来看,您尝试将“命令式编程”范式推入 SwiftUI 的响应式逻辑中。 I mean, I would rather implement it with the login logic wrapped into an ObjectBinding class with an exposed isLoggedin property and make the UI react to the current state (represented by isLoggedin ).我的意思是,我宁愿将登录逻辑封装到ObjectBinding类中,并带有公开的isLoggedin属性来实现它,并使 UI 对当前状态(由isLoggedin表示)做出反应。

Here's a very high level example :这是一个非常高级的示例:

struct MainView: View {
    @ObjectBinding private var loginManager = LoginManager()

    var body: some View {
        if loginManager.isLoggedin {
            Text("After login content")
        } else {
            Button(action: {  
                 self.loginManager.login()
            }) {
                 Text("Login")
            }
        }
    }
}

I used a Bool state for my login transition, it seems pretty fluid.我使用 Bool 状态进行登录转换,它看起来非常流畅。

struct ContentView: View {
    @State var loggedIn = false
    
    var body: some View {
        VStack{
            if self.loggedIn {
                Text("LoggedIn")
                Button(action: {
                    self.loggedIn = false 

                }) {
                    Text("Log out")
                }
            } else {
                LoginPage(loggedIn: $loggedIn)
            }
        }
    }
}

This can also be done programmatically such as below: 也可以通过编程方式完成,例如:

let vc = self.storyboard?.instantiateViewController(withIdentifier: "TheTargetViewController")
                            self.present(vc!, animated: true, completion: nil)

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

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