在没有导航按钮的情况下从 API 成功身份验证后,转到 SwftUI 中的新视图 [英] Segue to a new view in SwftUI after a successful authentication from API without a NavigationButton

查看:16
本文介绍了在没有导航按钮的情况下从 API 成功身份验证后,转到 SwftUI 中的新视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在从我的 API 获得 200 OK 响应代码后,我正尝试转向一个全新的视图.响应代码成功发送回应用程序并打印到控制台并加载视图.除非视图加载在上一个视图的顶部.

模型(这满足#1 & #3)

class LoginStateModel: ObservableObject {//更改此项将更改主视图@Published var 登录 = false//将存储在 LoginView 上输入的用户名@Published var username = ""功能登录(){//模拟成功的API调用DispatchQueue.main.asyncAfter(deadline: .now() + 1) {//当我们登录时,动画设置loggedIn为true的结果//(即动画显示 MainView)withAnimation(.default) {self.loggedIn = 真}}}}

顶级视图(这满足#2)

struct ContentView:查看{@ObservedObject var 模型 = LoginStateModel()var主体:一些视图{ZStack {//这里只是背景颜色(UIColor.cyan).不透明度(0.3).edgesIgnoringSafeArea(.all)//我们根据我们的模型显示 LoginView 或 MainView如果 model.loggedIn {主视图()} 别的 {登录视图()}}//这将模型向下传递给后代视图.environmentObject(模型)}}

从视图层次结构中添加和删除视图的默认转换是更改它们的不透明度.由于我们在 withAnimation(.default) 中包含了对 model.loggedIn 的更改,因此这种不透明度更改将缓慢发生(在真实设备上比下面的压缩 GIF 更好).

或者,我们可以使用偏移量让它们在屏幕上/下移动,而不是让视图淡入/淡出.对于第二个示例,将上面的 if/else 块(包括 if 本身)替换为

MainView().offset(x: model.loggedIn ? 0 : UIScreen.main.bounds.width, y: 0)登录视图().offset(x: model.loggedIn ? -UIScreen.main.bounds.width : 0, y: 0)

登录视图

struct LoginView: View {@EnvironmentObject var 模型:LoginStateModel@State 私有 var usernameString = ""@State private var passwordString = ""var主体:一些视图{VStack(间距:15){堆栈{文本(用户名")垫片()TextField("用户名", text: $usernameString).textFieldStyle(RoundedBorderTextFieldStyle())}堆栈{文本(密码")垫片()SecureField("密码",文本:$passwordString).textFieldStyle(RoundedBorderTextFieldStyle())}按钮(动作:{//保存输入的用户名,并尝试登录self.model.username = self.usernameStringself.model.login()}, 标签: {文本(登录").font(.title).inExpandingRectangle(Color.blue.opacity(0.6))}).buttonStyle(PlainButtonStyle())}.填充().inExpandingRectangle(Color.gray).frame(宽:300,高:200)}}

请注意,在真正的功能登录表单中,您需要进行一些基本的输入清理并禁用/速率限制登录按钮,这样如果有人向按钮发送垃圾邮件,您就不会收到十亿个服务器请求.

有关灵感,请参阅:
介绍Combine(WWDC 会议)
结合实践(WWDC 会议)
使用组合(UIKit 示例,但展示了如何节流网络请求)

主视图

struct MainView: View {@EnvironmentObject var 模型:LoginStateModelvar主体:一些视图{VStack(间距:15){ZStack {Text("你好 (model.username)!").font(.title).inExpandingRectangle(Color.blue.opacity(0.6)).frame(高度:60)堆栈{垫片()按钮(动作:{//当我们注销时,动画设置loggedIn为false的结果//(即动画显示登录视图)withAnimation(.default) {self.model.loggedIn = false}}, 标签: {文本(退出").inFittedRectangle(Color.green.opacity(0.6))}).buttonStyle(PlainButtonStyle()).填充()}}文本(内容").inExpandingRectangle(.gray)}.填充()}}

一些方便的扩展

扩展视图{func inExpandingRectangle(_ color: Color) ->一些视图{ZStack {圆角矩形(角半径:15).填色)自己}}func inFittedRectangle(_ color: Color) ->一些视图{自己.padding(5).background(RoundedRectangle(cornerRadius: 15).填色))}}

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!

解决方案

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

这篇关于在没有导航按钮的情况下从 API 成功身份验证后,转到 SwftUI 中的新视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