Building Subscription Blogging App: Part 2 – Firebase Set Up (2021, Xcode 12, Swift 5) – iOS
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로 활용 가능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))
}
}
}