본문 바로가기

프로젝트/Twitter Clone App (w∕Firebase)

09. 기능 구현하기 #1

기능 구현하기 #1
Firebase Import & Login Function


Firebase Import

 

02. Firebase 연결하기

Firebase 프로젝트 생성 Firebase에 회원가입을 하고, 'Get started'를 선택한다. 프로젝트를 생성하고, 이름을 지정한 뒤 지금은 필요 없는 Google Analystic은 비활성화한다. 해당 기능은 Google의 분석 툴로,

chillog.page

기본적인 방법은 위와 같다.
다만 사용하려는 모듈은 조금 다른데 아래와 같다.

회원가입과 로그인 기능을 위한 'FirebaseAuth', DB 접근을 위한 'FirebaseFirestore'와 'FirebaseFirestoreSwift',
이미지 등의 큰 데이터를 저장하고 불러 올 'FirebaseStorage' 총 네 개다.

Firebase Import
| 프로젝트에 Firebase 연결

@main
struct twitterClone_SwiftUIApp: App {
    @StateObject var viewModel = AuthViewModel()

    init() {
        FirebaseApp.configure()
    }

    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
            }
            .environmentObject(viewModel)
        }
    }
}

이전과 마찬가지로 main에 FirebaseApp을 초기화해 주기만 하면 동작한다.

 

Login Function
| Firebase Auth 사용하기

@main
struct twitterClone_SwiftUIApp: App {
    @StateObject var viewModel = AuthViewModel()

    init() {
        FirebaseApp.configure()
    }

    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
            }
            .environmentObject(viewModel)
        }
    }
}

main에 StateObject로 AuthViewModel을 초기화하고,
이것을 ContentView의 environmentObject로 지정해 앱이 실행되면 모든 View가 접근해 사용할 수 있도록 한다.

extension ContentView {
    var mainInterfaceView: some View {
        ZStack(alignment: .topLeading) {
            MainTabView()
                .toolbar(showMenu ? .hidden : .visible)

            if showMenu {
                ZStack {
                    withAnimation(.easeInOut) {
                        Color(.black)
                            .opacity(showMenu ? 0.25 : 0.0)
                    }
                }.onTapGesture {
                    withAnimation(.easeInOut) {
                        showMenu = false
                    }
                }
                .ignoresSafeArea()
            }

            SideMenuView()
                .frame(width: 300)
                .offset(x: showMenu ? 0 : -300, y: 0)
        }
        .navigationTitle("Home")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar{
            ToolbarItem(placement: .navigationBarLeading) {
                Button {
                    withAnimation(.easeInOut) {
                        showMenu.toggle()
                    }
                } label: {
                    Circle()
                        .frame(width: 32, height: 32)
                }
            }
        }
        .onAppear {
            showMenu = false
        }
    }
}

기존에 작성한 ContentView의 모든 코드를 extension으로 옮긴다.

struct ContentView: View {
    @State private var showMenu = false
    @EnvironmentObject var viewModel: AuthViewModel

