본문 바로가기

IOS(Swift)

[IOS/Swift] 콘텐츠 검색 앱 구현 - 3. 회원가입 UI 구현

반응형

Swift UI 를 사용한 회원가입 및 로그인 화면 구현 - 백엔드는 Django RestFramework 사용

구현 예시 - 회원가입

회원 가입은 4개 항목 (닉네임, 이메일, 비밀번호(+비밀번호 확인), MBTI 항목)을 입력받는 화면을 만든다. 여기서 MBTI 항목은 HTML에서 말하는 셀렉트 박스 UI를 사용하여 MBTI를 선택하도록 하였다.

//
//  SignUpView.swift
//  mbtiPlayground
//
//  Created by 박종후 on 2022/03/16.
//

import SwiftUI

struct SignUpView: View {
    @Environment(\\.presentationMode) var presentationMode: Binding<PresentationMode>
    @State private var showingAlert = false
    @State var uiTabarController: UITabBarController?
    
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var rePassword: String = ""
    @State private var username: String = ""
    @State private var mbti = 0
    let mbtiPicker = ["ISTJ", "ISFJ", "INFJ", "INTJ", "ISTP", "ISFP", "INFP","INTP", "ESTP", "ESFP", "ENFP", "ENTP", "ESTJ", "ESFJ", "ENFJ", "ENTJ"]
    
    var btnBack : some View { Button(action: {
        self.showingAlert = true
    }){
        Text("뒤로가기")
    }.alert(isPresented: $showingAlert){
        Alert(title: Text("회원 가입 취소"), message: Text("회원가입을 취소하시겠습니까?"),
              primaryButton: .destructive(Text("취소하기"), action: {
            presentationMode.wrappedValue.dismiss()
        })
              , secondaryButton: .cancel(Text("이어하기")))
    }
        
    }
    
    var body: some View {
        VStack{
            Text("Sign Up")
                .font(.title)
                .fontWeight(.medium)
                .padding()
                .foregroundColor(.black)
                .frame(width: 300)
            
            HStack{
                Image(systemName: "tag")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30, height: 30)
                    .padding(.bottom)
                
                TextField("닉네임을 입력하세요", text: $username)
                    .frame(width: 300, height: 10)
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)
                    
            }
            HStack{
                Image(systemName: "person.fill")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30, height: 30)
                    .padding(.bottom)
                
                TextField("이메일을 입력하세요.", text: $email)
                    .frame(width: 300, height: 10)
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)
                    
            }
            HStack{
                Image(systemName: "lock")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30, height: 30)
                    .padding(.bottom)
                    
                SecureField("비밀번호를 입력하세요", text: $password)
                    .frame(width: 300, height: 10)
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)
            }
            HStack{
                Image(systemName: "lock.rotation")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30, height: 30)
                    .padding(.bottom)
                    
                SecureField("비밀번호를 다시 입력하세요", text: $rePassword)
                    .frame(width: 300, height: 10)
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)
            }
            HStack{
                Text("MBTI 유형을 선택하세요: ")
                    .font(.headline)
                    .fontWeight(.medium)
                    .padding()
                Picker("MBTI 유형을 선택하세요", selection: $mbti){
                    ForEach(0 ..< mbtiPicker.count){
                        Text(self.mbtiPicker[$0])
                    }
                }
            }
            
            
            Button(action: {
                print(self.email + self.password + self.rePassword + String(self.mbtiPicker[self.mbti])+self.username)
                
                sendPostRequest("<http://localhost:8000/auth/signup>", parameters:
                                ["email": self.email, "username": self.username, "password": self.password, "mbti": self.mbtiPicker[self.mbti]]
                                 ){
                    responseObject, error in guard let _ = responseObject, error == nil else {
                        print(error ?? "Unknown error")
                        return
                    }
                }
                self.presentationMode.wrappedValue.dismiss()
            }){
                Text("회원가입")
                    .frame(width: 80, height: 10)
                    .font(.headline)
                    .foregroundColor(.white)
                    .padding()
                    .background(Color(.systemBlue))
                    .cornerRadius(10)
                    
            }
            .padding()
            .onSubmit {
                
            }
        }
        .padding(.all, 30)
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: btnBack)
    }
}

struct SignUpView_Previews: PreviewProvider {
    static var previews: some View {
        SignUpView()
    }
}

회원 가입 시 Post 요청으로 보내는 함수는 아래와 같다 sendPostRequest

import Foundation

func sendPostRequest(_ url: String, parameters: [String: String], completion: @escaping ([String: Any]?, Error?) -> Void) {
    let targetUrl = URL(string: url)
    let paramData = try? JSONSerialization.data(withJSONObject: parameters)
    
    var request = URLRequest(url: targetUrl!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = paramData
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard
            let data = data,                              // is there data
            let response = response as? HTTPURLResponse,  // is there HTTP response
            200 ..< 300 ~= response.statusCode,           // is statusCode 2XX
            error == nil                                  // was there no error
        else {
            completion(nil, error)
            return
        }
        
        let responseObject = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
        completion(responseObject, nil)
    }
    task.resume()
}

그런데 위 함수를 보면 Pyhon 과는 다르게 Return 값이 없다.

이 코드에 대해 설명하자면 URLSession 안에 미리 요청 내용을 담아둔 후 호출, 실제 실행(task.resume()) 부분에서 호출하여 받은 내용을 가져온다.

그렇다보니 처음 swift 를 접한 나에게는 어떻게 API 응답 코드에 대해 처리하는지 의문이었다.

그래서 보통 사용은 아래처럼 사용하게 된다.

sendPostRequest("<http://localhost:8000/auth/signup>", parameters:
                                ["email": self.email, "username": self.username, "password": self.password, "mbti": self.mbtiPicker[self.mbti]]
                                 ){
                    responseObject, error in guard let _ = responseObject, error == nil else {
                        print(error ?? "Unknown error")
                        return
                    }
                }
                self.presentationMode.wrappedValue.dismiss()

위 코드는 UI 코드 내에서 회원가입 버튼 클릭 시 액션에서 동작하는 함수이다. Swift 에서는 함수의 return 값을 클래스의 변수로 할당하는 부분이 있는 것이 아닌, 요청 함수 내에서 self.XXX 로 클래스 변수에 접근하여 값을 변경하는게 대부분이다.

이렇다보니 Swift UI 내에서 클래스 변수를 선언하고 잘 사용하는 방법을 연구하여야 한다.

반응형