Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
683 views
in Technique[技术] by (71.8m points)

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

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 console and the view is loaded. Except when the view loads in loads on top of the previous view. Photo of what I'm talking about. I can't use a navigation button because you can't have them preform functions and I don't want back button at the top left of the screen after a login. Here is the code I have now;

    @State var username: String = ""
    @State var password: String = ""
    @State var sayHello = false
    @State private var showingAreaView = false
    @State private var showingAlert = false
    @State private var alertTitle = ""
    @State private var alertMessage = ""

Button(action: {
      self.login()

      })//end of login function
               {
                       Image("StartNow 3").resizable()
                         .scaledToFit()
                        .padding()
                       }
                if showingAreaView == true {
                    AreaView()
                        .animation(.easeIn)
                }


//actual Login func

    func login(){
        let login = self.username
        let passwordstring = self.password
         guard let url = URL(string: "http://localhost:8000/account/auth/") else {return}

         let headers = [
             "Content-Type": "application/x-www-form-urlencoded",
             "cache-control": "no-cache",
             "Postman-Token": "89a81b3d-d5f3-4f82-8b7f-47edc39bb201"
         ]

         let postData = NSMutableData(data: "username=(login)".data(using: String.Encoding.utf8)!)
         postData.append("&password=(passwordstring)".data(using: String.Encoding.utf8)!)

         let request = NSMutableURLRequest(url: NSURL(string: "http://localhost:8000/account/auth/")! as URL,
             cachePolicy:.useProtocolCachePolicy, timeoutInterval: 10.0)
         request.httpMethod = "POST"
         request.allHTTPHeaderFields = headers
         request.httpBody = postData as Data

         let session = URLSession.shared
         let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) in
             if let httpResponse = response as? HTTPURLResponse {
                 guard let data = data else {return}
                 print(data)
                 if httpResponse.statusCode == 200{
                     DispatchQueue.main.async {
                         //Segue to new view goes here
                         print(httpResponse.statusCode)
                        self.showingAreaView = true

                     }
                 }else{
             if httpResponse.statusCode == 400{
                     DispatchQueue.main.async {
                    self.alertTitle = "Oops"
                    self.alertMessage = "Username or Password Incorrect"
                    self.showingAlert = true
                         print(httpResponse.statusCode)

                         }
                     }else{
                         DispatchQueue.main.async {
                            self.alertTitle = "Well Damn"
                            self.alertMessage = "Ay chief we have no idea what just happened but it didn't work"
                            self.showingAlert = true

                         }
                     }
             }
                 do{

                     let JSONFromServer = try JSONSerialization.jsonObject(with: data, options: [])
                     let decoder = JSONDecoder()
                     decoder.keyDecodingStrategy = .convertFromSnakeCase
                     let tokenArray = try decoder.decode(token.self, from: data)
                     print(tokenArray.token)
                     UserDefaults.standard.set(tokenArray.token, forKey: "savedToken")
                     let savedToken = UserDefaults.standard.object(forKey: "savedToken")
                     print(savedToken)
                 }catch{
                     if httpResponse.statusCode == 400{
                    self.alertTitle = "Oops"
                    self.alertMessage = "Username or Password Incorrect"
                    self.showingAlert = true

                     }
                     print(error)
                     print(httpResponse.statusCode)
                 }
             } else if let error = error {
                self.alertTitle = "Well Damn"
                self.alertMessage = "Ay chief we have no idea what just happened but it didn't work"
                self.showingAlert = true
                 print(error)
             }
         })
         dataTask.resume()
    }


I've tired to google this issue but haven't had any luck. All I need is a basic segue to a new view without the need of a NavigationButton.

Any help is appreciated!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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)

Opacity Transition Offset Transition

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...