Watchos: - Principles - Practices
Independent watchOS: - principles - practices
Vadim Drobinin | @valzevul Independent apps
@valzevul / drobinin.com 2 Independent apps. Just put a checkmark?
@valzevul / drobinin.com 3 Independent apps
@valzevul / drobinin.com 4 Questions?
drobinin.com | @valzevul Independent apps. Just put a checkmark? Think different.
@valzevul / drobinin.com 6 Agenda
@valzevul / drobinin.com 7 Agenda
— Back to the roots
@valzevul / drobinin.com 7 Agenda
— Back to the roots — watchOS 1..<6
@valzevul / drobinin.com 7 Agenda
— Back to the roots — watchOS 1..<6 — watchOS 6
@valzevul / drobinin.com 7 Agenda
— Back to the roots — watchOS 1..<6 — watchOS 6 — Principles
@valzevul / drobinin.com 7 Agenda
— Back to the roots — watchOS 1..<6 — watchOS 6 — Principles — Best practices
@valzevul / drobinin.com 7 Agenda
— Back to the roots — watchOS 1..<6 — watchOS 6 — Principles — Best practices — Watch App Store
@valzevul / drobinin.com 7 Agenda
— Back to the roots — watchOS 1..<6 — watchOS 6 — Principles — Best practices — Watch App Store — Summary
@valzevul / drobinin.com 7 https://paleotronic.com/2019/09/28/from-pocket-computers-to-palmtops-an-early-history-of-mobile-telecomputing/ 8 Back to the roots
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses — 16th century: wearable clocks
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses — 16th century: wearable clocks — 1960s: "Beat the Dealer" clock
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses — 16th century: wearable clocks — 1960s: "Beat the Dealer" clock — 1975: calculator wristwatch
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses — 16th century: wearable clocks — 1960s: "Beat the Dealer" clock — 1975: calculator wristwatch — 1984: The Organiser
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses — 16th century: wearable clocks — 1960s: "Beat the Dealer" clock — 1975: calculator wristwatch — 1984: The Organiser — 2002: bluetooth headset
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses — 16th century: wearable clocks — 1960s: "Beat the Dealer" clock — 1975: calculator wristwatch — 1984: The Organiser — 2002: bluetooth headset — 2006-2013: Nike+, Fitbit, Google Glass
@valzevul / drobinin.com 9 Back to the roots
— 13th century: eyeglasses — 16th century: wearable clocks — 1960s: "Beat the Dealer" clock — 1975: calculator wristwatch — 1984: The Organiser — 2002: bluetooth headset — 2006-2013: Nike+, Fitbit, Google Glass — 2014: Apple Watch
@valzevul / drobinin.com 9 Back to the roots
10 Back to the roots
— PDAs
10 Back to the roots
— PDAs — Smartphones
10 Back to the roots
— PDAs — Smartphones — Wearables
10 watchOS 1..<6
Name Apple Watch
Purpose and/or Tell time, track Function fitness
Beneficial Promote Effect(s) health awareness
Harmful Potentially Effect(s) distracting
@valzevul / drobinin.com 11 © apple.com 12 watchOS 6
@valzevul / drobinin.com 13 watchOS 6
— Dedicated App Store
@valzevul / drobinin.com 13 watchOS 6
— Dedicated App Store — New APIs for developers
@valzevul / drobinin.com 13 watchOS 6
— Dedicated App Store — New APIs for developers — Software Updates without iPhone
@valzevul / drobinin.com 13 watchOS 6
— Dedicated App Store — New APIs for developers — Software Updates without iPhone — New watch faces, Noise app
@valzevul / drobinin.com 13 watchOS 6
— Dedicated App Store — New APIs for developers — Software Updates without iPhone — New watch faces, Noise app — Cycle Tracking app, Activity Trends
@valzevul / drobinin.com 13 New APIs for developers
@valzevul / drobinin.com 14 New APIs for developers
— Independent apps
@valzevul / drobinin.com 14 New APIs for developers
— Independent apps — Extended Runtime
@valzevul / drobinin.com 14 New APIs for developers
— Independent apps — Extended Runtime — Streaming audio API
@valzevul / drobinin.com 14 New APIs for developers
— Independent apps — Extended Runtime — Streaming audio API — Sign-in with Apple
@valzevul / drobinin.com 14 New APIs for developers
— Independent apps — Extended Runtime — Streaming audio API — Sign-in with Apple — Core ML and the Neural Engine
@valzevul / drobinin.com 14
Extended Runtime
@valzevul / drobinin.com 16 Extended Runtime
— Provide runtime to complete targeted tasks
@valzevul / drobinin.com 16 Extended Runtime
— Provide runtime to complete targeted tasks — Self Care
@valzevul / drobinin.com 16 Extended Runtime
— Provide runtime to complete targeted tasks — Self Care — Mindfulness
@valzevul / drobinin.com 16 Extended Runtime
— Provide runtime to complete targeted tasks — Self Care — Mindfulness — Physical Therapy
@valzevul / drobinin.com 16 Extended Runtime
— Provide runtime to complete targeted tasks — Self Care — Mindfulness — Physical Therapy — Health Monitoring
@valzevul / drobinin.com 16 Extended Runtime
— Provide runtime to complete targeted tasks — Self Care — Mindfulness — Physical Therapy — Health Monitoring — Alarm
@valzevul / drobinin.com 16 Extended Runtime
— Provide runtime to complete targeted tasks — Self Care — Mindfulness — Physical Therapy — Health Monitoring — Alarm — Preserve battery
@valzevul / drobinin.com 16 Extended Runtime
@valzevul / drobinin.com 17 Extended Runtime
— For non-workout activities
@valzevul / drobinin.com 17 Extended Runtime
— For non-workout activities — Move and Exercise rings not affected
@valzevul / drobinin.com 17 Extended Runtime
— For non-workout activities — Move and Exercise rings not affected — Heart rate sensor off by default
@valzevul / drobinin.com 17 Extended Runtime let runtimeSession = WKExtendedRuntimeSession()
// Must be called while app is active runtimeSession.delegate = self runtimeSession.start() runtimeSession.invalidate()
@valzevul / drobinin.com 18 Extended Runtime let runtimeSession = WKExtendedRuntimeSession()
// Must be called while app is active runtimeSession.delegate = self runtimeSession.start() runtimeSession.invalidate()
@valzevul / drobinin.com 18 Extended Runtime let runtimeSession = WKExtendedRuntimeSession()
// Must be called while app is active runtimeSession.delegate = self runtimeSession.start() runtimeSession.invalidate()
@valzevul / drobinin.com 18 Streaming audio API
@valzevul / drobinin.com 19 Streaming audio API
— HLS (via AVQueuePlayer)
@valzevul / drobinin.com 19 Streaming audio API
— HLS (via AVQueuePlayer) — Custom audio protocols
@valzevul / drobinin.com 19 Streaming audio API
— HLS (via AVQueuePlayer) — Custom audio protocols — Network.framework
@valzevul / drobinin.com 19 Streaming audio API
— HLS (via AVQueuePlayer) — Custom audio protocols — Network.framework — URLSessionStreamingTask from URLSession
@valzevul / drobinin.com 19 Streaming audio API
— HLS (via AVQueuePlayer) — Custom audio protocols — Network.framework — URLSessionStreamingTask from URLSession — URLSessionWebSocketTask (new in iOS 13 and watchOS 6)
@valzevul / drobinin.com 19 Streaming audio API
— HLS (via AVQueuePlayer) — Custom audio protocols — Network.framework — URLSessionStreamingTask from URLSession — URLSessionWebSocketTask (new in iOS 13 and watchOS 6) — AVPlayer and AVQueuePlayer
@valzevul / drobinin.com 19 Sign-in with Apple
@valzevul / drobinin.com 20 Sign-in with Apple
— Use Sign-in with Apple:
@valzevul / drobinin.com 20 Sign-in with Apple
— Use Sign-in with Apple: — WKInterfaceAuthorizationAppleIDButton
@valzevul / drobinin.com 20 Sign-in with Apple
— Use Sign-in with Apple: — WKInterfaceAuthorizationAppleIDButton — AuthenticationServices.framework
@valzevul / drobinin.com 20 Sign-in with Apple
— Use Sign-in with Apple: — WKInterfaceAuthorizationAppleIDButton — AuthenticationServices.framework — Create an app without user accounts
@valzevul / drobinin.com 20 Sign-in with Apple
— Use Sign-in with Apple: — WKInterfaceAuthorizationAppleIDButton — AuthenticationServices.framework — Create an app without user accounts — Create custom form
@valzevul / drobinin.com 20 Core ML and the Neural Engine
@valzevul / drobinin.com 21 Core ML and the Neural Engine
— SoundAnalysis.framework "Use the SoundAnalysis framework to analyze audio and recognize it as a particular type, such as laughter or applause"
@valzevul / drobinin.com 21 Core ML and the Neural Engine
— SoundAnalysis.framework "Use the SoundAnalysis framework to analyze audio and recognize it as a particular type, such as laughter or applause" — MLUpdateTask et al "A task that updates a model with additional training data"
@valzevul / drobinin.com 21 22 Principles
@valzevul / drobinin.com 23 ! Lightweight interactions
@valzevul / drobinin.com 24 ! Holistic design
@valzevul / drobinin.com 25 ! Personal communication
@valzevul / drobinin.com 26 Glanceable
@valzevul / drobinin.com 27 Glanceable — readiliy available information
@valzevul / drobinin.com 27 Glanceable — readiliy available information — no distractions
@valzevul / drobinin.com 27 Glanceable — readiliy available information — no distractions — up-to-date snapshots
@valzevul / drobinin.com 27 Glanceable — readiliy available information — no distractions — up-to-date snapshots — provides a complication
@valzevul / drobinin.com 27 Glanceable — readiliy available information — no distractions — up-to-date snapshots — provides a complication — a custom notification interface
@valzevul / drobinin.com 27 Actionable
@valzevul / drobinin.com 28 Actionable — mindful of the information
@valzevul / drobinin.com 28 Actionable — mindful of the information — what’s onscreen is always current and relevant
@valzevul / drobinin.com 28 Actionable — mindful of the information — what’s onscreen is always current and relevant — refreshes in background
@valzevul / drobinin.com 28 Actionable — mindful of the information — what’s onscreen is always current and relevant — refreshes in background — custom actions in notifications
@valzevul / drobinin.com 28 Responsive
@valzevul / drobinin.com 29 Responsive — interactions are quick
@valzevul / drobinin.com 29 Responsive — interactions are quick — minimizes the launch time
@valzevul / drobinin.com 29 Responsive — interactions are quick — minimizes the launch time — responds with immediate feedback
@valzevul / drobinin.com 29 Best practices
@valzevul / drobinin.com 30 Best practices
@valzevul / drobinin.com 31 Best practices
— Create account / Sign in
@valzevul / drobinin.com 31 Best practices
— Create account / Sign in — Request proper permissions (i.e HealthKit)
@valzevul / drobinin.com 31 Best practices
— Create account / Sign in — Request proper permissions (i.e HealthKit) — Download data directly to the watch
@valzevul / drobinin.com 31 Best practices
— Create account / Sign in — Request proper permissions (i.e HealthKit) — Download data directly to the watch — Send Push Notifications directly to the watch
@valzevul / drobinin.com 31 Best practices
The independent app can’t use WatchConnectivity as its main source of data, and must be capable of accessing information on its own a
a Ensure Your App Runs Independently, developer.apple.com
@valzevul / drobinin.com 32 Extended Runtime (self-care)
@valzevul / drobinin.com 33 Extended Runtime (self-care)
— Focused on health or emotional well-being
@valzevul / drobinin.com 33 Extended Runtime (self-care)
— Focused on health or emotional well-being — Sessions run up to 10 minutes
@valzevul / drobinin.com 33 Extended Runtime (self-care)
— Focused on health or emotional well-being — Sessions run up to 10 minutes — Exiting app ends session
@valzevul / drobinin.com 33 Extended Runtime (mindfulness)
@valzevul / drobinin.com 34 Extended Runtime (mindfulness)
— Silent or near-silent meditation
@valzevul / drobinin.com 34 Extended Runtime (mindfulness)
— Silent or near-silent meditation — Sessions run up to 1 hour
@valzevul / drobinin.com 34 Extended Runtime (mindfulness)
— Silent or near-silent meditation — Sessions run up to 1 hour — Exiting app ends session
@valzevul / drobinin.com 34 Extended Runtime (physical therapy)
@valzevul / drobinin.com 35 Extended Runtime (physical therapy)
— Stretching, strengthening, or range-of-motion exercises
@valzevul / drobinin.com 35 Extended Runtime (physical therapy)
— Stretching, strengthening, or range-of-motion exercises — Continues to run in the background
@valzevul / drobinin.com 35 Extended Runtime (physical therapy)
— Stretching, strengthening, or range-of-motion exercises — Continues to run in the background — Sessions run up to 1 hour
@valzevul / drobinin.com 35 Extended Runtime (physical therapy)
— Stretching, strengthening, or range-of-motion exercises — Continues to run in the background — Sessions run up to 1 hour — Move and Exercise rings not affected
@valzevul / drobinin.com 35 Extended Runtime (health monitoring)
@valzevul / drobinin.com 36 Extended Runtime (health monitoring)
— Requires entitlement
@valzevul / drobinin.com 36 Extended Runtime (health monitoring)
— Requires entitlement — Use respectfully
@valzevul / drobinin.com 36 Extended Runtime (health monitoring)
— Requires entitlement — Use respectfully — Continues in the background
@valzevul / drobinin.com 36 Extended Runtime (alarm)
@valzevul / drobinin.com 37 Extended Runtime (alarm)
— Monitor for the optimal time to play an alarm
@valzevul / drobinin.com 37 Extended Runtime (alarm)
— Monitor for the optimal time to play an alarm — Can be scheduled up to 36 hours in advance
@valzevul / drobinin.com 37 Extended Runtime (alarm)
— Monitor for the optimal time to play an alarm — Can be scheduled up to 36 hours in advance — Sessions run up to 30 minutes
@valzevul / drobinin.com 37 Extended Runtime (alarm)
— Monitor for the optimal time to play an alarm — Can be scheduled up to 36 hours in advance — Sessions run up to 30 minutes — Continues to run in the background
@valzevul / drobinin.com 37 Extended Runtime (alarm)
— Monitor for the optimal time to play an alarm — Can be scheduled up to 36 hours in advance — Sessions run up to 30 minutes — Continues to run in the background — Must call WKInterfaceDevice.play()
@valzevul / drobinin.com 37 Extended Runtime (alarm)
— Monitor for the optimal time to play an alarm — Can be scheduled up to 36 hours in advance — Sessions run up to 30 minutes — Continues to run in the background — Must call WKInterfaceDevice.play() — Supports haptics
@valzevul / drobinin.com 37 Streaming audio API
@valzevul / drobinin.com 38 Streaming audio API
— WKInterfaceDevice.current().supportsAudioStreaming
@valzevul / drobinin.com 38 Streaming audio API
— WKInterfaceDevice.current().supportsAudioStreaming — Caching
@valzevul / drobinin.com 38 Streaming audio API
— WKInterfaceDevice.current().supportsAudioStreaming — Caching — Reachability
@valzevul / drobinin.com 38 Streaming audio API
— WKInterfaceDevice.current().supportsAudioStreaming — Caching — Reachability — Network transitions
@valzevul / drobinin.com 38 Streaming audio API
— WKInterfaceDevice.current().supportsAudioStreaming — Caching — Reachability — Network transitions — Audio Quality
@valzevul / drobinin.com 38 Sign-in
@valzevul / drobinin.com 39 Sign-in
— Support Password Autofill:
@valzevul / drobinin.com 39 Sign-in
— Support Password Autofill: — WKTextContentType (username, password, newPassword, oneTimeCode)
@valzevul / drobinin.com 39 Sign-in
— Support Password Autofill: — WKTextContentType (username, password, newPassword, oneTimeCode) — Set up your app’s associated domains
@valzevul / drobinin.com 39 Sign-in
— Support Password Autofill: — WKTextContentType (username, password, newPassword, oneTimeCode) — Set up your app’s associated domains — Add placeholders
@valzevul / drobinin.com 39 Sign-in
@valzevul / drobinin.com 40 Sign-in
— WKAlertAction for Terms & Conditions
@valzevul / drobinin.com 40 Sign-in
— WKAlertAction for Terms & Conditions — Embed TextField
@valzevul / drobinin.com 40 Sign-in
— WKAlertAction for Terms & Conditions — Embed TextField — Use textContentType
@valzevul / drobinin.com 40 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications import WatchKit import UserNotifications class ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if granted { WKExtension.shared().registerForRemoteNotifications() } else { ... } } } }
@valzevul / drobinin.com 41 Push Notifications func didRegisterForRemoteNotifications(withDeviceToken deviceToken: Data) { /* Forward the token to your provider, using a custom method. */ } func didFailToRegisterForRemoteNotificationsWithError(_ error: Error) { /* Disable remote notification features */ }
@valzevul / drobinin.com 42 Push Notifications func didRegisterForRemoteNotifications(withDeviceToken deviceToken: Data) { /* Forward the token to your provider, using a custom method. */ } func didFailToRegisterForRemoteNotificationsWithError(_ error: Error) { /* Disable remote notification features */ }
@valzevul / drobinin.com 42 Push Notifications func didRegisterForRemoteNotifications(withDeviceToken deviceToken: Data) { /* Forward the token to your provider, using a custom method. */ } func didFailToRegisterForRemoteNotificationsWithError(_ error: Error) { /* Disable remote notification features */ }
@valzevul / drobinin.com 42 Push Notifications func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler: @escaping (WKBackgroundFetchResult) -> Void) { // handle it // ... fetchCompletionHandler(.newData) }
@valzevul / drobinin.com 43 Push Notifications func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler: @escaping (WKBackgroundFetchResult) -> Void) { // handle it // ... fetchCompletionHandler(.newData) }
@valzevul / drobinin.com 43 Push Notifications func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler: @escaping (WKBackgroundFetchResult) -> Void) { // handle it // ... fetchCompletionHandler(.newData) }
@valzevul / drobinin.com 43 Push Notifications func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler: @escaping (WKBackgroundFetchResult) -> Void) { // handle it // ... fetchCompletionHandler(.newData) }
@valzevul / drobinin.com 43 Push Notifications func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler: @escaping (WKBackgroundFetchResult) -> Void) { // handle it // ... fetchCompletionHandler(.newData) }
@valzevul / drobinin.com 43 Watch App Store
@valzevul / drobinin.com 44 ✍ Text Optimization
@valzevul / drobinin.com 45 ! Graphics Optimization
@valzevul / drobinin.com 46 ⭐ Rating and Reviews
@valzevul / drobinin.com 47 ! Ge!ing Featured
@valzevul / drobinin.com 48 Summary
@valzevul / drobinin.com 49 Summary
— Apple Watch ≠ iPhone's extension
@valzevul / drobinin.com 49 Summary
— Apple Watch ≠ iPhone's extension — Check out the new APIs
@valzevul / drobinin.com 49 Summary
— Apple Watch ≠ iPhone's extension — Check out the new APIs — Extended Runtime is very powerful
@valzevul / drobinin.com 49 Summary
— Apple Watch ≠ iPhone's extension — Check out the new APIs — Extended Runtime is very powerful — Think about ASO
@valzevul / drobinin.com 49 Not enough?
@valzevul / drobinin.com 50 Not enough?
— Streaming Audio on watchOS 6, session 716
@valzevul / drobinin.com 50 Not enough?
— Streaming Audio on watchOS 6, session 716 — Extended Runtime for watchOS Apps, session 251
@valzevul / drobinin.com 50 Not enough?
— Streaming Audio on watchOS 6, session 716 — Extended Runtime for watchOS Apps, session 251 — Creating Independent Watch Apps, session 208
@valzevul / drobinin.com 50 Questions?
drobinin.com | @valzevul