로그인 샘플@AppStorage SwiftUI

Woozoo·2023년 1월 19일
0

개인프로젝트

목록 보기
4/12

@AppStorage

UserDefaults와 비슷한 SwiftUI의 @AppStorage를 사용해 로그인 샘플 구현

struct IntroView: View {
    
    @AppStorage("user_signIn") var userSignIn: Bool = false
    
    var body: some View {
        ZStack {
            RadialGradient(colors: [.blue, .purple], center: .topLeading, startRadius: 5, endRadius: 900).ignoresSafeArea()

                Group {
                    if userSignIn {
                        ProfileView()
                            .transition(AnyTransition.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .top)))
                    } else {
                        OnboardingView()
                            .transition(AnyTransition.asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)))
                    }
                }
                .animation(.spring())
        }        
    }
}

인트로뷰의 코드인데 트랜지션이 if else 조건 구문에서 작동이 제대로 되지 않는 현상을 발견
앞으로는 사라지게 될 표현인 .animation을 그룹으로 묶은 userSignIn에서 구현해줘야 제대로 작동한다

struct OnboardingView: View {
    @AppStorage("user_signIn") var userSignIn: Bool = false
    @AppStorage("name") var userName: String?
    @AppStorage("age") var userAge: Int?
    @AppStorage("gender") var userGender: String?
    
    @State var onboardingState: Int = 0
    let transition: AnyTransition = .asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
    
    @State var name: String = ""
    @State var age: Double = 20.0
    @State var gender: String = ""
    
    @State var alertTitle: String = ""
    @State var showAlert: Bool = false
    
    var body: some View {
        ZStack {
            VStack {
                switch onboardingState {
                    
                case 0: welcomeSection
                        .transition(transition)
                case 1: addNameSection
                        .transition(transition)
                case 2: addAgeSection
                        .transition(transition)
                case 3: addGenderSection
                        .transition(transition)
                    
                default: Text("Section 구성하자")
                }
                Spacer()
                nextButton
            }
            .alert(isPresented: $showAlert) {
                return Alert(title: Text(alertTitle))
            }
        }
    }
}

extension OnboardingView {
    private var nextButton: some View {
        Text(onboardingState == 0 ? "로그인하기" : "다음으로" )
            .font(.largeTitle)
            .fontWeight(.bold)
            .foregroundColor(.purple)
            .frame(height: 55)
            .frame(maxWidth: .infinity)
            .background(Color.white).cornerRadius(10)
            .onTapGesture {
                nextButtonTapped()
            }
            .padding(20)
    }
    
    private var welcomeSection: some View {
        VStack(spacing: 40) {
            Spacer()
            Image(systemName: "rectangle.portrait.and.arrow.forward")
                .resizable()
                .scaledToFit()
                .frame(width: 250, height: 250)
            Text("로그인 해볼까요?")
                .font(.largeTitle)
                .overlay(
                    Capsule(style: .continuous)
                        .frame(height: 3)
                        .offset(y: 10), alignment: .bottom
                )
            Text("로그인을 해서 AppStorage를 한번 사용해 볼 거에요. 그리고 사용자의 로그인 정보를 저장하는 방법을 알아보고 UserDefaults랑 어떤 차이점이 있는지 살펴봅시다.")
                .font(.headline)
                .fontWeight(.semibold)
                .multilineTextAlignment(.leading)
                
            Spacer()
            Spacer()
        }
        .padding(.horizontal, 40)
        .foregroundColor(.white)
    }
    
    private var addNameSection: some View {
        VStack(spacing: 40) {
            Spacer()
            
            Text("이름이 뭐에요?")
                .font(.largeTitle)
                .fontWeight(.bold)
                .overlay(
                    Capsule(style: .continuous)
                        .frame(height: 3)
                        .offset(y: 10), alignment: .bottom
                )
            TextField("이름을 입력하세요", text: $name)
                .foregroundColor(.black)
                .padding()
                .background(.white).cornerRadius(12)
                
                
            Spacer()
            Spacer()
        }
        .padding(.horizontal, 40)
        .foregroundColor(.white)
    }
    
