- Tagging ViewControllers
- UI Preservation Process
- UI Restoration Process
VC와 VC의 view 들을 보존(preservation) 하기 위해선 VC의 restorationIdentifier 를 설정해야 함
restorationIdentifier 는 스토리보드 or 코드로 구현할 수 있다.
restorationIdentifier = "ViewController"
Restoration ID 는 유니크 해야 함
따라서, 일반적으로 클래스 이름과 동일하게 설정되는 것이 권장되지만,
같은 클래스가 여러 번 사용 되는 경우 해당 VC 에서 관리하는 데이터와 관련된 문자열을 설정하면 된다.
ex) ProductListViewController 처럼 여러 번 재사용되는 VC 의 경우 FreeMovieProductListViewController, Price1000MovieProductListViewController
restoration Identifier 를 할당 할 때 주의점은, 부모 VC도 마찬가지로 restoration Identifier 를 가져야 한다는 점이다.
Preservation process(보존 과정) 동안, UIKit 은 Window(UIWindow)의 Root VC 부터 시작하여 VC 계층을 따라 이동한다. 만약 계층 내의 VC가 restoration identifier 를 가지고 있지 않은 경우 그 VC 뿐만 아니라 해당 VC의 모든 Child VC, presented VC 의 Preservation process 또한 진행되지 않는다.
func application(_ application: UIApplication, shouldSaveSecureApplicationState coder: NSCoder) -> Bool {
true
}
func application(_ application: UIApplication, willEncodeRestorableStateWith coder: NSCoder) {
}
// Tells your delegate to save any high-level state information
// at the beginning of the state preservation process.
(선택 사항)
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
coder.encode(firstTextField.text, forKey: "firstLabel")
coder.encode(secondTextField.text, forKey: "secondLabel")
coder.encode(thirdTextField.text, forKey: "thirdLabel")
if firstTextField.isFirstResponder {
coder.encode(1, forKey: "EditField")
} else if secondTextField.isFirstResponder {
coder.encode(2, forKey: "EditField")
} else if thirdTextField.isFirstResponder {
coder.encode(3, forKey: "EditField")
}
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
if let text = coder.decodeObject(forKey: "firstLabel") as? String {
firstTextField.text = text
}
if let text = coder.decodeObject(forKey: "secondLabel") as? String {
secondTextField.text = text
}
if let text = coder.decodeObject(forKey: "thirdLabel") as? String {
thirdTextField.text = text
}
switch coder.decodeInteger(forKey: "EditField") {
case 1:
firstTextField.becomeFirstResponder()
case 2:
secondTextField.becomeFirstResponder()
case 3:
thirdTextField.becomeFirstResponder()
default:
break
}
}
Encode 해야 하는 데이터는 ❓
만약 보존하고 싶지 않은 VC가 있는 경우 ❓
해당 VC 뿐 아니라 Child, Presented VC 들 모두 Preservation process 를 진행하지 않음
func application(_ application: UIApplication, shouldRestoreSecureApplicationState coder: NSCoder) -> Bool {
true
}
Ask the view controller’s restoration class
: VC의 restoration class 에게 객체 재생성을 요청한다.
class PresentedViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
restorationIdentifier = String(describing: PresentedViewController.self)
restorationClass = PresentedViewController.self
}
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
print("presented", #function)
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
print("presented", #function)
}
}
extension PresentedViewController: UIViewControllerRestoration {
static func viewController(withRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? { // 코드로 하는 경우 필요
let vc = PresentedViewController()
vc.view.backgroundColor = .white // restoration class 에서 이상하게 view background가 black이 됨
return vc
}
}
Ask the app delegate
restoration class 를 설정하지 않는 경우 AppDelegate 로 Restoration process 가 위임됨
func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
switch identifierComponents.last {
case "ViewController":
return ViewController() // 2번 app delegate(실행O)
case "PresentedViewController":
return PresentedViewController() // 1번 restoration class(실행X)
case "SecondViewController":
return SecondViewController() // 2번 app delegate(실행O)
default:
return nil // 3,4번 tabbarcontroller(실행O 지만, 3/4번으로 감)
}
}
Before state restoration even begins, UIKit loads your app’s default view controllers from your storyboards. Because UIKit loads these view controllers for free, it is better not to create them using a restoration class or your app delegate. For all other view controllers, assign a restoration class only if the view controller is not defined in your storyboards. You might also assign a restoration class to prevent the creation of your view controller in specific circumstances.
VC 를 코드로 구현하는 경우
→ 스토리보드로 구현하는 경우에는 UIKit 이 Restoration Process 가 시작되기 전에 스토리보드에서 VC를 로드하기 때문에 스토리보드를 사용하는 경우 restoration class 나 app delegate 를 통해 VC를 다시 로드하는 것은 오히려 비효율적
즉, 코드로 구현한 경우 1,2 단계, 스토리보드로 구현한 경우 3,4 단계 방법 사용
특정 VC에 대해 restoration-process을 하고 싶지 않은 경우(restoration class 만 해당)
→ static func viewController(withRestorationIdentifierPath ) 에서 nil 반환
(3, 4 단계는 UIKit 에 의해 자동으로 VC 객체가 생성됨)
: UIKit 은 정확히 같은 restoration path 에 있는 이미 생성된 VC를 찾는다.
: 여전히 VC를 찾지 못한 경우, UIKit 은 자동으로 스토리보드로부터 VC를 인스턴스화 한다.
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
coder.encode(firstTextField.text, forKey: "firstLabel")
coder.encode(secondTextField.text, forKey: "secondLabel")
coder.encode(thirdTextField.text, forKey: "thirdLabel")
if firstTextField.isFirstResponder {
coder.encode(1, forKey: "EditField")
} else if secondTextField.isFirstResponder {
coder.encode(2, forKey: "EditField")
} else if thirdTextField.isFirstResponder {
coder.encode(3, forKey: "EditField")
}
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
if let text = coder.decodeObject(forKey: "firstLabel") as? String {
firstTextField.text = text
}
if let text = coder.decodeObject(forKey: "secondLabel") as? String {
secondTextField.text = text
}
if let text = coder.decodeObject(forKey: "thirdLabel") as? String {
thirdTextField.text = text
}
switch coder.decodeInteger(forKey: "EditField") {
case 1:
firstTextField.becomeFirstResponder()
case 2:
secondTextField.becomeFirstResponder()
case 3:
thirdTextField.becomeFirstResponder()
default:
break
}
}
extension SecondViewController: UIDataSourceModelAssociation {
func modelIdentifierForElement(at idx: IndexPath, in view: UIView) -> String? {
return "\(idx.row)"
}
func indexPathForElement(withModelIdentifier identifier: String, in view: UIView) -> IndexPath? {
guard let id = Int(identifier) else { return nil }
return IndexPath(row: id, section: 0)
}
}
func application(_ application: UIApplication, didDecodeRestorableStateWith coder: NSCoder) {
}
State Preservation 에는 2가지 접근법이 존재 문서링크
iOS12 이하의 VC based state restoration 사용하기 위해 SceneDelegate 를 삭제함