Search code examples
ioswatchkituilocalnotificationwatchos-2swift2

LocalNotification with only sound and haptic on watchOS2


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?


Solution

  • 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.