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")
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With