状态恢复工作但随后在 viewDidLoad 中无效
state restoration working but then nullified in viewDidLoad
注意代码已更新以包含评论中详述的修复,但这里是原始问题文本:
状态恢复适用于下面基于代码的 ViewController,但随后 "undone" 需要第二次调用 viewDidLoad。我的问题是:我该如何避免这种情况?
在 decodeRestorableState
处有一个断点,我可以看到它实际上确实恢复了 2 个参数 selectedGroup
和 selectedType
但随后它再次通过 viewDidLoad 并且这些参数被重置为 nil 所以恢复是没有效果。没有情节提要:如果您将此 class 与空的 ViewController 相关联,它将起作用(我仔细检查了这一点——也有一些按钮资产,但功能不需要它们)。我还在底部包含了启用状态恢复所需的 AppDelegate 方法。
import UIKit
class CodeStackVC2: UIViewController, FoodCellDel {
let fruit = ["Apple", "Orange", "Plum", "Qiwi", "Banana"]
let veg = ["Lettuce", "Carrot", "Celery", "Onion", "Brocolli"]
let meat = ["Beef", "Chicken", "Ham", "Lamb"]
let bread = ["Wheat", "Muffin", "Rye", "Pita"]
var foods = [[String]]()
let group = ["Fruit","Vegetable","Meat","Bread"]
var sView = UIStackView()
let cellId = "cellId"
var selectedGroup : Int?
var selectedType : Int?
override func viewDidLoad() {
super.viewDidLoad()
restorationIdentifier = "CodeStackVC2"
foods = [fruit, veg, meat, bread]
setupViews()
displaySelections()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let index = selectedGroup, let type = selectedType else { return }
pageControl.currentPage = index
let indexPath = IndexPath(item: index, section: 0)
cView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
cView.reloadItems(at: [indexPath])
guard let cell = cView.cellForItem(at: indexPath) as? FoodCell else { return }
cell.pickerView.selectRow(type, inComponent: 0, animated: true)
}
//State restoration encodes parameters in this func
override func encodeRestorableState(with coder: NSCoder) {
if let theGroup = selectedGroup,
let theType = selectedType {
coder.encode(theGroup, forKey: "theGroup")
coder.encode(theType, forKey: "theType")
}
super.encodeRestorableState(with: coder)
}
override func decodeRestorableState(with coder: NSCoder) {
selectedGroup = coder.decodeInteger(forKey: "theGroup")
selectedType = coder.decodeInteger(forKey: "theType")
super.decodeRestorableState(with: coder)
}
override func applicationFinishedRestoringState() {
//displaySelections()
}
//MARK: Views
lazy var cView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
layout.itemSize = CGSize(width: self.view.frame.width, height: 120)
let cRect = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 120)
let cv = UICollectionView(frame: cRect, collectionViewLayout: layout)
cv.backgroundColor = UIColor.lightGray
cv.isPagingEnabled = true
cv.dataSource = self
cv.delegate = self
cv.isUserInteractionEnabled = true
return cv
}()
lazy var pageControl: UIPageControl = {
let pageC = UIPageControl()
pageC.numberOfPages = self.foods.count
pageC.pageIndicatorTintColor = UIColor.darkGray
pageC.currentPageIndicatorTintColor = UIColor.white
pageC.backgroundColor = .black
pageC.addTarget(self, action: #selector(changePage(sender:)), for: UIControlEvents.valueChanged)
return pageC
}()
var textView: UITextView = {
let tView = UITextView()
tView.font = UIFont.systemFont(ofSize: 40)
tView.textColor = .white
tView.backgroundColor = UIColor.lightGray
return tView
}()
func makeButton(_ tag:Int) -> UIButton{
let newButton = UIButton(type: .system)
let img = UIImage(named: group[tag])?.withRenderingMode(.alwaysTemplate)
newButton.setImage(img, for: .normal)
newButton.tag = tag // used in handleButton()
newButton.contentMode = .scaleAspectFit
newButton.addTarget(self, action: #selector(handleButton(sender:)), for: .touchUpInside)
newButton.isUserInteractionEnabled = true
newButton.backgroundColor = .clear
return newButton
}
//Make a 4-item vertical stackView containing
//cView,pageView,subStackof 4-item horiz buttons, textView
func setupViews(){
view.backgroundColor = .lightGray
cView.register(FoodCell.self, forCellWithReuseIdentifier: cellId)
//generate an array of buttons
var buttons = [UIButton]()
for i in 0...foods.count-1 {
buttons += [makeButton(i)]
}
let subStackView = UIStackView(arrangedSubviews: buttons)
subStackView.axis = .horizontal
subStackView.distribution = .fillEqually
subStackView.alignment = .center
subStackView.spacing = 20
//set up the stackView
let stackView = UIStackView(arrangedSubviews: [cView,pageControl,subStackView,textView])
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 5
//Add the stackView using AutoLayout
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
cView.translatesAutoresizingMaskIntoConstraints = false
textView.translatesAutoresizingMaskIntoConstraints = false
cView.heightAnchor.constraint(equalTo: textView.heightAnchor, multiplier: 0.5).isActive = true
}
// selected item returned from pickerView
func pickerSelection(_ foodType: Int) {
selectedType = foodType
displaySelections()
}
func displaySelections() {
if let theGroup = selectedGroup,
let theType = selectedType {
textView.text = "\n \n Group: \(group[theGroup]) \n \n FoodType: \(foods[theGroup][theType])"
}
}
// 3 User Actions: Button, Page, Scroll
func handleButton(sender: UIButton) {
pageControl.currentPage = sender.tag
let x = CGFloat(sender.tag) * cView.frame.size.width
cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * cView.frame.size.width
cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let index = Int(cView.contentOffset.x / view.bounds.width)
pageControl.currentPage = Int(index) //change PageControl indicator
selectedGroup = Int(index)
let indexPath = IndexPath(item: index, section: 0)
guard let cell = cView.cellForItem(at: indexPath) as? FoodCell else { return }
selectedType = cell.pickerView.selectedRow(inComponent: 0)
displaySelections()
}
//this causes cView to be recalculated when device rotates
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
cView.collectionViewLayout.invalidateLayout()
}
}
//MARK: cView extension
extension CodeStackVC2: UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return foods.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FoodCell
cell.foodType = foods[indexPath.item]
cell.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: textView.frame.height * 0.4)
}
}
// *********************
protocol FoodCellDel {
func pickerSelection(_ food:Int)
}
class FoodCell:UICollectionViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
var delegate: FoodCellDel?
var foodType: [String]? {
didSet {
pickerView.reloadComponent(0)
//pickerView.selectRow(0, inComponent: 0, animated: true)
}
}
lazy var pickerView: UIPickerView = {
let pView = UIPickerView()
pView.frame = CGRect(x:0,y:0,width:Int(pView.bounds.width), height:Int(pView.bounds.height))
pView.delegate = self
pView.dataSource = self
pView.backgroundColor = .lightGray
return pView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
backgroundColor = .clear
addSubview(pickerView)
addConstraintsWithFormat("H:|[v0]|", views: pickerView)
addConstraintsWithFormat("V:|[v0]|", views: pickerView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if let count = foodType?.count {
return count
} else {
return 0
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let pickerLabel = UILabel()
pickerLabel.font = UIFont.systemFont(ofSize: 15)
pickerLabel.textAlignment = .center
pickerLabel.adjustsFontSizeToFitWidth = true
if let foodItem = foodType?[row] {
pickerLabel.text = foodItem
pickerLabel.textColor = .white
return pickerLabel
} else {
print("chap = nil in viewForRow")
return UIView()
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if let actualDelegate = delegate {
actualDelegate.pickerSelection(row)
}
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
AppDelegate 中的函数如下:
//====if set true, these 2 funcs enable state restoration
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
//replace the storyboard by making our own window
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
//this defines the entry point for our app
window?.rootViewController = CodeStackVC2()
return true
}
在我六年的 iOS 编程生涯中,我不记得曾见过 iOS 在同一个视图控制器上调用 viewDidLoad()
两次。所以很可能你正在实例化 CodeStackVC2 两次 :)
据我所知,您正在 didFinishLaunchingWithOptions
中以编程方式创建视图层次结构。但是,在调用此委托方法之前会调用状态恢复。因此,iOS 向视图控制器的 恢复 class 请求一个新的视图控制器实例,然后执行设置基本层次结构的代码,创建一个新的视图控制器。
尝试将您的代码从 didFinishLaunchingWithOptions
移动到 willFinishLaunchingWithOptions:
(在任何状态恢复之前调用)。然后,由于 iOS 试图恢复的视图控制器已经存在,它不会使用 UIViewControllerRestoration
协议中的长名称调用该方法,而是在该视图上调用 decodeRestorableState(with coder:)
控制器。
如果您需要更深入的解释,请尝试 useyourloaf or of course the Apple docs - 我发现两者对于理解 Apple 实施背后的概念都非常有用。虽然我必须承认,我自己读了几遍才明白。
如果 viewDidLoad
被调用两次,那是因为你的视图控制器被创建了两次。
你没有说你是如何创建视图控制器的,但我怀疑你的问题是视图控制器首先是由故事板或应用程序委托创建的,然后是第二次,因为你已经设置了恢复 class.
如果您的视图控制器不是由正常的应用程序加载序列创建的(否则一个恢复标识符就足够了),您只需要设置一个恢复 class。尝试删除 viewDidLoad
中设置恢复 class 的行,我想你会看到 viewDidLoad
被调用一次,然后是 decodeRestorableState
.
更新:确认您正在应用委托中创建视图控制器,因此您不需要使用恢复 class。这解决了 viewDidLoad
被调用两次的问题。
您想在应用委托中的 willFinishLaunchingWithOptions
中创建初始根视图控制器,因为它在状态恢复发生之前被调用。
恢复 selectedGroup 和 selectedType 值后的最后一个问题是更新 UI 元素(页面控件、集合视图)等以使用恢复的值
注意代码已更新以包含评论中详述的修复,但这里是原始问题文本:
状态恢复适用于下面基于代码的 ViewController,但随后 "undone" 需要第二次调用 viewDidLoad。我的问题是:我该如何避免这种情况?
在 decodeRestorableState
处有一个断点,我可以看到它实际上确实恢复了 2 个参数 selectedGroup
和 selectedType
但随后它再次通过 viewDidLoad 并且这些参数被重置为 nil 所以恢复是没有效果。没有情节提要:如果您将此 class 与空的 ViewController 相关联,它将起作用(我仔细检查了这一点——也有一些按钮资产,但功能不需要它们)。我还在底部包含了启用状态恢复所需的 AppDelegate 方法。
import UIKit
class CodeStackVC2: UIViewController, FoodCellDel {
let fruit = ["Apple", "Orange", "Plum", "Qiwi", "Banana"]
let veg = ["Lettuce", "Carrot", "Celery", "Onion", "Brocolli"]
let meat = ["Beef", "Chicken", "Ham", "Lamb"]
let bread = ["Wheat", "Muffin", "Rye", "Pita"]
var foods = [[String]]()
let group = ["Fruit","Vegetable","Meat","Bread"]
var sView = UIStackView()
let cellId = "cellId"
var selectedGroup : Int?
var selectedType : Int?
override func viewDidLoad() {
super.viewDidLoad()
restorationIdentifier = "CodeStackVC2"
foods = [fruit, veg, meat, bread]
setupViews()
displaySelections()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let index = selectedGroup, let type = selectedType else { return }
pageControl.currentPage = index
let indexPath = IndexPath(item: index, section: 0)
cView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
cView.reloadItems(at: [indexPath])
guard let cell = cView.cellForItem(at: indexPath) as? FoodCell else { return }
cell.pickerView.selectRow(type, inComponent: 0, animated: true)
}
//State restoration encodes parameters in this func
override func encodeRestorableState(with coder: NSCoder) {
if let theGroup = selectedGroup,
let theType = selectedType {
coder.encode(theGroup, forKey: "theGroup")
coder.encode(theType, forKey: "theType")
}
super.encodeRestorableState(with: coder)
}
override func decodeRestorableState(with coder: NSCoder) {
selectedGroup = coder.decodeInteger(forKey: "theGroup")
selectedType = coder.decodeInteger(forKey: "theType")
super.decodeRestorableState(with: coder)
}
override func applicationFinishedRestoringState() {
//displaySelections()
}
//MARK: Views
lazy var cView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
layout.itemSize = CGSize(width: self.view.frame.width, height: 120)
let cRect = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 120)
let cv = UICollectionView(frame: cRect, collectionViewLayout: layout)
cv.backgroundColor = UIColor.lightGray
cv.isPagingEnabled = true
cv.dataSource = self
cv.delegate = self
cv.isUserInteractionEnabled = true
return cv
}()
lazy var pageControl: UIPageControl = {
let pageC = UIPageControl()
pageC.numberOfPages = self.foods.count
pageC.pageIndicatorTintColor = UIColor.darkGray
pageC.currentPageIndicatorTintColor = UIColor.white
pageC.backgroundColor = .black
pageC.addTarget(self, action: #selector(changePage(sender:)), for: UIControlEvents.valueChanged)
return pageC
}()
var textView: UITextView = {
let tView = UITextView()
tView.font = UIFont.systemFont(ofSize: 40)
tView.textColor = .white
tView.backgroundColor = UIColor.lightGray
return tView
}()
func makeButton(_ tag:Int) -> UIButton{
let newButton = UIButton(type: .system)
let img = UIImage(named: group[tag])?.withRenderingMode(.alwaysTemplate)
newButton.setImage(img, for: .normal)
newButton.tag = tag // used in handleButton()
newButton.contentMode = .scaleAspectFit
newButton.addTarget(self, action: #selector(handleButton(sender:)), for: .touchUpInside)
newButton.isUserInteractionEnabled = true
newButton.backgroundColor = .clear
return newButton
}
//Make a 4-item vertical stackView containing
//cView,pageView,subStackof 4-item horiz buttons, textView
func setupViews(){
view.backgroundColor = .lightGray
cView.register(FoodCell.self, forCellWithReuseIdentifier: cellId)
//generate an array of buttons
var buttons = [UIButton]()
for i in 0...foods.count-1 {
buttons += [makeButton(i)]
}
let subStackView = UIStackView(arrangedSubviews: buttons)
subStackView.axis = .horizontal
subStackView.distribution = .fillEqually
subStackView.alignment = .center
subStackView.spacing = 20
//set up the stackView
let stackView = UIStackView(arrangedSubviews: [cView,pageControl,subStackView,textView])
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 5
//Add the stackView using AutoLayout
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
cView.translatesAutoresizingMaskIntoConstraints = false
textView.translatesAutoresizingMaskIntoConstraints = false
cView.heightAnchor.constraint(equalTo: textView.heightAnchor, multiplier: 0.5).isActive = true
}
// selected item returned from pickerView
func pickerSelection(_ foodType: Int) {
selectedType = foodType
displaySelections()
}
func displaySelections() {
if let theGroup = selectedGroup,
let theType = selectedType {
textView.text = "\n \n Group: \(group[theGroup]) \n \n FoodType: \(foods[theGroup][theType])"
}
}
// 3 User Actions: Button, Page, Scroll
func handleButton(sender: UIButton) {
pageControl.currentPage = sender.tag
let x = CGFloat(sender.tag) * cView.frame.size.width
cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * cView.frame.size.width
cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let index = Int(cView.contentOffset.x / view.bounds.width)
pageControl.currentPage = Int(index) //change PageControl indicator
selectedGroup = Int(index)
let indexPath = IndexPath(item: index, section: 0)
guard let cell = cView.cellForItem(at: indexPath) as? FoodCell else { return }
selectedType = cell.pickerView.selectedRow(inComponent: 0)
displaySelections()
}
//this causes cView to be recalculated when device rotates
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
cView.collectionViewLayout.invalidateLayout()
}
}
//MARK: cView extension
extension CodeStackVC2: UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return foods.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FoodCell
cell.foodType = foods[indexPath.item]
cell.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: textView.frame.height * 0.4)
}
}
// *********************
protocol FoodCellDel {
func pickerSelection(_ food:Int)
}
class FoodCell:UICollectionViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
var delegate: FoodCellDel?
var foodType: [String]? {
didSet {
pickerView.reloadComponent(0)
//pickerView.selectRow(0, inComponent: 0, animated: true)
}
}
lazy var pickerView: UIPickerView = {
let pView = UIPickerView()
pView.frame = CGRect(x:0,y:0,width:Int(pView.bounds.width), height:Int(pView.bounds.height))
pView.delegate = self
pView.dataSource = self
pView.backgroundColor = .lightGray
return pView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
backgroundColor = .clear
addSubview(pickerView)
addConstraintsWithFormat("H:|[v0]|", views: pickerView)
addConstraintsWithFormat("V:|[v0]|", views: pickerView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if let count = foodType?.count {
return count
} else {
return 0
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let pickerLabel = UILabel()
pickerLabel.font = UIFont.systemFont(ofSize: 15)
pickerLabel.textAlignment = .center
pickerLabel.adjustsFontSizeToFitWidth = true
if let foodItem = foodType?[row] {
pickerLabel.text = foodItem
pickerLabel.textColor = .white
return pickerLabel
} else {
print("chap = nil in viewForRow")
return UIView()
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if let actualDelegate = delegate {
actualDelegate.pickerSelection(row)
}
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
AppDelegate 中的函数如下:
//====if set true, these 2 funcs enable state restoration
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
//replace the storyboard by making our own window
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
//this defines the entry point for our app
window?.rootViewController = CodeStackVC2()
return true
}
在我六年的 iOS 编程生涯中,我不记得曾见过 iOS 在同一个视图控制器上调用 viewDidLoad()
两次。所以很可能你正在实例化 CodeStackVC2 两次 :)
据我所知,您正在 didFinishLaunchingWithOptions
中以编程方式创建视图层次结构。但是,在调用此委托方法之前会调用状态恢复。因此,iOS 向视图控制器的 恢复 class 请求一个新的视图控制器实例,然后执行设置基本层次结构的代码,创建一个新的视图控制器。
尝试将您的代码从 didFinishLaunchingWithOptions
移动到 willFinishLaunchingWithOptions:
(在任何状态恢复之前调用)。然后,由于 iOS 试图恢复的视图控制器已经存在,它不会使用 UIViewControllerRestoration
协议中的长名称调用该方法,而是在该视图上调用 decodeRestorableState(with coder:)
控制器。
如果您需要更深入的解释,请尝试 useyourloaf or of course the Apple docs - 我发现两者对于理解 Apple 实施背后的概念都非常有用。虽然我必须承认,我自己读了几遍才明白。
如果 viewDidLoad
被调用两次,那是因为你的视图控制器被创建了两次。
你没有说你是如何创建视图控制器的,但我怀疑你的问题是视图控制器首先是由故事板或应用程序委托创建的,然后是第二次,因为你已经设置了恢复 class.
如果您的视图控制器不是由正常的应用程序加载序列创建的(否则一个恢复标识符就足够了),您只需要设置一个恢复 class。尝试删除 viewDidLoad
中设置恢复 class 的行,我想你会看到 viewDidLoad
被调用一次,然后是 decodeRestorableState
.
更新:确认您正在应用委托中创建视图控制器,因此您不需要使用恢复 class。这解决了 viewDidLoad
被调用两次的问题。
您想在应用委托中的 willFinishLaunchingWithOptions
中创建初始根视图控制器,因为它在状态恢复发生之前被调用。
恢复 selectedGroup 和 selectedType 值后的最后一个问题是更新 UI 元素(页面控件、集合视图)等以使用恢复的值