ViewController는 State의 복원과 보존에서 중요합니다.

 

상태 보존

앱이 suspend되기 전에 정보를 저장한 후에, 이후에 실행시 앱을 이전 상태로 되돌린다.

 

방법 2가지

  1. iOS 13 이상에서는 NSUserActivity를 사용해서 각 Scene의 상태를 저장
  2. iOS 12 이하에서는 ViewController의 Configuration을 저장

예제) https://developer.apple.com/documentation/uikit/uiviewcontroller/restoring_your_app_s_state

 

Apple Developer Documentation

 

developer.apple.com

 

  • 복원하길 원하는 ViewController에게 restore identifier를 할당
  • iOS에게 앱이 시작되었을 때, 새로운 ViewController 객체의 위치를 어떻게 지정할지 알려준다.
  • VC가 원래 스토리보드 파일에서 로드된 경우, UIKit은 저장된 스토리 보드 정보를 사용해 찾아 만든다.

~ iOS 12

Tagging ViewControllers for Preservation

UIKit은 보존하기 위해 지정한 VC만 보존한다. 각 VC는 restorationIdentifier 속성을 갖고 있고 기본적으로 값은 nil이다.

이 속성을 문자열로 설정하면 UIKit에서 VC와 해당 View를 preserve한다.

복원 식별자는 programitically / storyboard 모두 할당 가능하다.

 

복원 식별자를 할당할 때, VC 계층에 있는 부모 VC 또한 복원 식별자를 갖고 있어야 한다.

복원 처리하는 동안 UIKit은 window의 root VC에서 시작하여 VC 계층을 탐색한다.

계층의 VC가 복원 식별자를 갖지 않는다면, VC와 해당 VC의 자식 VC는 무시된다.

-> A 라는 VC를 복원하고 싶다면, 해당 VC의 계층적으로 연결된 부모 VC들 또한 복원식별자가 필요하다?

 

Choosing Effective Restoration Identifiers

UIKit은 복원 식별자 문자열을 사용하여 나중에 VC를 다시 작성하므로 코드에서 쉽게 식별할 수 있는 문자열을 선택하세요.

UIKit에서 VC 중 하나를 자동으로 만들 수 없는 경우, VC 및 모든 상위 VC의 복원 식별자를 제공하여 VC를 만들것을 요청합니다.

이 식별자 체인은 각 VC를 위한 복원 경로를 대표하고, 요청된 VC가 어떻게 결정되어지는 지에 대한 방법입니다.

복원 경로는 root VC에서 시작하여 요청된 모든 VC를 포함합니다.

 

복원 식별자는 종종 VC의 클래스 이름으로 사용한다 -> (나 역시도 이게 편하다...)

 

모든 VC의 복원 경로는 고유해야 한다. Container VC에 두개의 자식이 있는 경우 Container는 각 자식에게 고유한 복원 식별자를 할당해야 한다.

UIKit의 일부 컨테이너는 자동으로 그들의 자식 VC들의 명확한 차이를 보여주고, 각 자식에 대해 동일한 복원 식별자를 사용할 수 있도록 한다.

ex) UINavigationController 는 탐색 스택의 위치를 기반으로 각 자식에 정보를 추가합니다.

 

Excluding Groups of View Controllers

복원 처리에서 VC의 전체 그룹을 제외하려면 상위 VC의 복원 식별자를 nil로 설정하세요.

 

*이 달린 VC는 복원 식별자를 갖고 있다.

빨간색의 VC는 preserve가 되지 않는다.

좌측의 UINavigationController가 복원 식별자를 갖고 있지 않기 때문에, 아래의 VC들이 복원 식별자를 갖고 있어도 preserve되지 않음

 

 

Preserving a View Controller's Views

뷰들은 뷰와 관계된 추가적인 상태 정보를 가집니다.

하지만 부모 VC는 추가 상태 정보가 없습니다.

ex) ScrollView는 보존하길 원하는 Scroll Offset을 가집니다. VC가 ScrollView의 컨텐츠를 제공하는 역할을 하는 동안 내부 View를 보존해야 한다.

 

