skip to Main Content

I want to react every time the application state changes. I’m listening to the .applicationState publisher:

UIApplication.shared.publisher(for: .applicationState)
  .subscribe(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
    switch completion {
    case .finished:
      print("finished")
    case .failure:
      print("failure")
    }
  }, receiveValue: { state in
    print("state", state.rawValue)
  })
  .store(in: &cancellables)

However, this only fires once (but doesn’t complete). If I background the app and reopen it, it doesn’t publish any new values. Why?

2

Answers


  1. The publisher you are using is for KVO-compliant properties. (See Performing Key-Value Observing with Combine.) But the archived Key-Value Observing Programming Guide warns us:

    Important: Not all class are KVO-compliant for all properties. … Typically properties in Apple-supplied frameworks are only KVO-compliant if they are documented as such.

    But the notification center can inform us of these state changes, e.g., didBecomeActiveNotification:

    NotificationCenter.default
        .publisher(for: UIApplication.didBecomeActiveNotification)
        .subscribe(on: DispatchQueue.main)
        .sink { completion in
            switch completion {
            case .finished:
                print("finished")
            case .failure:
                print("failure")
            }
        } receiveValue: { notification in
            print("notification", notification)
        }
        .store(in: &cancellables)
    

    Or, just:

    NotificationCenter.default
        .publisher(for: UIApplication.didBecomeActiveNotification)
        .subscribe(on: DispatchQueue.main)
        .sink { notification in
            print("notification", notification)
        }
        .store(in: &cancellables)
    

    See Routing Notifications to Combine Subscribers. See Using Key-Value Observing in Swift for information about creating your own KVO-compliant types in Swift.

    Login or Signup to reply.
  2. It looks applicationstate like is not ‘KVO-compliant’. Which means you can’t use KVO to observe the property.

    NSObject.publisher(for:) is managed by KVO

    To read the applicationState from single Publisher you can use multiple Notification about UI-Lifecycle like bleow.

        // If you have no UIScene setting on info.plist and not using SwiftUI App, this publisher is enough
        let legacyAppStatePublisher = NotificationCenter.default.publisher(
            for: UIApplication.didBecomeActiveNotification,
            object: UIApplication.shared
        )
        // when didBecomeActiveNotification is dispatched, applicationState already changed to next state so read it directly
        .map{ _ in
            UIApplication.shared.applicationState
        }.merge(
            with: NotificationCenter.default.publisher(
                for: UIApplication.didEnterBackgroundNotification,
                object: UIApplication.shared
            )
            // when didEnterBackgroundNotification is dispatched, applicationState already changed to next state so read it directly
            .map{ _ in UIApplication.shared.applicationState },
            NotificationCenter.default.publisher(
                for: UIApplication.willEnterForegroundNotification,
                object: UIApplication.shared
            )
            // when willEnterForegroundNotification occurs applicationState is not changed yet, redispatch to main queue to handle it after change occurs.
            .receive(on: DispatchQueue.main)
            .map{ _ in UIApplication.shared.applicationState },
            NotificationCenter.default.publisher(
                for: UIApplication.willResignActiveNotification,
                object: UIApplication.shared
            )
            // when willEnterForegroundNotification occurs applicationState is not changed yet, redispatch to main queue to handle it after change occurs.
            .receive(on: DispatchQueue.main)
            .map{ _ in UIApplication.shared.applicationState }
        )
        // prepend initial state if you needs this
        .prepend(UIApplication.shared.applicationState)
        
        // If your app's UI is managed by UIScene, you have to use this publisher
        let modernAppStatePublisher =
        NotificationCenter.default.publisher(
            for: UIScene.didEnterBackgroundNotification
        )
        .map{ _ in
            UIApplication.shared.applicationState
        }
        .merge(
            with: NotificationCenter.default.publisher(
                for: UIScene.didActivateNotification
            )
            // did... notification read state now
            .map{ _ in UIApplication.shared.applicationState },
            NotificationCenter.default.publisher(
                for: UIScene.willDeactivateNotification
            )
            // will... notification read state after notification dispatching is finished
            .receive(on: DispatchQueue.main)
            .map{ _ in UIApplication.shared.applicationState },
            NotificationCenter.default.publisher(
                for: UIScene.willEnterForegroundNotification
            )
            // will... notification read state after notification dispatching is finished
            .receive(on: DispatchQueue.main)
            .map{ _ in UIApplication.shared.applicationState }
        )
        // prepend initial state if you needs this
        .prepend(UIApplication.shared.applicationState)
        // remove duplicates since UIApplicationState represents the most active scene's state
        .removeDuplicates()
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search