代码之家  ›  专栏  ›  技术社区  ›  Tony M

状态恢复工作,但在viewDidLoad中无效

  •  2
  • Tony M  · 技术社区  · 7 年前

    状态恢复在下面基于代码的ViewController上起作用,但随后通过第二次调用viewDidLoad“撤消”。我的问题是:我该如何避免这种情况? 断点位于 decodeRestorableState selectedGroup selectedType

    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))
      }
    }
    

      //====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
      }
    
    2 回复  |  直到 7 年前
        1
  •  8
  •   kharrison    7 年前

    viewDidLoad 将被调用两次,因为您的视图控制器被创建了两次。

    您没有说明如何创建视图控制器,但我怀疑您的问题是,视图控制器首先是由情节提要或应用程序委托创建的,然后是第二次,因为您设置了恢复类。

    viewDidLoad视图 viewDidLoad视图 调用一次,后跟 decodeRestorableState

    更新:确认您正在应用程序委托中创建视图控制器,因此不需要使用还原类。这解决了 被叫了两次。

    您希望在中创建初始根视图控制器 willFinishLaunchingWithOptions 在应用程序中,在状态恢复发生之前调用委托。

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

        2
  •  1
  •   CloakedEddy    7 年前

    在我六年的iOS编程生涯中,我不记得见过iOS呼叫 viewDidLoad() 在同一视图控制器上两次。因此,很可能您正在实例化CodeStackVC2两次:)

    据我所知,您正在中以编程方式创建视图层次结构 didFinishLaunchingWithOptions 之前 调用此委托方法。因此,iOS询问视图控制器的 对于新的视图控制器实例,然后执行设置基本层次结构的代码,创建新的视图控制器。

    尝试将代码从 willFinishLaunchingWithOptions: (在任何状态恢复之前调用)。然后,由于iOS尝试恢复的视图控制器已经存在,因此它不会从 UIViewControllerRestoration 协议,而不是调用 decodeRestorableState(with coder:)

    如果你需要更深入的解释,试试看 useyourloaf Apple docs -我发现这两种方法在理解苹果实施背后的概念方面都非常有用。尽管我必须承认,在我自己理解之前,我读了几遍书。