Segue to a new view in SwftUI after a successful authentication from API without a NavigationButton

后端 未结 2 517
心在旅途
心在旅途 2020-12-12 05:37

I am trying to segue to an entirely new view after I get a 200 OK response code from my API. The response code is successful sent back to the app and is printed into the con

相关标签:
2条回答
  • 2020-12-12 05:57

    You could use a flag to trigger a navigation, curently it is not possible to navigate without a NavigationLink

    struct ContentView: View {
    
        @State var logedIn = false
    
        var body: some View {
    
            return NavigationView {
                VStack {
                    NavigationLink(destination: DestinationView(), isActive: $logedIn, label: { 
                        EmptyView() 
                    })// It won't appear on screen because the label is EmptyView
                    Button("log in") {
                        // log in logic
                        if succesful login {
                            logedIn = true // this will trigger the NavigationLink
                        }
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-12 06:21

    Assuming you've got two basic views (e.g., a LoginView and a MainView), you can transition between them in a couple ways. What you'll need is:

    1. Some sort of state that determines which is being shown
    2. Some wrapping view that will transition between two layouts when #1 changes
    3. Some way of communicating data between the views

    In this answer, I'll combine #1 & #3 in a model object, and show two examples for #2. There are lots of ways you could make this happen, so play around and see what works best for you.

    Note that there is a lot of code just to style the views, so you can see what's going on. I've commented the critical bits.

    Pictures (opacity method on left, offset method on right)

    The model (this satisfies #1 & #3)

    class LoginStateModel: ObservableObject {
        // changing this will change the main view
        @Published var loggedIn = false
        // will store the username typed on the LoginView
        @Published var username = ""
    
        func login() {
            // simulating successful API call
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                // when we log in, animate the result of setting loggedIn to true
                //   (i.e., animate showing MainView)
                withAnimation(.default) {
                    self.loggedIn = true
                }
            }
        }
    }
    

    The top-level view (this satisfies #2)

    struct ContentView: View {
        @ObservedObject var model = LoginStateModel()
    
        var body: some View {
            ZStack {
                // just here for background
                Color(UIColor.cyan).opacity(0.3)
                    .edgesIgnoringSafeArea(.all)
    
                // we show either LoginView or MainView depending on our model
                if model.loggedIn {
                    MainView()
                } else {
                    LoginView()
                }
            }
                // this passes the model down to descendant views
                .environmentObject(model)
        }
    }
    

    The default transition for adding and removing views from the view hierarchy is to change their opacity. Since we wrapped our changes to model.loggedIn in withAnimation(.default), this opacity change will happen slowly (its better on a real device than the compressed GIFs below).

    Alternatively, instead of having the views fade in/out, we could have them move on/off screen using an offset. For the second example, replace the if/else block above (including the if itself) with

    MainView()
        .offset(x: model.loggedIn ? 0 : UIScreen.main.bounds.width, y: 0)
    LoginView()
        .offset(x: model.loggedIn ? -UIScreen.main.bounds.width : 0, y: 0)
    

    The login view

    struct LoginView: View {
        @EnvironmentObject var model: LoginStateModel
    
        @State private var usernameString = ""
        @State private var passwordString = ""
    
        var body: some View {
            VStack(spacing: 15) {
                HStack {
                    Text("Username")
                    Spacer()
                    TextField("Username", text: $usernameString)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
                HStack {
                    Text("Password")
                    Spacer()
                    SecureField("Password", text: $passwordString)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
                Button(action: {
                    // save the entered username, and try to log in
                    self.model.username = self.usernameString
                    self.model.login()
                }, label: {
                    Text("Login")
                        .font(.title)
                        .inExpandingRectangle(Color.blue.opacity(0.6))
                })
                    .buttonStyle(PlainButtonStyle())
            }
            .padding()
            .inExpandingRectangle(Color.gray)
            .frame(width: 300, height: 200)
        }
    }
    

    Note that in a real functional login form, you'd want to do some basic input sanitation and disable/rate limit the login button so you don't get a billion server requests if someone spams the button.

    For inspiration, see:
    Introducing Combine (WWDC Session)
    Combine in Practice (WWDC Session)
    Using Combine (UIKit example, but shows how to throttle network requests)

    The main view

    struct MainView: View {
        @EnvironmentObject var model: LoginStateModel
    
        var body: some View {
            VStack(spacing: 15) {
                ZStack {
                    Text("Hello \(model.username)!")
                        .font(.title)
                        .inExpandingRectangle(Color.blue.opacity(0.6))
                        .frame(height: 60)
    
                    HStack {
                        Spacer()
                        Button(action: {
                            // when we log out, animate the result of setting loggedIn to false
                            //   (i.e., animate showing LoginView)
                            withAnimation(.default) {
                                self.model.loggedIn = false
                            }
                        }, label: {
                            Text("Logout")
                                .inFittedRectangle(Color.green.opacity(0.6))
                        })
                            .buttonStyle(PlainButtonStyle())
                            .padding()
                    }
                }
    
                Text("Content")
                    .inExpandingRectangle(.gray)
            }
            .padding()
        }
    }
    

    Some convenience extensions

    extension View {
        func inExpandingRectangle(_ color: Color) -> some View {
            ZStack {
                RoundedRectangle(cornerRadius: 15)
                    .fill(color)
                self
            }
        }
    
        func inFittedRectangle(_ color: Color) -> some View {
            self
                .padding(5)
                .background(RoundedRectangle(cornerRadius: 15)
                    .fill(color))
        }
    }
    
    0 讨论(0)
提交回复
热议问题