Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to transition Views programmatically using SwiftUI?

Tags:

swift

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")
}
like image 221
Kevin Avatar asked Jul 22 '19 16:07

Kevin


2 Answers

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.

like image 177
Vlad Lego Avatar answered Oct 26 '22 16:10

Vlad Lego


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

like image 2
Bogdan Farca Avatar answered Oct 26 '22 17:10

Bogdan Farca