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?
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.
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
Related
My watchOS app uses workout API in order to stay running while the app goes to background. The issue is that WCSession becomes unreachable when the app is in background. However, I'm able to run my code and on some condition, it needs to send a message to the iPhone counterpart app.
The specifics of the app require that user doesn't have to interact with it - if there is a timeout, the watch app should send the message to the phone automatically.
Is this possible to achieve? Thanks.
I believe the handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) method is what you're looking for. Documentation Link
Without seeing your code I can't be sure what your current progress is, but I have used this method to update watchOS Complications in the background as the user's location changes.
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
if WKExtension.shared().applicationState == .background {
// Do your background work here.
if let watchComplication = task as? WKWatchConnectivityRefreshBackgroundTask {
pendingConnectivityTasks.append(watchComplication)
}
}
task.setTaskCompletedWithSnapshot(true)
}
completePendingConnectivityTasksIfNeeded()
}
As a side note I will add if your app is not an actual workout app, it will get rejected during App Review for using a HealthKit workout session.
I have an application which I was testing on iOS 10.3 since few days and it was working fine. I recently tested it on an iOS 12 device and it was not working as expected. The application was not able to connect to my server.
Upon investigation, I found that the "Mobile Data" switch was turned off for my application in Settings -> AppName. After turning it on, it started working perfectly.
So, given this scenario, is there a way I can determine the status of this switch from my code? If I can know the status and if it's off, I can redirect the user to the application setting using:
let urlObj = NSURL.init(string:UIApplicationOpenSettingsURLString)
if #available(iOS 10.0, *) {
UIApplication.shared.open(urlObj as! URL, options: [ : ], completionHandler: { Success in
})
} else {
let success = UIApplication.shared.openURL(url as URL)
print("Open \(url): \(success)")
}
P.S: I am not looking for a solution using Reachability as it is not completely reliable.
There is no way to check this setting in the latest iOS release. You have two options to deal with this: first is you check if device is not connected to WiFi, and also not in airplane mode. If server can not be reached it’s safe to assume that data is disabled.
Second option is just to present user errors saying the application is unable to reach the server and to change UI/notice in application accordingly to deal with any scenario where a network connection can not be reached.
CTCellularData is a potential option however as of iOS 11 has some down falls of how often the state is checked.
I am working on app where there is an MQTT connection with the server and server is sending some values related to device and UI changes accordingly. But when app is in background user should get local notification that certain values are changed. I know background service are not allowed in iOS but I want to make sure that is that there is no way to achieve this.
I successfully added local notification with app in background by UIApplication.shared.beginBackgroundTask but it's only work for 3 min exact after that apple terminates the app.
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
print("Background task ended.")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
And just calling self.registerBackgroundTask() makes the app runnable in background for three min.
Next that I am going to try is that background fetch and widget to run service, Here I just want some suggestion that is there any chance that one of above two will work ?
It sounds like "Communicating with an External Accessory" would be the background mode that fits your application.
See Apple Docs for reference.
You have to activate Backround Mode for your project and set the value to "external-accessory". Then you can do ongoing small downloads in background. Apple mentions heart rate monitors as an example.
Please note that continous background polling is waste of energy and would deplete battery quickly. Check if this is really needed for your application. If the user just needs infrequent notifications/alarms, remote notifictions would be a much better solution. I use remote notifications in my own projects and it works very smooth and reliable. Additional benefit is, that it would wake up an app even if the user has closed it before.
For more than 3 Minute. You will be enable any mode. Otherwise when app will enter in background app. After 3 min.App will not perform any action.
I want to develop an app that detecting the user's moving way (walking, cycling, driving etc...) and send a specific UILocalNotification for each activity type.
My question is: is it possible to detect it on the background (when the app is completely closed) without draining the device's battery? What will be the best way to do it?
Thank you!
There is coprocessor m7(+) in iPhones upper 5s.
It gives you possibility to get device motion.
Just
import CoreMotion
in your file.
Create a CMMotionActivityManager object:
let motionActivityManager = CMMotionActivityManager()
Check if it`s available on your device:
motionActivityManager.isActivityAvailable()
Use this method:
motionActivityManager.startActivityUpdates(to: OperationQueue.main) { (activity) in
if (activity?.automotive)! {
print("User using car")
}
if (activity?.cycling)! {
print("User is cycling")
}
if (activity?.running)! {
print("User is running")
}
if (activity?.walking)! {
print("User is walking")
}
if (activity?.stationary)! {
print("User is standing")
}
if (activity?.unknown)! {
print("Unknown activity")
}
}
It would return you types of user activity.
Regarding the user activity which can be handled in background tasks are the below once which does not mention about (walking, cycling,driving etc...)
Implementing Long-Running Background Tasks
For tasks that require more execution time to implement, you must request specific permissions to run them in the background without their being suspended. In iOS, only specific app types are allowed to run in the background:
Apps that play audible content to the user while in the background,
such as a music player app
Apps that record audio content while in the background.
Apps that keep users informed of their location at all times, such as
a navigation app Apps that support Voice over Internet Protocol
(VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
Yes it´s possible to do that!
If your iOS app must keep monitoring location even while it’s in the
background, use the standard location service and specify the location
value of the UIBackgroundModes key to continue running in the
background and receiving location updates. (In this situation, you
should also make sure the location manager’s
pausesLocationUpdatesAutomatically property is set to YES to help
conserve power.) Examples of apps that might need this type of
location updating are fitness or turn-by-turn navigation apps.
Read more here.
How do I trigger a UILocalNotification from the iPhone which would have no alert but only play a sound / haptic feedback on the Apple Watch?
Further background:
I am making a watchOS2 timer app. I use a UIlocalNotification triggered by the iPhone to tell the user that end of timer is reached (to handle the scenario where the watch is not active).
The problem is that Apple uses its own logic to determine if a notification appears on the watch or the phone. If I trigger a notification with no alert but only sound, this notification always plays on phone, never on the watch.
I'd prefer that notification sound/haptic to play on the watch. How can I achieve this?
The downside of what you're asking:
A sound-only notification on the watch would be confusing.
Without a message associated with the sound, the user wouldn't see any reason for the notification, when they glanced at their watch.
The downside of how to currently do what you're asking:
The WKInterfaceDevice documentation points out Apple's intention for playing haptic feedback:
You can also use this object to play haptic feedback when your app is active.
What follows is a misuse of the API to accomplish something it wasn't intended to do. It's fragile, potentially annoying, and may send users in search of a different timer app.
Changes for iOS 10 prevent this from working, unless your complication is on the active watch face.
How you could currently do what you're asking in watchOS 2:
To provide haptic feedback while your app is not active, you'd need a way for the watch extension to immediately wake up in the background, to provide the feedback:
WKInterfaceDevice.currentDevice().playHaptic(.Notification)
To do this, you could misuse the WCSession transferCurrentComplicationUserInfo method. Its proper use is to immediately transfer complication data from the phone to the watch (that a watch face complication might be updated). As a part of that process, it wakes the watch extension in the background to receive its info.
In your phone's timerDidFire method:
After checking that you have a valid Watch Connectivity session with the watch, use transferCurrentComplicationUserInfo to immediately send a dictionary to the watch extension.
guard let session = session where session.activationState == .Activated && session.paired && session.watchAppInstalled else { // iOS 9.3
return
}
let hapticData = ["hapticType": 0] // fragile WKHapticType.Notification.rawValue
// Every time you misuse an API, an Apple engineer dies
session.transferCurrentComplicationUserInfo(hapticData)
As shown, the dictionary could contain a key/value pair specifying the type of haptic feedback, or simply hold a key indicating that the watch should play a hardcoded notification.
Using an internal raw value is fragile, since it may change. If you do need to specify a specific haptic type from the phone, you should setup an enum instead of using a magic number.
In the watch extension's session delegate:
watchOS will have woken your extension in preparation to receive the complication user info. Here, you'd play the haptic feedback, instead of updating a complication, as would be expected.
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let hapticTypeValue = userInfo["hapticType"] as? Int, hapticType = WKHapticType(rawValue: hapticTypeValue) {
WKInterfaceDevice.currentDevice().playHaptic(hapticType)
}
}
The proper solution:
Request that Apple provide a way to schedule local notifications on the watch. Apple's timer app does this already.
You may also wait to see what is announced at WWDC 2016, to decide if any new functionality available in watchOS 3 would help to create a proper standalone watch timer app.