🔷 SceneDelegate.swift
🏆 전체 흐름 정리
1️⃣ 앱이 실행되면 scene(_:willConnectTo:options:)이 호출됨.
2️⃣ setupWindow()를 통해 UIWindow를 초기화하고, 화면을 표시할 준비를 함.
3️⃣ checkAuthentication()에서 현재 로그인 상태를 확인함.
- 로그인이 안 되어 있다면 gotoController(with: OnboardingViewController()) 실행
- 로그인이 되어 있다면 gotoController(with: MainTabBarController()) 실행
4️⃣ gotoController(with:)가 rootViewController를 변경하고 애니메이션을 적용하여 화면을 전환함.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
self.setupWindow(with: scene)
checkAuthentication()
}
private func setupWindow(with scene: UIScene) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
self.window = window
self.window?.makeKeyAndVisible()
}
// ✅ Auth.auth().currentUser를 확인하여 로그인 상태를 검사
public func checkAuthentication() {
if Auth.auth().currentUser == nil {
self.gotoController(with: UINavigationController(rootViewController: OnboardingViewController()))
} else {
// ✅ 기존에 MainTabBarController가 있으면 새로 만들지 않음
if !(window?.rootViewController is MainTabBarController) {
gotoController(with: MainTabBarController())
}
}
}
private func gotoController(with viewController: UIViewController) {
DispatchQueue.main.async { [weak self] in
UIView.animate(withDuration: 0.3) {
self?.window?.layer.opacity = 0
} completion: { [weak self] _ in
let vc = viewController
vc.modalPresentationStyle = .fullScreen
self?.window?.rootViewController = vc
UIView.animate(withDuration: 0.3) {
self?.window?.layer.opacity = 1
}
}
}
}
}
🔷 AuthManager.swift
- Firebase Authentication을 사용하여 이메일 & 비밀번호 기반 회원가입을 수행
- Combine의 Future를 사용하여 비동기 작업을 AnyPublisher<User, Error>로 변환
- 성공하면 Firebase User 객체를 반환하고, 실패하면 Error를 반환
import Foundation
import Firebase
import FirebaseAuth
import FirebaseAuthCombineSwift
import Combine
class AuthManager {
// MARK: - Variable
static let shared = AuthManager()
private init() { }
// MARK: - Function
func registerUser(email: String, password: String) -> AnyPublisher<User, Error> {
return Future { promise in
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
if let error = error {
promise(.failure(error)) // ❌ 실패 시 에러 반환
} else if let user = authResult?.user {
promise(.success(user)) // ✅ 성공 시 User 반환
}
}
}
.eraseToAnyPublisher()
}
}
🔷 AuthenticationViewModel.swift
- Combine + MVVM 패턴을 적용하여 회원가입 로직을 구현
- @Published 프로퍼티 (뷰와 데이터 바인딩)
- validateAuthenticationForm() (이메일 & 비밀번호 유효성 검사)
- createUser() (회원가입 요청)
import Foundation
import FirebaseAuth
import Combine
final class AuthenticationViewModel: ObservableObject {
@Published var email: String?
@Published var password: String?
@Published var isAuthenticationFormValid: Bool = false
@Published var user: User?
@Published var error: String?
private var cancelable: Set<AnyCancellable> = []
// MARK: - Function
/// 이메일, 비밀번호 입력 유효성 검사 메서드
func validateAuthenticationForm() {
guard let email = email,
let password = password else {
isAuthenticationFormValid = false
return }
isAuthenticationFormValid = isValidEmail(email) && password.count >= 8
}
/// email 양식 검토 메서드
func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailPred.evaluate(with: email)
}
/// 이메일, 비밀번호로 회원가입하는 메서드
func createUser() {
guard let email = email,
let password = password else { return }
AuthManager.shared.registerUser(email: email, password: password)
.sink { [weak self] completion in
switch completion {
case .failure(let error):
self?.error = error.localizedDescription
case .finished:
print("회원가입 성공")
}
} receiveValue: { [weak self] user in
self?.user = user
}
.store(in: &cancelable)
}
}
🔷 RegisterViewController.swift
// MARK: - Function
private func bindViews() {
emailTextField.addTarget(self, action: #selector(didChangedEmailField), for: .editingChanged)
passwordTextField.addTarget(self, action: #selector(didChangedPassword), for: .editingChanged)
registerButton.addTarget(self, action: #selector(didTapRegister), for: .touchUpInside)
viewModel.$isAuthenticationFormValid
.sink { [weak self] validationState in
self?.registerButton.isEnabled = validationState
}
.store(in: &cancelable)
// 회원가입 성공에 따른 화면 이동
viewModel.$user
.sink { [weak self] user in
guard user != nil else { return }
// 현재 RegisterViewController가 띄워진 모든 모달을 닫음 (Onboarding 포함)
self?.view.window?.rootViewController?.dismiss(animated: true, completion: {
// ✅ 모든 화면을 닫은 후, rootViewController를 변경 (MainTabBarController로)
if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate {
sceneDelegate.window?.rootViewController = MainTabBarController()
}
})
}
.store(in: &cancelable)
}
/// 빈 곳을 누르면 키보드 내려가게 하는 메서드
private func resigneKeyboard() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tapGesture)
}
// MARK: - Action
@objc private func didChangedEmailField() {
viewModel.email = emailTextField.text
viewModel.validateAuthenticationForm()
}
@objc private func didChangedPassword() {
viewModel.password = passwordTextField.text
viewModel.validateAuthenticationForm()
}
'Project > MovieClip' 카테고리의 다른 글
💾 Firestore 에 유저 정보 저장 및 이미지 업로드 (0) | 2025.02.28 |
---|---|
👤 Firebase에 로그인, 회원정보를 FireStore 저장, 회원정보 불러오기 (0) | 2025.02.27 |
🌟 회원 가입 화면 뷰 관리? 순서?는 어떻게 해야하나? (0) | 2025.02.26 |
❌ 문제 해결 - 검색 결과의 상세페이지 이동이 안됨.. (0) | 2025.02.24 |
🔨 검색결과 내의 영화, 티비의 장르 가져오는 부분 개선 (0) | 2025.02.24 |