How to start main iOS app from companion watch app in background?
Asked Answered
F

2

13

Situation:

Since our users have updated their iOS to 11 and/or WatchOS to 4, our iOS app doesn't seem to fire any scheduled timers when the app gets started by our WatchOS app. Maybe we are doing something wrong when starting our main app from the WatchOS app.

Context & code:

Our WatchOS app is a companion app that lets the user start/stop our iPhone app in the background by pressing a button. We do this by using:

func startMainApp() {
    guard WCSession.default().isReachable == true else {
        print("Watch is not reachable")
        return
    }

    var data = [String : AnyObject]()
    data[WatchActions.actionKey()] = NSNumber.init(value: WatchActions.startApp.rawValue as Int)

    WCSession.default().sendMessage(data, replyHandler: { (result: [String : Any]) in
        let resultNumber = result[WatchActions.resultKey()] as? NSNumber
        let resultBool = resultNumber!.boolValue
        if resultBool == true {
            self.setModeActivated()
        } else {
            self.setModeActivationFailed()
        }

    }) { (error: Error) in
        if (error as NSError).code != 7012 {
            print("start app error: \(error.localizedDescription)")
            self.setModeActivationFailed()
        }
    }
}

Then in our main app, we receive the message and start our base controller:

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
    if let actionNumber : NSNumber = message[WatchActions.actionKey()] as? NSNumber {
        if let watchAction : WatchActions = WatchActions(rawValue: actionNumber.intValue) {

            switch(watchAction) {
                case .isAppActive:
                    let result = BaseController.sharedInstance.sleepAndWakeUpController.isAwake()
                     replyHandler([WatchActions.resultKey() : NSNumber.init(value: result as Bool)])
                return

                case .startApp:
                    AudioController.sharedInstance().playActivatedSound()

                    let isRunningOnForeground = ApplicationStateHelper.isActive()
                    if isRunningOnForeground == false {
                        BaseController.sharedInstance.start(inBackground: true)
                    }
                    let result = true
                    replyHandler([WatchActions.resultKey() : NSNumber.init(value: result as Bool)])

                    DDLogInfo("[APPLE WATCH] [didReceiveMessage] [.startApp]")
                return
            }
        }
    }

    replyHandler([WatchActions.resultKey() : NSNumber.init(value: false as Bool)])
    return
}

Everything seems to work as before, we correctly get GPS locations, all our processes get started, however, Timer objects that get started, don't fire.

This worked perfectly before on iOS 10, so I suspect this has something to do with iOS 11 background states that work differently. However, I cannot seem to find any documentation of this.

Extra info:

  • On iOS 10, when we started our main app this way, the app got visible in the multitask view on the iPhone. Now on iOS 11, it isn't visible in the multitask view, however it does run on background. I successfully see local notifications that I schedule on the background, I can debug through the active code and when tapping the app icon, the app is immediately available.
  • Our WatchOS app has the deployment target of 2.0
  • Debugged via XCode with device connected, using Debug-->Attach to PID or Name-->Entered app name. Then start our app from the Apple Watch and debug.
  • Reproducable on iOS 11.0.3 with WatchOS 4.0 on iPhone 6

Questions: What is the best way to start our main app from the watch app? Has something changed in iOS 11/WatchOS 4 regarding to background states? Can I find documentation of this? Could this be an iOS bug?

Footpad answered 27/11, 2017 at 9:28 Comment(1)
You mention timers not working, but the code you shared doesn’t have an example of a timer that doesn’t fire.Allelomorph
A
6

All I can offer you is a confirmation that this behavior did in fact change from iOS 10 to iOS 11. It is my suspicion that the behavior on iOS 10 (and earlier?) was incorrect. Apple doesn't have any qualms about changing behavior that was inadvertent/what they deem incorrect even if developer's come to rely on the behavior (I'm pretty sure I used this behavior on my last watch project).

The fact is that the UIApplication's state when launched by a message from the watch is background. Timer's aren't supposed to run when the application is in the background unless using particular background execution modes/background task. That fact is pretty well known and is usually encountered quite early on in an iOS developer's career. The fact that timer's would run in the background when launched from the watch was, I can surmise, a mistake.

I do not know your use case, i.e. why you were relying on those timers, but one thing you can do that is quite simple is to create an empty background task that will get you a little more time when the app is launched.

var backgroundTask: UIBackgroundTaskIdentifier?
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "app Start Task", expirationHandler: {
    guard let task = backgroundTask else { return }
    UIApplication.shared.endBackgroundTask(task)
})

let timer = Timer(timeInterval: 1, repeats: true) { (timer) in
    print("Running")
}

If you need a more consistent, longer running solution, you may need to leverage your location updates as an opportunity to do whatever work the timer is currently for. There are plenty of other background modes to pursue as well.

Summary of your questions:

Q: What is the best way to start our main app from the watch app?
A: Your proposed code is a great way to launch the companion app.

Q: Has something changed in iOS 11/WatchOS 4 regarding to background states?
A: No, especially in regards to timers. The different behavior is likely a correction.

Q: Can I find documentation of this?
A: I can't. Sometimes you can squeeze this information out of apple engineers on the forums or via the code level support questions through your developer account or go to WWDC.

Q: Could this be an iOS bug?
A: The earlier behavior was likely the bug.

Allelomorph answered 6/12, 2017 at 2:40 Comment(0)
Q
1

When app is closed and not running in background then location never track in iOS 11 it's not an iWatchOS 4 and iOS 11 Bug.

Changes to location tracking in iOS 11

follow this documentation link: Changes to location tracking in iOS 11 iOS 11 also makes some major changes to existing APIs. One of the affected areas is location tracking.

If your app only uses location while the app is in the foreground, as most apps do, you might not have to change anything at all; however, if it’s one of those apps that continuously track user’s location throughout the day, you should probably book some time this summer for making some changes in how you do the tracking and testing possible usage scenarios.

For example, let’s consider those two apps again; the continuous background location and the significant location change monitoring app. Suppose the user goes for a run with the continuous background location app. They’ll go on the run, they’ll come back, they’ll see the solid arrow the whole time, and when they look at their map, they’ll see every twist and turn they took. When they install that app that uses significant location change monitoring, they’ll see the same thing, a solid arrow. As far as the user is aware, this app is probably receiving the same amount of information as their run tracking app. So if users are misinterpreting our signals, we decided the best way to fix this was to adjust how we indicate location usage.

watchOS has access to many of the same technologies found in iOS apps; however, even if a technology is available, you may not be able to use it in quite the same way you did on iPhone.

Do not use background execution modes for a technology. In general, Watch apps are considered foreground apps; they run only while the user interacts with one of their interfaces. As a result, the corresponding WatchKit extension cannot take advantage of most background execution modes to perform tasks. might be help this documentation: Leveraging iOS Technologies for watch

Quadragesima answered 1/12, 2017 at 6:18 Comment(1)
I understand the permission changes in iOS 11. We do track user location based on user preference, 'always' or 'in use'. However, I don't think this is relevant to our problem here. Our WatchOS app is a foreground-only app, it is just a switch to 'switch on' our app in the background, when you already gave location permission. The only change I see since iOS 11, is that Timers don't work. Location updates seem to come in like normal.Footpad

© 2022 - 2024 — McMap. All rights reserved.