View의 상태를 저장하기 위해 다음의 것들을 해야 합니다.

  • View의 유효한 문자열을 View의 restorationIdentifier 속성에 할당해야 합니다.
  • VC에서 사용하는 View 또한 유효한 복원 식별자를 가집니다.
  • TableView와 CollectionView는 UIDataSourceModelAssociation Protocol을 채택하는 DataSource를 할당합니다.

복원 식별자를 View에 할당하여 UIKit에게 보존 아카이브로 View의 상태를 기록해야 한다.

VC가 이후에 복원될 때, UIKit 또한 복원 식별자를 가진 View를 복원한다.

 

 

Restoring View Controllers at Launch Time

앱 실행시, UIKit은 앱을 이전 상태로 복원을 시도합니다.

이때, UIKit은 앱에서 유저 인터페이스를 보존한 VC 객체를 생성하도록 앱에게 요청합니다.

UIKit은 VC를 찾을 때, 다음 순서로 검색합니다.

  • VC가 복원 class를 가졌다면, UIKit은 VC를 제공하기 위해 class에게 요청합니다.
  • VC가 복원 class를 가지지 않았다면, UIKit AppDelegate에게 VC를 제공하라고 요청합니다.
  • VC가 올바른 복원 경로가 이미 존재한다면, UIKit은 이 객체를 사용합니다.
    • 앱을 실행할 때, VC를 생성(code/storyboard)하고 이 VC들은 복원 식별자를 갖고 있고 UIKit은 이 복원 경로를 기반으로 이들을 찾습니다.
  • VC가 storyboard에서 로드되었다면, UIKit은 이 VC를 생성하고 위치를 찾기 위해 저장된 storyboard 정보를 사용한다.
    • UIKit은 복원 아카이브 내부에 VC의 storyboard에 대한 정보를 저장합니다.
    • 복원시 UIKit은 같은 storyboard 파일을 찾기 위해 이 정보를 사용하고 이것에 일치하는 VC를 인스턴스화 합니다.

복원 class를 VC에 할당하면 UIKit에서 해당 VC를 암시적으로 검색할 수 없게 합니다.

복원 class를 사용하면 실제로 VC를 만들지 여부를 제어할 수 있습니다.

ex) viewControllerWithResotrationIdentifierPath:coder:) 메소드는 VC를 다시 만들지 않아야 한다고 결정하면 nil을 반환 가능

-> 상황에 따라 복원 class가 있더라도, 복원하지 않고 싶다면 nil을 return

 

복원 class를 사용할 때, viewControllerWithResotrationIdentifierPath:coder:) 메소드는 class의 새로운 인스턴스를 생성해야 하고 최소한의 초기화를 수행하고 결과 객체를 반환해야 합니다.

아래의 코드는 storyboard에서 VC를 로드하는 방법이다.

static func viewController(withRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
    var vc: MyViewController?
    let sb = coder.decodeObject(forKey: UIApplication.stateRestorationViewControllerStoryboardKey) as? UIStoryboard
    if sb != nil {
        vc = sb?.instantiateViewController(withIdentifier: "MyViewController") as? PushViewController
        vc?.restorationIdentifier = identifierComponents.last
        vc?.restorationClass = MyViewController.self
    }
    return vc
}

복원 식별자와 복원 class를 재할당하는 것은 VC가 수동으로 재 생성되었을 때 채택하지 위한 좋은 습관이다.

복원 식별자를 복원하는 간단한 방법은 identifierComponents 배열의 마지막 아이템을 가져오는 것이다.

 

앱을 실행할 때, 앱의 Main Storyboard에서 생성했던 객체의 경우, 각 객체의 새로운 인스턴스를 생성하지 않습니다.

UIKit은 이 객체를 암묵적으로 찾거나,  AppDelegate의 application:viewcontrollerWithRestorationIdentifierPath:coder: 를 통해 찾는다.

 

 

Encoding and Decoding View Controller's State

보존이 예정된 각 객체의 경우 UIKit은 상태 저장할 기회를 제공하기 위해

encodeRestorableStateWithcoder: 를 호출한다.