    var body: some View {
        Group {
            //need login
            if viewModel.userSession == nil {
                LoginView()
            } else {
                //login successfuly
                mainInterfaceView
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

위와 같은 방식으로 AuthViewModel의 userSession을 확인해
 Login 상태에 맞춰 해당 View를 표시하거나 로그인과 회원가입을 위한 AuthView를 표시한다.

Login Function
| Firebase Auth 사용하기

class AuthViewModel: ObservableObject {
    @Published var userSession: FirebaseAuth.User?

    init() {
        self.userSession = Auth.auth().currentUser

        print("debug: user session is \(self.userSession)")
    }

    func login(withEmail email: String, password: String) {
        print("debug: login with email \(email)")
    }

    func register(withEmail email: String, password: String, fullname: String, username: String) {
        print("debug: register with email \(email)")
    }
}

MVVM 구조를 지키기 위해 해당 과정에서 사용할 ViewModel 파일을 하나 생성한다.
안에는 현재 로그인된 사용자를 표시할 수 있도록 생성자에서 Auth의 currentUser 속성을 출력하고,
login과 회원가입에 필요한 파라미터를 전달받는 메서드를 선언한다.

login

  • email
  • password

register

  • email
  • password
  • fullname
  • username

Login Function
| 메서드 호출 위치 지정하기

struct LoginView: View {
    @State private var email = ""
    @State private var password = ""
    @EnvironmentObject var viewModel: AuthViewModel
struct RegistrationView: View {
    @Environment(\.dismiss) var dismiss

    @EnvironmentObject var viewModel: AuthViewModel

    @State private var email = ""
    @State private var password = ""
    @State private var userName = ""
    @State private var fullName = ""

해당 메서드들은 각각 LoginView과 RegistrationView에서 각각 호출된다.
따라서 해당 메서드를 호출할 수 있도록 EnvironmenObject 변수로 처음 지정한 AuthViewModel을 연결한다.

LoginView

Button {
    viewModel.login(withEmail: email, password: password)
} label: {
    Text("Sign In")
        .font(.headline)
        .foregroundColor(.white)
        .frame(width: 340, height: 50)
        .background(Color(.systemBlue))
        .clipShape(Capsule())
        .padding()
}
.shadow(color: .gray.opacity(0.5), radius: 10)

RegistrationView

Button {
    viewModel.register(withEmail: email, password: password, fullname: fullName, username: userName)
} label: {
    Text("Sign Up")
        .font(.headline)
        .foregroundColor(.white)
        .frame(width: 340, height: 50)
        .background(Color(.systemBlue))
        .clipShape(Capsule())
        .padding()
}

이제부터는 각각의 버튼을 누를 때마다 해당 메서드가 호출돼 Console에 출력되는 메시지를 확인할 수 있다.

Login Function
| Login 메서드 구현

func login(withEmail email: String, password: String) {
    Auth.auth().signIn(withEmail: email, password: password) { result, error in
        if let error = error {
            print("debug: failed to signin \(error.localizedDescription)")
            return
        }

        guard let user = result?.user else {
            return
        }
        self.userSession = user
        print("debug: did log user in")
    }
}

Firebase의 Login은 Auth의 auth의 signIn 메서드를 사용한다.
로그인에 사용할 email, password를 전달하면 완료다.
error 처리를 위해 error를 바인딩하고 guard문을 사용해 결과를 필터링하며,
userSession에 결과를 생성자에서 초기화 한 인스턴스에 현재 로그인된 계정의 정보를 저장한다.

Login Function
| register 메서드 구현

func register(withEmail email: String, password: String, fullname: String, username: String) {
    Auth.auth().createUser(withEmail: email, password: password) { result, error in
        if let error = error {
            print("debug: failed to register \(error.localizedDescription)")
            return
        }

        guard let user = result?.user else {
            return
        }
        self.userSession = user

        print("debug: registration successful")
        print("debug: user is \(self.userSession)")

        let data = ["email": email,
                    "username": username.lowercased(),
                    "fullname": fullname,
                    "uid": user.uid]

        Firestore.firestore().collection("users").document(user.uid).setData(data) { _ in
            print("debug: did upload user data")
        }
    }
}

로그인할 계정이 없다면 LoginView의 아래에 Sign Up 버튼을 눌러 RegistrationView를 표시해 회원가입을 진행할 수 있다.
Firebase의 회원가입은 createUser를 사용하며 email, password, 사용자 이름과 계정 이름을 전달하면 되도록 구현했다.
guard문과 error 바인딩으로 오류를 처리하며,
결과를 전달받아 userSession에 저장한다.
password를 제외한 email, username, fullname과 이후에 사용하기 위해 usersession의 uid까지 firestore에 저장한다.
'users' collcetion에 uid를 데이터의 제목으로 삼아 저장되며, 존재하지 않는 경우 새로 생성하도록 되어있다.

Login Function
| signOut 메서드 구현

func signOut() {
    userSession = nil
    try? Auth.auth().signOut()
}

로그인이 된 상태면 userSession이 항상 존재하고, LoginView와 RegistrationView를 볼 수 없다.
signOut메서드는 userSession을 nil로 변경하고, FirebaseAuth의 signOut 메서드를 호출해 토큰을 삭제한다.

signOut 메서드와 logIn 메서드가 정상적으로 동작하는 걸 확인할 수 있다.

'프로젝트 > Twitter Clone App (w∕Firebase)' 카테고리의 다른 글

11. 기능 구현하기 #3  (0) 2022.12.22
10. 기능 구현하기 #2  (0) 2022.12.20
08. 기본 UI 구현하기 #8  (0) 2022.12.02
07. 기본 UI 구성하기 #7  (0) 2022.12.02
06. 기본 UI 구성하기 #6  (0) 2022.12.01