우리는 로그인 화면에서 로그인이 완료되면 Onboarding 화면으로 넘어가고, 또 onboarding 화면을 그만 보거나 다시 돌아가는 과정이 필요하다. 이 과정들을 구현하기 위해 protocol-delegate 패턴이 사용된다.
즉, protocol-delegate 패턴을 이용해서 로그인과 온보딩 과정에서 app delegate에게 신호를 보내는 것이다. 이 방법은 다른 뷰 컨트롤러에서도 적용될 수 있다. 그러나 우리는 유저가 한 번 온보딩 화면을 보면 그 다음부터는 온보딩 화면이 나타나지 않도록 해야 한다. 온보딩이 끝났는지와 끝난 시점을 감지하려면 뷰 컨트롤러들에게서 신호를 받을 수 있는 app delegate를 사용하는 것이 가장 좋은 방법일 것이다.
LoginViewController
에서 LoginViewControllerDelegate
프로토콜을 작성하고, AppDelegate
에서 loginViewController
의 delegate를 self로 지정해준다.
로그인 화면에서 온보딩 화면으로 넘어가려면 rootViewController
를 온보딩 화면으로 설정하면 된다.
extension AppDelegate: LoginViewControllerDelegate {
func didLogin() {
window?.rootViewController = onboardingContainerViewController
}
}
코드를 추가하고 앱을 실행해보면 문제없이 화면이 넘어가긴 하지만 애니메이션 없이 다른 화면이 띄워져서 딱딱하게 느껴진다.
extension AppDelegate {
func setRootViewController(_ vc: UIViewController, animated: Bool = true) {
guard animated, let window = self.window else {
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
return
}
window.rootViewController = vc
window.makeKeyAndVisible()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: nil, completion: nil)
}
}
화면 이동을 할 때 애니메이션 효과를 넣기 위해 위와 같이 새로운 메소드를 추가해준다.
🤔 makeKeyAndVisible()? : https://ios-development.tistory.com/314 참고
로그인 후 앱 내에서 로그아웃을 하면 다시 로그인 화면(초기 화면)으로 돌아가는 것을 구현하기 위해 임시로 DummyViewController
를 만들어준다. 이 뷰 컨트롤러에는 로그아웃 버튼이 있고, 온보딩 화면 다음에 보여질 예정이다. 로그아웃 역시 delegate를 만들어서 관련 메소드를 구현한다.
로그아웃을 하고 다시 로그인 화면으로 가면 로그인 성공 후의 상태(아이디, 비밀번호 텍스트필드 내 값, 버튼의 indicator)가 그대로 남아있다. 초기 모습으로 되돌리기 위해 viewDidDisappear(_:)
메소드에서 관련 작업을 해준다.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
signInButton.configuration?.showsActivityIndicator = false
}
강의에서는 indicator 숨기는 코드만 추가했는데, 텍스트필드 값을 비워주는 작업도 해야 할 것 같다.
온보딩을 한 번 한 이후로 온보딩 화면이 뜨지 않게 하려면 어떻게 해야 할까?
먼저 메모리에 온보딩 상태를 저장하는 방법이 있다. AppDelegate
에 hasOnboarded
라는 플래그 변수를 하나 선언하고 온보딩이 끝났을 때는 값을 true
로 지정한다. 로그인을 마쳤을 때는 hasOnboarded
의 상태에 따라 더미 뷰 컨트롤러 또는 온보딩 뷰 컨트롤러로 이동하도록 한다.
extension AppDelegate: LoginViewControllerDelegate {
func didLogin() {
if hasOnboarded {
setRootViewController(dummyViewController)
} else {
setRootViewController(onboardingContainerViewController)
}
}
}
extension AppDelegate: OnboardingContainerViewControllerDelegate {
func didFinishOnboarding() {
hasOnboarded = true
setRootViewController(dummyViewController)
}
}
이 방법은 앱을 종료하거나 재시작하면 변수의 state가 초기화된다는 문제가 있다.
UserDefaults(NSUserDefaults)는 key와 value의 문자열 쌍을 저장할 수 있는 iOS 기기 내의 저장소이다. 기본적으로 사용자 설정이 저장되고, 앱을 실행할 때 이를 반영한다. UserDefaults에 저장된 데이터는 기기 내에 영구적으로 저장된다.
import Foundation
public class LocalState {
private enum Keys: String {
case hasOnboarded
}
public static var hasOnboarded: Bool {
get {
return UserDefaults.standard.bool(forKey: Keys.hasOnboarded.rawValue)
}
set(newValue) {
UserDefaults.standard.set(newValue, forKey: Keys.hasOnboarded.rawValue)
UserDefaults.standard.synchronize()
}
}
}
UserDefaults에 저장할 데이터(온보딩 여부)를 관리하는 LocalState 클래스를 다음과 같이 만들어준다.
extension AppDelegate: LoginViewControllerDelegate {
func didLogin() {
if LocalState.hasOnboarded {
setRootViewController(dummyViewController)
} else {
setRootViewController(onboardingContainerViewController)
}
}
}
extension AppDelegate: OnboardingContainerViewControllerDelegate {
func didFinishOnboarding() {
LocalState.hasOnboarded = true
setRootViewController(dummyViewController)
}
}
그리고 AppDelegate의 hasOnboarded
변수를 LocalState.hasOnboarded
로 수정한다.