It seems to me that Apple is encouraging us to give up using UIViewController
in SwiftUI, but without using view controlelrs, I feel a little bit powerless. Wha
I'm adding some snippets here because I think it simplifies some things and makes reusing navigation links easier:
1. Add View Navigation Extensions
extension View {
func navigatePush(whenTrue toggle: Binding<Bool>) -> some View {
NavigationLink(
destination: self,
isActive: toggle
) { EmptyView() }
}
func navigatePush<H: Hashable>(when binding: Binding<H>,
matches: H) -> some View {
NavigationLink(
destination: self,
tag: matches,
selection: Binding<H?>(binding)
) { EmptyView() }
}
func navigatePush<H: Hashable>(when binding: Binding<H?>,
matches: H) -> some View {
NavigationLink(
destination: self,
tag: matches,
selection: binding
) { EmptyView() }
}
}
Now, you can call on any view (make sure they (or a parent) are in a navigation view)
2. Use at leisure
struct Example: View {
@State var toggle = false
@State var tag = 0
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 24) {
Text("toggle pushed me")
.navigatePush(whenTrue: $toggle)
Text("tag pushed me (2)")
.navigatePush(when: $tag, matches: 2)
Text("tag pushed me (4)")
.navigatePush(when: $tag, matches: 4)
Button("toggle") {
self.toggle = true
}
Button("set tag 2") {
self.tag = 2
}
Button("set tag 4") {
self.tag = 4
}
}
}
}
}
as @Bhodan mentioned you can do it by changing state
Using EnvironmentObject with SwiftUI
class UserData: ObservableObject, Identifiable {
let id = UUID()
@Published var firebase_uid: String = ""
@Published var name: String = ""
@Published var email: String = ""
@Published var loggedIn: Bool = false
}
the loggedIn
property will be used to monitor when a change in user logs in or out
@EnvironmentObject
in your SceneDelegate.swift
file in Xcode
this just makes it so its accessible everywhere in your app class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
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).
// Create the SwiftUI view that provides the window contents.
let userData = UserData()
let contentView = ContentView().environmentObject(userData)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
Once you make any change to the loggedIn
property any UI that is Binded to it will respond to the true/false value change
the as @Bhodan mentioned just add this to your view and it will respond to that change
struct LoginView: View {
@EnvironmentObject var userData: UserData
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: ProfileView(), isActive: self.$userData.loggedin) {
EmptyView()
}.hidden()
}
}
}
}
I've found the answer. If you want to show another view on callback you should
1) Create state @State var pushActive = false
2) When ViewModel notifies that login is successful set pushActive
to true
func handleSuccessfullLogin() {
self.pushActive = true
print("handleSuccessfullLogin")
}
3) Create hidden NavigationLink
and bind to that state
NavigationLink(destination: ProfileView(viewModel: ProfileViewModelImpl()), isActive: self.pushActive) {
Text("")
}.hidden()
As of beta 5, NavigationLink is the mechanism used to programmatically push views. You can see an example of it here.
Workaround without creating additional empty views.
You can use .disabled(true)
or .allowsHitTesting(false)
modifiers to disable taps on NavigationLink.
Disadvantage: You loose default button tap highlighting.
NavigationLink(destination: EnterVerificationCodeScreen(), isActive: self.$viewModel.verifyPinIsShowing) {
Text("Create an account")
}
.allowsHitTesting(false) // or .disabled(true)
.buttonStyle(ShadowRadiusButtonStyle(type: .dark, height: 38))