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.
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.
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
Having trouble figuring out how to update my watch and iOS apps for watchOS 2.2 in order to support multiple watches.
I know there are new functions that must be implemented primarily on the iOS app side, but also on the watch extension according to the Developer Library:
session:activationDidCompleteWithState:error:
sessionDidBecomeInactive:
sessionDidDeactivate:
I'm not really sure how to do it and what code these functions should run.
An iPhone running iOS 9.3 or later may pair with more than one Apple Watch running watchOS 2.2 or later.
All three WCSessionDelegate methods are required on iOS to support asynchronous session activation required for quick watch switching.
Switching from a watch:
When automatic switching is enabled, and the user switches from one Apple Watch to another, the iOS app moves to the inactive and deactivated states during a switch.
Moving to the inactive state gives the session a small amount of time to deliver any data that has already been received.
// MARK: WCSessionDelegate - Asynchronous Activation
// The next 3 methods are required in order to support asynchronous session activation; required for quick watch switching.
func sessionDidBecomeInactive(session: WCSession) { // iOS only
/*
The session calls this method when it detects that the user has
switched to a different Apple Watch. While in the inactive state,
the session delivers any pending data to your delegate object and
prevents you from initiating any new data transfers. After the last
transfer finishes, the session moves to the deactivated state.
Use this method to update any private data structures that might be
affected by the impending change to the active Apple Watch. For example,
you might clean up data structures and close files related to
outgoing content.
*/
print("session did become inactive")
}
As soon as that data is delivered, the session moves to the deactivated state. At that point, the iOS app must call the activateSession method again to connect to the newly active watch.
func sessionDidDeactivate(session: WCSession) { // iOS only
print("session did deactivate")
/*
The session calls this method when there is no more pending data
to deliver to your app and the previous session can be formally closed.
iOS apps that process content delivered from their Watch Extension
should finish processing that content, then call activateSession()
to initiate a session with the new Apple Watch.
*/
// Begin the activation process for the new Apple Watch
WCSession.defaultSession().activateSession()
}
Switching to a watch:
Both iOS and watchOS apps would implement the following method to be called once their WCSession is activated:
func session(session: WCSession, activationDidCompleteWithState activationState: WCSessionActivationState, error: NSError?) {
if let error = error {
print("session activation failed with error: \(error.localizedDescription)")
return
}
/*
Called when the activation of a session finishes. Your implementation
should check the value of the activationState parameter to see if
communication with the counterpart app is possible. When the state is
WCSessionActivationStateActivated, you may communicate normally with
the other app.
*/
print("session activated with state: \(activationState.rawValue)")
}
Sample Code:
Apple has provided QuickSwitch sample code to demonstrate proper use of the WatchConnectivity framework in order to support quick watch switching with multiple Apple Watches. It uses updateApplicationContext to pass a designator and color from a paired watch to the phone.
Other notes:
An unconnected watch app can continue to use all transfer methods including interactive messaging (although outgoing data does get queued by the system, and is not transferred until the user switches back to that watch).
For further details, see How can watchOS 2.2 app determine if its paired iPhone has switched to another Apple Watch?
Credits:
Provided code is based on QuickSwitch, and details found in the WCSessionDelegate Protocol Reference, and WCSession Class Reference, under Supporting Communication with Multiple Apple Watches.
We are using Google Analytics, and want to know how many of our users are in possession of an AppleWatch. I have searched Stack for answers, and the recurring answer is to use this:
if WCSession.isSupported() { // check if the device support to handle an Apple Watch
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession() // activate the session
if session.paired { // Check if the iPhone is paired with the Apple Watch
// Do stuff
}
}
The problem with this is that it prompts the user to 'accept' the app on the AppleWatch. Granted, the if-statement is true wether or not the user accepts, but I don't want the user to get their hopes up, thinking the app supports AppleWatch. I simply want to know if the user has an AppleWatch, I don't want to use it. Yet.
Is there a property on the iOS-device that can be accessed to show if the user has or ever had an AppleWatch connected, without prompting the user through the Watch?
Probably you can use (Push)Notifications, since there is no need to develop a native applewatch app to receive notifications on the watch. For example i receive "whats app messages" on my watch, but it does not have any native app on the watch either.
In your watchkit extension ExtensionDelegate.m you can handle provided answers to the push message separately. Link to Apple
This would be a(nother) approach, where you have to be creative!
I have an application that can detect accidents, it is really important for us to alert users using vibration and alert sound if an accident is detected.
My questions are:
Is it possible to add long vibration in application by using custom sounds or something that apple might be ok with?
If I use private apis, is it possible to convince apple to approve my app considering that the use case is really critical?
Two questions here, really.
1st one: haptic feedback / control vibration on iOS / custom iOS patterns
No, it can't be done, even in the new Apple Watch without Jailbreaking your phone. You can have a look at this keyboard mod (needs Jailbreak). Here you have some code but it needs Jailbreaking your phone.
If you need to alert your users I recommend just playing the default vibration inside a while. Sleep the current thread for 1 sec, then vibrate again until some boolean flag changes. Objective-C Pseudocode:
while (!endVibrationAlert) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
[NSThread sleepForTimeInterval:1];
}
// when user touches some button to dismiss alert
self.endVibrationAlert = true;
2nd one: never ever try to bypass Apple's reviewing system. Abide by the rules. For your 1st version, maybe it passes. Then, in the next update your App can get rejected.