    private var addAgeSection: some View {
        VStack(spacing: 40) {
            Spacer()
            
            Text("몇 살이에요?")
                .font(.largeTitle)
                .fontWeight(.bold)
                .overlay(
                    Capsule(style: .continuous)
                        .frame(height: 3)
                        .offset(y: 10), alignment: .bottom
                )
            Slider(value: $age, in: 1...50, step: 1.0)
                .tint(.white)
            
            
            let ageToString = String(format: "%.0f", age)
            Text(ageToString)
                .font(.system(size: 100))
                
                
            Spacer()
            Spacer()
        }
        .padding(.horizontal, 40)
        .foregroundColor(.white)
    }
    
    private var addGenderSection: some View {
        VStack(spacing: 40) {
            Spacer()
            
            Text("성별은 어떻게 되나요?")
                .font(.largeTitle)
                .fontWeight(.bold)
                .overlay(
                    Capsule(style: .continuous)
                        .frame(height: 3)
                        .offset(y: 10), alignment: .bottom
                )
            Picker("성별", selection: $gender) {
                Text("남자").tag("남자")
                Text("여자").tag("여자")
                Text("Non-Binary").tag("Non-Binary")
            }.pickerStyle(.menu)
            .tint(.white)
                
            Spacer()
            Spacer()
        }
        .padding(.horizontal, 40)
        .foregroundColor(.white)
    }
    
    func nextButtonTapped() {
        
        switch onboardingState {
        case 1:
            guard name.count >= 3 else {
                getAlert(title: "3글자 이상의 이름을 입력해주세요🤣")
                return
            }
        case 3:
            guard gender.count > 0 else {
                getAlert(title: "성별을 선택해주세요🥹")
                return
            }
        default: break
        }
        
        if onboardingState == 3 {
            userSignIn = true
            userName = name
            userAge = Int(age)
            userGender = gender
        } else {
            withAnimation(.spring()) {
                onboardingState += 1
            }
        }
    }
    
    func getAlert(title: String) {
        alertTitle = title
        showAlert.toggle()
    }
    
}

Onboarding View는 enum으로 만들지 않았고,
onboardingState를 나타내는 인트 값으로 표현해서 버튼을 누르면 인트를 증가시켜주고,
state 값에따라서 화면을 보여주는 걸로 구현했다.

struct ProfileView: View {
    @AppStorage("user_signIn") var userSignIn: Bool = false
    @AppStorage("name") var userName: String?
    @AppStorage("age") var userAge: Int?
    @AppStorage("gender") var userGender: String?
    
    var body: some View {
        VStack(spacing: 10) {
            Image(systemName: "person.circle.fill")
                .resizable()
                .scaledToFit()
                .frame(width: 150, height: 150)
            
            Text("유저의 이름은 \(userName ?? "노유저")")
            Text("유저의 나이는 \(userAge ?? 0)")
            Text("유저의 성별은 \(userGender ?? "노유저")")
            
            Text("로그아웃")
                .foregroundColor(.white)
                .padding(20)
                .background(.blue).cornerRadius(.infinity)
                .onTapGesture {
                    signOut()
                }
        }
        .font(.system(size: 30))
        .foregroundColor(.purple)
        .padding()
        .padding(.vertical, 40)
        .background(.white)
        .cornerRadius(10)
        .shadow(radius: 20)
    }
}

extension ProfileView {
    
    func signOut() {
        userName = nil
        userAge = nil
        userGender = nil
        userSignIn = false
    }
}

프로필 뷰에선 이전에 @AppStorage에 저장된 값들을 이용하고, signOut을 누르게 되면 초기화 해주는 방법을 사용했다.
@AppStorage로 선언한 user_signIn이 현재의 뷰에선 true값으로 초기화 해줘야 될거라 생각했는데 true로 넣을 경우엔 저장된 값을 불러오는 과정에서 에러가 생기는 것 같다.

🤔@AppStorage가 어떤 cycle로 값을 가져오는 건지 알아둬야겠다

profile
우주형

0개의 댓글