无法让状态恢复与编程导航控制器一起使用

Posted

技术标签:

【中文标题】无法让状态恢复与编程导航控制器一起使用【英文标题】:Can't get State Restoration to work with Programmatic Navigation Controller 【发布时间】:2017-08-21 07:36:39 【问题描述】:

我无法使用导航控制器进行状态恢复。我正在使用 Swift,不想使用 Storyboards (Programmatic)。我在网上找到的几乎所有帮助都不是 Swift 或 Storyboards。

在下面的演示代码中,ViewController 包含一个简单的 PickerView,一个 selection 变量跟踪 Picker 的选择。 AppDelegate 提供了 2 个选项。使用 Option 1,不使用 Navigation Controller,并且 Picker 的状态恢复正常,但使用 Option 2 中的 Navigation Controller 无法恢复。 (在下面的代码中,选项 1 被注释掉,选项 2 处于活动状态)。

您可以将下面的代码复制并粘贴到新的 singleView 应用程序中,它应该会重现我所描述的内容。 (我测试过)

AppDelegate 代码:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate 

  var window: UIWindow?

  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 
    window = UIWindow(frame: UIScreen.main.bounds)
    window?.makeKeyAndVisible()
    let pickerVC = ViewController()
    //Option 1: No NavC used
    //window?.rootViewController = pickerVC

    //Option 2: NavC used
    let navC = UINavigationController(rootViewController: pickerVC)
    navC.restorationIdentifier = "PickerNav"
    window?.rootViewController = navC

    return true
  

//  func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [Any], coder: NSCoder) -> UIViewController? 
//    let storyboard = UIStoryboard(name: "Main", bundle: nil)
//      if let lastItem = identifierComponents.last as? String 
//        return storyboard.instantiateViewController(withIdentifier: lastItem)
//      
//    return nil
//  

ViewController 代码:

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource 

  var pickerView: UIPickerView!
  var selection = 0
  let group = ["Fruit","Vegetable","Meat","Bread"]

  override func viewDidLoad() 
    super.viewDidLoad()
    restorationIdentifier = "PickerVC"
    setupPicker()
  

  override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)
    pickerView.selectRow(selection, inComponent: 0, animated: false)
  

  override func encodeRestorableState(with coder: NSCoder) 
    coder.encode(selection, forKey: "selection")
    super.encodeRestorableState(with: coder)
  

  override func decodeRestorableState(with coder: NSCoder) 
    selection = coder.decodeInteger(forKey: "selection")
    super.decodeRestorableState(with: coder)
  

  func setupPicker() 
    pickerView = UIPickerView()
    pickerView.delegate = self
    pickerView.dataSource = self
    view.addSubview(pickerView)

    pickerView.translatesAutoresizingMaskIntoConstraints = false
    pickerView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
    pickerView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor).isActive = true
    pickerView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    pickerView.heightAnchor.constraint(equalToConstant: 300).isActive = true
  

  func numberOfComponents(in pickerView: UIPickerView) -> Int 
    return 1
  

  func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int 
    return group.count
  

  func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) 
    selection = pickerView.selectedRow(inComponent: 0)
  

  func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView 
    let pickerLabel = UILabel()
    pickerLabel.font = UIFont.systemFont(ofSize: 26)
    pickerLabel.textAlignment = .center
    pickerLabel.text = group[row]
    pickerLabel.textColor = .white
    return pickerLabel
  

我的测试详情: 使用选项 1,更改 Picker 后跟 cmd-Shift-H 会导致 selection 变量保存在 encodeRestorableState 中。然后我点击 Xcode Stop 按钮,然后再次运行,selection 变量在decodeRestorableState 中恢复。相比之下,使用选项 2 状态恢复不起作用,因为从未调用过decodeRestorableState,因此不会恢复selection 变量。但是,viewDidAppear 处的断点显示 navigationController?.restorationIdentifier = "PickerNav" 和 restorationIdentifier = "PickerVC"

根据我的阅读,我怀疑我可能需要在 AppDelegate 中使用viewControllerWithRestorationIdentifierPath,但我不知道如何正确使用它。我在 AppDelegate 底部的尝试(代码被注释掉)导致应用程序崩溃。

【问题讨论】:

【参考方案1】:

您可以为您的 ViewController 添加一个扩展,使其符合协议UIViewControllerRestoration 并实现viewControllerWithRestorationIdentifierPath 方法。 在ViewControllerviewDidLoad函数中添加restorationClass就像restorationIdentifer一样

    override func viewDidLoad() 
        super.viewDidLoad()
        restorationIdentifier = "PickerVC"
        restorationClass = ViewController.self
        setupPicker()
    


extension ViewController: UIViewControllerRestoration 

    static func viewController(withRestorationIdentifierPath identifierComponents: [Any], coder: NSCoder) -> UIViewController? 
        let vc = ViewController()
        return vc
    


在您的 ViewController 类中添加此代码并删除您在 AppDelegate 中添加的 viewControllerWithRestorationIdentifierPath 方法

【讨论】:

这行得通——谢谢!我不清楚为什么在导航控制器之前没有恢复类的情况下工作正常,然后需要一个带有导航控制器的恢复类。根据文档,“如果视图控制器没有恢复类,UIKit 要求应用程序委托提供视图控制器 [使用 viewControllerWithRestorationIdentifierPath] ...如果该方法返回 nil,UIKit 会尝试隐式查找视图控制器”。不确定在这种情况下发生了什么?无论如何,再次感谢! @TonyM 很酷,它起作用了。我也不知道为什么会这样:(

以上是关于无法让状态恢复与编程导航控制器一起使用的主要内容,如果未能解决你的问题,请参考以下文章

将标签栏控制器与导航控制器一起使用时出现问题

标签栏和导航控制器应用程序中的状态恢复

无法在 tableviewcontroller 上获得半透明导航栏。与视图控制器和 tableview 一起工作正常

在 iOS 7 中让导航栏与状态栏重叠

当 hidesBottomBarWhenPushed = YES 状态恢复时,导航控制器不隐藏标签栏

让导航栏与iOS 7中的状态栏重叠