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로 값을 가져오는 건지 알아둬야겠다