TCA and restoring state using User Activity

I want to support User Activities to be able to save, restore and hand off state in this new TCA refactoring project.

@mbrandonw @stephencelis Have you looked into it and do you have any suggestions? Thanks

Edit: 3rd attempt:

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

  lazy var store = {
    Store(
      initialState: AppState(),
      reducer: appReducer.debug(),
      environment: .init(
        mainQueue: { DispatchQueue.main.eraseToAnyScheduler() },
        userInfo: { [weak self] in
          self?.window?.windowScene?.userActivity?.userInfo
        },
        addUserInfoEntries: { [weak self] entries in
          guard let scene = self?.window?.windowScene else { return }

          if scene.userActivity == nil {
            scene.userActivity = NSUserActivity(
              activityType: SceneDelegate.mainSceneActivityType()
            )
          }

          scene.userActivity?.addUserInfoEntries(from: entries)
        }
      )
    )
  }()

  // Tells the delegate about the addition of a scene to the app.
  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    if let userActivity = connectionOptions.userActivities.first
        ?? session.stateRestorationActivity {
        scene.userActivity = userActivity
    }

    window = (scene as? UIWindowScene).map(UIWindow.init(windowScene:))
    window?.rootViewController = RootViewController(store: store)
    window?.makeKeyAndVisible()
  }

  func sceneDidBecomeActive(_ scene: UIScene) {
    if let userActivity = window?.windowScene?.userActivity {
      userActivity.becomeCurrent()
    }
  }

 func sceneWillResignActive(_ scene: UIScene) {
    if let userActivity = window?.windowScene?.userActivity {
      userActivity.resignCurrent()
    }
  }

  func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
    return scene.userActivity
  }

extension SceneDelegate {
  // Activity type for restoring this scene (loaded from the plist).
  static let mainSceneActivityType = { () -> String in
    // Load the activity type from the Info.plist.
    let activityTypes = Bundle.main.infoDictionary?["NSUserActivityTypes"] as? [String]
    return activityTypes![0]
  }
}

in my reducer:

   switch action {
    case .onAppear:
      guard let userInfo = environment.userInfo(),
            let rawValue = userInfo[NSUserActivity.selectedTagKey] as? Int,
            let selectedTag = Tag(rawValue: rawValue)  else {
        return .none
      }
      return .merge(.init(value: .select(selectedTag)))
  case .select(let tag):
      state.selectedTag = tag

      return .fireAndForget {
        environment.addUserInfoEntries(
          [NSUserActivity.selectedTagKey: tag.rawValue]
        )
      }

It works. However it feels there might be a more practical way to do it.

Hum...

Reading Increasing App Usage with Suggestions Based on User Activities ... my approach is a bit too naive.

They use a viewController to manage a single user activity and they use userActivity property of UIViewController and implement a NSUserActivityDelegate

Use the userActivity property of UIViewController, which is defined in
UIResponder. UIKit will automatically manage this user activity and
make it current when the view controller is present in the view
hierarchy.

Terms of Service

Privacy Policy

Cookie Policy