보존 처리 동안 UIKit은 decodeRestorableStateWithCoder: 메소드를 호출하여 해당 상태를 해독하고 객체에 적용한다.

메소드들의 구현은 선택적이다.

저장되는 정보들은 다음의 타입을 따라야 한다.

  • 화면에 표시되어지는 데이터 참조(데이터 그 자체가 아니다?)
  • Container VC의 경우 자식 VC를 참조한다.
  • 현재 선택에 대한 정보
  • 유저가 구성할 수 있는 VC의 경우 그 View의 현재 구성에 대한 정보

인코딩과 디코딩 메소드에서 객체와 coder가 지원하는 데이터 타입을 인코딩할 수 있다.

View와 VC들을 제외한 모든 객체의 경우 해당 객체는 반드시 NSCoding Protocol를 채택해야 하고, 상태 기록을 위해 protocol의 메소드 사용

View와 VC의 경우 상태를 저장하기 위해 NSCoding Protocol 사용 X

 

 


iOS 13 ~

SceneDelegate를 사용할 것이다.

 

우선은 Info.plist에 속성을 추가해준다.

 

Enable State Preservation and Restoration for Your App

해당 Application의 Scene에서 NSUserActivity를 사용하여 State를 복구할거야! 라고 명시하는 메소드입니다.

 

Preserve and Restore the App State with an Activity Object

NSUserActivity객체는 현재 시점의 앱 상태를 캡처한다고 한다.

ex) 앱을 사용하다가 다른 앱으로의 전환 및 홈화면으로의 이동 등의 상황에서 앱이 Active상태를 나갈 때 상태를 저장하면 된다.

 

- detailUserActivity는 property로 직접 생성해야 한다.

 

우선, 메소드를 작성하게 되면, Scene이 ResignActive 될 때, 코드에 작성한 ViewController의 detailUserActivity라는 NSUserActivity라는 객체가 scene의 userActivity로 저장된다.

 

MainViewController에 detailUserActivity 만들기

하단에 있는 메소드인 addUserInfoEntries에서 딕셔너리를 저장하고 있다.

해당 딕셔너리에 Restore하고 싶은 값들은 Key와 함께 저장하면 된다.

 

Scene Configure

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions){
	if let userActivity = connectionOptions.userAcitvities.first ?? session.stateRestorationActivity{
    	if !self.configure(window: self.window, with userActivity){
        	print("Failed to restore MainViewController")
        }
    }
}

func configure(window: UIWindow?, with activity: NSUserActivity) -> Bool{
	let mainVC = MainViewController()
    if let navigation = window?.rootViewController as? UINavigationController{
    	navigation.pushViewController(mainVC, animated: false)
        mainVC.restoreUserActivityState()
        mainVC.label = activity.userInfo?["key"] as? String
        ......
    }
}

항상 원하지 않는 다면

configure 메소드를 호출하기 전에 조건을 걸거나 하면 원하는 상황일 때만, Restoration이 가능하다

 

 

근데... 정확하게 어떤식으로 복구되는 지를 모르겠다...

  1. App이 Active 상태를 잃어버릴 때, detailUserActivity가 scene의 activity가 된다.
  2. App을 재 실행하게 되면 scene(_:willConnectTo: ...)가 호출되면서 scene에 activity가 존재하면 configure()메소드를 호출한다.
  3. configure() 메소드에서 해당 VC를 찾고 매개변수로 넘어온 activity를 사용해서 UI를 복구한다.

이 순서로 동작하는 것 같은데... 그렇다면 LocalDB나 UserDefaults를 사용하는 것이랑 다른 것이 뭐지...?

좀 더 자세히 알아봐야겠다...

'iOS' 카테고리의 다른 글

[iOS] HitTest  (0) 2021.05.21
[iOS] Responder / Responder Chain  (0) 2021.05.21
[iOS] App Life Cycle  (0) 2021.05.18
[iOS] TabBar Slide Down/Up Animation  (0) 2021.03.25
[iOS] Animation - 2 (feat. Frame-Based Layout vs AutoLayout)  (0) 2021.03.25

+ Recent posts