[UIKit] BlogClone: Firebase Setup

Junyoung Park·2022년 11월 22일
0

UIKit

목록 보기
96/142
post-thumbnail

Building Subscription Blogging App: Part 2 – Firebase Set Up (2021, Xcode 12, Swift 5) – iOS

BlogClone: Firebase Setup

구현 목표

  • 파이어베이스 사용 환경 설정

구현 태스크

  • 인증 서비스 매니저 구현
  • 유저 데이터 서비스 구현
  • 블로그 포스트 서비스 구현
  • 인증 정보를 바탕으로 윈도우 변경 로직 구현

핵심 코드

import Foundation
import FirebaseAuth
import FirebaseFirestore
import Combine
import UIKit

final class AuthManager {
    static let shared = AuthManager()
    let userSession: CurrentValueSubject<FirebaseAuth.User?, Never> = .init(nil)
    let currentUser: CurrentValueSubject<UserModel?, Never> = .init(nil)
    let profileImage: CurrentValueSubject<UIImage?, Never> = .init(nil)
    private var tempUserSession: FirebaseAuth.User?
    private var didAuthenticateUser = false
    private let service = UserService()
    private var cancellables = Set<AnyCancellable>()
    
    private init() {
        bind()
    }
    
    private func bind() {
        userSession
            .sink { [weak self] _ in
                self?.fetchUser()
            }
            .store(in: &cancellables)
        userSession.send(Auth.auth().currentUser)
    }
    
    func login(email: String, password: String) {
        Auth.auth().signIn(withEmail: email, password: password) { [weak self] result, error in
            guard
                let user = result?.user,
                error == nil else { return }
            self?.userSession.send(user)
            print("Firebase Login Did Succeed")
        }
    }
    
    func register(email: String, userName: String, password: String) {
        Auth.auth().createUser(withEmail: email, password: password) { [weak self] result, error in
            guard
                let user = result?.user,
                error == nil else { return }
            self?.tempUserSession = user
            let data = ["email": email, "userName": userName.lowercased()]
            Firestore.firestore().collection("users")
                .document(user.uid)
                .setData(data) { [weak self] error in
                    if let error = error {
                        print(error.localizedDescription)
                    } else {
                        self?.didAuthenticateUser = true
                        print("Firestore Register Did Succeed")
                    }
                }
        }
    }
    
    func uploadProfileImage(with image: UIImage) {
        guard let uid = tempUserSession?.uid else { return }
        ImageUploader.uploadImage(image: image, folder: .profileImage) { [weak self] result in
            switch result {
            case .failure(let error): print(error.localizedDescription)
            case .success(let urlString):
                let data = ["profileImageURL": urlString]
                Firestore.firestore().collection("users")
                    .document(uid)
                    .setData(data, merge: true) { [weak self] error in
                        if let error = error {
                            print(error.localizedDescription)
                        } else {
                            self?.didAuthenticateUser = false
                            self?.userSession.send(self?.tempUserSession)
                            self?.tempUserSession = nil
                        }
                    }
            }
        }
    }
    
    func signOut() {
        do {
            try Auth.auth().signOut()
            userSession.send(nil)
        } catch {
            print(error.localizedDescription)
        }
    }
    
    func fetchUser() {
        guard let uid = userSession.value?.uid else {
            currentUser.send(nil)
            return
        }
        service.fetchUser(with: uid) { [weak self] result in
            switch result {
            case .failure(let error):
                print(error.localizedDescription)
            case .success(let user):
                self?.currentUser.send(user)
                self?.downloadProfileImage(with: user.profileImageURL)
            }
        }
    }
    
    private func downloadProfileImage(with urlString: String?) {
        guard
            let urlString = urlString,
            let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard
                let data = data,
                let response = response as? HTTPURLResponse,
                response.statusCode >= 200,
                response.statusCode < 400,
                error == nil,
                let image = UIImage(data: data) else { return }
            DispatchQueue.main.async { [weak self] in
                self?.profileImage.send(image)
            }
        }
    }
}
  • 강의 영상 내용을 보다 발전, 이전의 트위터 클론 강의에서 익힌 바와 유사하게 코드를 작성
  • CurrentPublisher 타입을 구독함으로써 현재 상황을 캐치 가능
  • uid를 다큐먼트 ID로 활용 가능
  • 회원가입 이후 생성된 UID로 데이터베이스 생성, 이후 프로필 이미지를 업로드한 뒤 데이터베이스 머지
import Foundation
import FirebaseAuth
import FirebaseFirestoreSwift

struct UserModel: Identifiable, Codable {
    @DocumentID var id: String?
    let userName: String
    let email: String
    var profileImageURL: String?
    var isCurrentUser: Bool {
        return Auth.auth().currentUser?.uid == id
    }
}
  • @DocumentID라는 파이어베이스가 기본적으로 제공하는 프로퍼티 래퍼를 통해 다큐먼트 아이디로 곧바로 세팅 가능
struct UserService {
    func fetchUser(with uid: String, completion: @escaping(Result<UserModel, Error>) -> Void) {
        Firestore.firestore().collection("users")
            .document(uid)
            .getDocument { document, error in
                guard
                    error == nil,
                    let document = document,
                    let user = try? document.data(as: UserModel.self) else {
                    completion(.failure(DatabaseError.fetchUserDidFail))
                    return
                }
                completion(.success(user))
            }
    }
}
  • 유저 서비스를 제공하는 구조체 내 함수로 유저 관련 정보를 패치하는 서비스를 제공

구현 화면

  • 현재 파이어베이스 로그인 상황에 따라서 로그인 뷰 또는 실제 탭바 뷰를 띄우도록 구현
profile
JUST DO IT

0개의 댓글