You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于NSUserActivity的UITabViewController(含导航控制器)状态恢复问询

基于NSUserActivity的Scene状态恢复方案

针对疑问的解答

1. 导航栈中视图控制器的恢复逻辑与时机

NSUserActivity不会自动感知导航栈,状态恢复的核心逻辑需要主动实现:

  • 时机:当Scene重新激活(比如APP从后台恢复、CarPlay切换连接)时,系统会在SceneDelegate的scene(_:willConnectTo:options:)方法中传入保存的NSUserActivity,此时就是触发状态恢复的时机。
  • 识别目标控制器:保存状态时,需把导航栈中每个控制器的标识(如类名、自定义ID)和必要参数存入NSUserActivity的userInfo字典;恢复时读取这些信息,逐个实例化控制器并重建导航栈。另外UINavigationController本身支持自动保存栈结构,但需要给每个控制器设置唯一的restorationIdentifier,不过自定义userInfo的方式更灵活,适合复杂层级。

2. 确保恢复的控制器为正确类

可通过两种方式保障:

  • 保存状态时,将控制器的类名(通过NSStringFromClass(type(of: self))获取)存入NSUserActivity的userInfo;恢复时用NSClassFromString解析类名,实例化对应的控制器类。
  • 给每个可恢复的控制器设置restorationClass属性,指定恢复时使用的类,系统会自动根据该属性创建正确的实例。

基于UITabBarController+UINavigationController的简单示例

1. SceneDelegate核心代码

负责状态的保存与触发恢复:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: windowScene)
        
        // 初始化根TabBar控制器
        let tabBarVC = MainTabBarController()
        window?.rootViewController = tabBarVC
        window?.makeKeyAndVisible()
        
        // 处理传入的NSUserActivity,执行状态恢复
        if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
            restoreAppState(from: activity)
        }
    }
    
    // 生成保存状态的NSUserActivity
    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
        guard let tabBarVC = window?.rootViewController as? MainTabBarController else { return nil }
        let activity = NSUserActivity(activityType: "com.yourapp.state.restoration")
        
        // 保存当前选中的Tab索引
        activity.userInfo?["selectedTabIndex"] = tabBarVC.selectedIndex
        
        // 保存当前Tab下的导航栈信息
        if let navVC = tabBarVC.selectedViewController as? UINavigationController {
            var stackData = [[String: Any]]()
            for vc in navVC.viewControllers {
                if let restorableVC = vc as? StateRestorable {
                    stackData.append(restorableVC.encodeState())
                }
            }
            activity.userInfo?["navigationStack"] = stackData
        }
        return activity
    }
    
    // 从NSUserActivity恢复应用状态
    private func restoreAppState(from activity: NSUserActivity) {
        guard let tabBarVC = window?.rootViewController as? MainTabBarController,
              let userInfo = activity.userInfo else { return }
        
        // 恢复选中的Tab
        if let index = userInfo["selectedTabIndex"] as? Int {
            tabBarVC.selectedIndex = index
        }
        
        // 重建导航栈
        if let navVC = tabBarVC.selectedViewController as? UINavigationController,
           let stackData = userInfo["navigationStack"] as? [[String: Any]] {
            var restoredVCs = [UIViewController]()
            for data in stackData {
                guard let className = data["className"] as? String,
                      let vcClass = NSClassFromString(className) as? StateRestorable.Type else { continue }
                let vc = vcClass.init()
                vc.restoreState(from: data)
                restoredVCs.append(vc)
            }
            if !restoredVCs.isEmpty {
                navVC.setViewControllers(restoredVCs, animated: false)
            }
        }
    }
}

2. 定义可恢复协议

统一控制器的状态编码与恢复接口:

protocol StateRestorable: UIViewController {
    func encodeState() -> [String: Any]
    func restoreState(from data: [String: Any])
}

// 扩展协议,提供默认实现
extension StateRestorable {
    func encodeState() -> [String: Any] {
        return ["className": NSStringFromClass(type(of: self))]
    }
    
    func restoreState(from data: [String: Any]) {
        // 子类可重写此方法,恢复自定义数据
    }
}

3. 实现TabBar控制器

配置Tab与对应的导航控制器:

class MainTabBarController: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 首页Tab
        let homeNav = UINavigationController(rootViewController: HomeViewController())
        homeNav.tabBarItem = UITabBarItem(title: "首页", image: UIImage(systemName: "house"), tag: 0)
        
        // 我的Tab
        let profileNav = UINavigationController(rootViewController: ProfileViewController())
        profileNav.tabBarItem = UITabBarItem(title: "我的", image: UIImage(systemName: "person"), tag: 1)
        
        viewControllers = [homeNav, profileNav]
    }
}

4. 具体页面控制器实现

// 首页控制器
class HomeViewController: UIViewController, StateRestorable {
    var userData: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        title = "首页"
        
        // 添加跳转按钮
        let pushBtn = UIButton(type: .system)
        pushBtn.setTitle("查看详情", for: .normal)
        pushBtn.addTarget(self, action: #selector(pushDetail), for: .touchUpInside)
        pushBtn.frame = CGRect(x: 100, y: 200, width: 200, height: 44)
        view.addSubview(pushBtn)
    }
    
    @objc func pushDetail() {
        let detailVC = DetailViewController()
        detailVC.detailContent = "首页专属详情内容"
        navigationController?.pushViewController(detailVC, animated: true)
    }
    
    // 重写编码方法,保存自定义数据
    func encodeState() -> [String: Any] {
        var state = super.encodeState()
        state["userData"] = userData ?? ""
        return state
    }
    
    func restoreState(from data: [String: Any]) {
        userData = data["userData"] as? String
    }
}

// 详情页控制器
class DetailViewController: UIViewController, StateRestorable {
    var detailContent: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .lightGray
        title = "详情页"
        
        if let content = detailContent {
            let label = UILabel(frame: CGRect(x: 50, y: 200, width: 300, height: 44))
            label.text = content
            view.addSubview(label)
        }
    }
    
    func encodeState() -> [String: Any] {
        var state = super.encodeState()
        state["detailContent"] = detailContent ?? ""
        return state
    }
    
    func restoreState(from data: [String: Any]) {
        detailContent = data["detailContent"] as? String
    }
}

// 我的页面控制器
class ProfileViewController: UIViewController, StateRestorable {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .blue
        title = "我的"
    }
}

注意事项

  • 在Info.plist中注册NSUserActivityTypes,添加你使用的activityType(如com.yourapp.state.restoration)
  • 给Scene配置restorationIdentifier,可在Info.plist的UIApplicationSceneManifest中设置,或代码中通过scene.session.configuration.restorationIdentifier指定

内容的提问来源于stack exchange,提问作者Nic Hubbard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:28:13