简体   繁体   中英

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. Is there such an alternative in SwiftUI?

The NavigationButton solution does not work as I need to validate the user input before transitioning to the other view.

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.

NavigationDestinationLink and NavigationButton have been deprecated and replaced by NavigationLink .

Here's a full working example of programatically pushing a view to a 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. If you have a button that triggers the link, you'll use the label of the 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. .hidden() doesn't have the same effect.

You could do it like bellow, based on this response (it's packed like a Playground for easy testing:

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. 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 ).

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.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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