Push View programmatically in callback, SwiftUI

后端 未结 5 1606
长发绾君心
长发绾君心 2020-12-17 09:25

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

相关标签:
5条回答
  • 2020-12-17 09:58

    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
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-17 10:00

    as @Bhodan mentioned you can do it by changing state

    Using EnvironmentObject with SwiftUI

    1. Add UserData ObservableObject :
    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

    1. Now add it as an @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()
       }
      }
     }
    }
    
    0 讨论(0)
  • 2020-12-17 10:04

    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()
    
    0 讨论(0)
  • 2020-12-17 10:08

    As of beta 5, NavigationLink is the mechanism used to programmatically push views. You can see an example of it here.

    0 讨论(0)
  • 2020-12-17 10:21

    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))
    
    0 讨论(0)
提交回复
热议问题