ios - 状态恢复工作但随后在 viewDidLoad 中无效

标签 ios swift state-restoration

注意代码已更新以包含评论中详述的修复,但这里是原始问题文本:

状态恢复在下面基于代码的 ViewController 上运行,但随后通过对 viewDidLoad 的第二次调用“撤消”。我的问题是:我该如何避免这种情况? 在 decodeRestorableState 处设置断点,我可以看到它实际上确实恢复了 2 个参数 selectedGroupselectedType 但随后它再次通过 viewDidLoad 和那些参数重置为 nil,因此恢复无效。没有 Storyboard :如果您将此类与一个空的 ViewController 相关联,它将起作用(我仔细检查了这一点——也有一些按钮 Assets ,但功能不需要它们)。我还在底部包含了启用状态恢复所需的 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
  }

最佳答案

如果 viewDidLoad 被调用两次,那是因为您的 View Controller 被创建了两次。

你没有说你是如何创建 View Controller 的,但我怀疑你的问题是 View Controller 首先是由 Storyboard或应用程序委托(delegate)创建的,然后是第二次,因为你设置了一个恢复类。

如果您的 View Controller 不是由正常的应用程序加载序列创建的(否则一个恢复标识符就足够了),您只需要设置一个恢复类。尝试删除 viewDidLoad 中设置恢复类的行,我想您会看到 viewDidLoad 被调用一次,然后是 decodeRestorableState

更新:确认您正在应用委托(delegate)中创建 View Controller ,因此您不需要使用恢复类。这解决了 viewDidLoad 被调用两次的问题。

您想在应用程序委托(delegate)的 willFinishLaunchingWithOptions 中创建初始 Root View Controller ,因为它在状态恢复发生之前被调用。

恢复 selectedGroup 和 selectedType 值后的最后一个问题是更新 UI 元素(页面控件、 Collection View )等以使用恢复的值

关于ios - 状态恢复工作但随后在 viewDidLoad 中无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45534177/

相关文章:

iOS - AudioServicesPlayAlertSound 在 ipod 上不工作?

ios - 在...的对象上找不到属性 NSManagedObjects 上的错误调试属性

ios - 模态视图 Controller 动画状态恢复

安卓 : Restoring recyclerview parallax

ios - 核心蓝牙 : testing state preservation and restoration

ios - 尝试通过 UITableViewCell 的滑动手势更新 FMDB

ios - 通过函数更新 UILabel ....我缺少什么吗?

swift - 我如何在 SpriteKit 中在我的角色后面留下痕迹?

Swift 检查两个符合协议(protocol)的对象是否引用相同

swift - 分配最小最大变量/解构数组的好方法?