Siri Shortcuts - Swift Heroes, Torino, Italy, November 2018

Siri Shortcuts - Swift Heroes, Torino, Italy, November 2018

Updated talk on the history of Shortcuts, my co-worker named Siri, and the new iOS 12 APIs you can use to start creating your own (semi-limited) shortcuts.

Now with way more info about configuring responses, setting up an intents UI extension, and some crazy things one developer has done to try to bend Intent-powered shortcuts to his will.

Sample code available at https://github.com/designatednerd/TravelPlanner

C4861b1dfdf3bbb21faec4a1acdf183d?s=128

Ellen Shapiro

November 09, 2018
Tweet

Transcript

  1. SIRI SHORTCUTS SWIFT HEROES | TORINO, ITALY | NOVEMBER 2018

    @DESIGNATEDNERD | BAKKENBAECK.COM | JUSTHUM.COM
  2. !

  3. None
  4. "HEY SIRI!"

  5. Photo illustration via http://thetechnews.com/2016/12/07/not-only-samsung-iphone-explodes-too/

  6. Photo via [http://www.gizmodo.co.uk/2013/07/apparently-people-hated-the-iphone-5-the-most/

  7. None
  8. ! " #

  9. None
  10. None
  11. None
  12. MACSTORIES

  13. MACSTORIES

  14. None
  15. None
  16. None
  17. None
  18. None
  19. WORKFLOW -> SHORTCUTS

  20. None
  21. [https://twitter.com/AdamFootUK/status/1028760230304333824]

  22. None
  23. https://twitter.com/bpmarkowitz/status/1015303601839837186

  24. https://twitter.com/bpmarkowitz/status/1015303601839837186

  25. https://twitter.com/davedelong/status/1015445465062502400

  26. https://twitter.com/AdrianEves07/status/1017032252629319680

  27. https://twitter.com/AdrianEves07/status/1017032252629319680

  28. https://twitter.com/AdrianEves07/status/1017032252629319680

  29. https://twitter.com/AdrianEves07/status/1017032252629319680

  30. None
  31. https://twitter.com/thomaswood/status/1017428845748768769]

  32. None
  33. WHAT ABOUT MY APP?

  34. None
  35. None
  36. 1. CREATE SHORTCUT

  37. 2. DONATE SHORTCUT TO THE SYSTEM FOR LATER USE

  38. None
  39. 3. HANDLE SHORTCUT

  40. CREATING A SHORTCUT

  41. TEH CODEZ! https://github.com/designatednerd TravelPlanner

  42. EASY MODE: NSUserActivity

  43. NSUserActivity

  44. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  45. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  46. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  47. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  48. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  49. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  50. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  51. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity userActivity.becomeCurrent()
  52. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity // IMPORTANT userActivity.becomeCurrent()
  53. NSUserActivity SETUP let userActivity = NSUserActivity(activityType: "com.yourco.YourApp.ActivityType") userActivity.isEligibleForSearch = true

    userActivity.isEligibleForPrediction = true userActivity.title = "View \(trip.name ?? "") trip" userActivity.suggestedInvocationPhrase = "View \(trip.name ?? "") trip" userActivity.requiredUserInfoKeys = [ UserActivityInfoKey.trip.rawValue ] userActivity.userInfo = [ UserActivityInfoKey.trip.rawValue: trip.id ] userActivity.persistentIdentifier = trip.id viewController.userActivity = userActivity // IMPORTANT userActivity.becomeCurrent()
  54. None
  55. None
  56. None
  57. None
  58. ! SPOTLIGHT

  59. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = "View details for your trip to \(destination)" } attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  60. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = "View details for your trip to \(destination)" } attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  61. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = "View details for your trip to \(destination)" } attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  62. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = "View details for your trip to \(destination)" } attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  63. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = "View details for your trip to \(destination)" } // TODO: Y U NO WORK?! attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  64. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = "View details for your trip to \(destination)" } // TODO: Y U NO WORK?! attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  65. None
  66. None
  67. DON'T TRY TO CRAM WAY TOO MUCH REPETITIVE INFORMATION INTO

    THE CONTENT DESCRIPTION
  68. DON'T TRY TO CRAM WAY TOO MUCH REPETITIVE INFORMATION INTO

    THE CONTENT DESCRIPTION ⬆ (LIKE THIS)
  69. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = "\(destination) || \(trip.formattedDateInterval)" } attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  70. None
  71. SPOTLIGHT SETUP let attributes = CSSearchableItemAttributeSet (itemContentType: kUTTypeContent as String)

    if let destination = trip.destination { attributes.contentDescription = """ ! \(destination) " \(trip.formattedTripInterval) """ } attributes.thumbnailData = UIImage(named: "trip")?.pngData() userActivity.contentAttributeSet = attributes
  72. None
  73. HANDLING SHORTCUT SELECTION

  74. UIApplicationDelegate

  75. UIApplicationDelegate SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler:

    @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == "com.yourco.YourApp.ActivityType" else { return false } let handlingVC = // Omitted for length restorationHandler(handlingVC) return true }
  76. UIApplicationDelegate SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler:

    @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == "com.yourco.YourApp.ActivityType" else { return false } let handlingVC = // Omitted for length restorationHandler(handlingVC) return true }
  77. UIApplicationDelegate SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler:

    @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == "com.yourco.YourApp.ActivityType" else { return false } let handlingVC = // Omitted for length restorationHandler(handlingVC) return true }
  78. UIApplicationDelegate SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler:

    @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == "com.yourco.YourApp.ActivityType" else { return false } let handlingVC = // Omitted for length restorationHandler(handlingVC) return true }
  79. None
  80. HARD MODE: Intents FRAMEWORK

  81. None
  82. None
  83. INIntent + INInteraction

  84. None
  85. None
  86. None
  87. None
  88. None
  89. None
  90. None
  91. None
  92. None
  93. None
  94. None
  95. None
  96. None
  97. None
  98. None
  99. DONATING AN INTERACTION let arrivalIntent = ArrivalTimeIntent() arrivalIntent.origin = plan.originName

    arrivalIntent.destination = plan.destinationName arrivalIntent.suggestedInvocationPhrase = "\(plan.destinationName) arrival" let arrivalInteraction = INInteraction(intent: arrivalIntent, response: nil) arrivalInteraction.donate { error in if let error = error { debugPrint("Error donating arrival intent: \(error)") } }
  100. DONATING AN INTERACTION let arrivalIntent = ArrivalTimeIntent() arrivalIntent.origin = plan.originName

    arrivalIntent.destination = plan.destinationName arrivalIntent.suggestedInvocationPhrase = "\(plan.destinationName) arrival" let arrivalInteraction = INInteraction(intent: arrivalIntent, response: nil) arrivalInteraction.donate { error in if let error = error { debugPrint("Error donating arrival intent: \(error)") } }
  101. DONATING AN INTERACTION let arrivalIntent = ArrivalTimeIntent() arrivalIntent.origin = plan.originName

    arrivalIntent.destination = plan.destinationName arrivalIntent.suggestedInvocationPhrase = "\(plan.destinationName) arrival" let arrivalInteraction = INInteraction(intent: arrivalIntent, response: nil) arrivalInteraction.donate { error in if let error = error { debugPrint("Error donating arrival intent: \(error)") } }
  102. None
  103. None
  104. None
  105. None
  106. DONATING AN INTERACTION let arrivalIntent = ArrivalTimeIntent() arrivalIntent.origin = plan.originName

    arrivalIntent.destination = plan.destinationName arrivalIntent.suggestedInvocationPhrase = "\(plan.destinationName) arrival" let arrivalInteraction = INInteraction(intent: arrivalIntent, response: nil) arrivalInteraction.donate { error in if let error = error { debugPrint("Error donating arrival intent: \(error)") } }
  107. DONATING AN INTERACTION let arrivalIntent = ArrivalTimeIntent() arrivalIntent.origin = plan.originName

    arrivalIntent.destination = plan.destinationName arrivalIntent.suggestedInvocationPhrase = "\(plan.destinationName) arrival" let arrivalInteraction = INInteraction(intent: arrivalIntent, response: nil) arrivalInteraction.donate { error in if let error = error { debugPrint("Error donating arrival intent: \(error)") } }
  108. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let arrivalIntent = intent as? ArrivalTimeIntent { guard let plan = Plan.fromArrivalIntent(arrivalIntent, in: CoreDataManager.shared.mainContext) else { return false } coordinator.viewPlan(plan) return true } // Else, go through the rest of method we saw earlier }
  109. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let arrivalIntent = userActivity.interaction?.intent as? ArrivalTimeIntent { guard let plan = Plan.fromArrivalIntent(arrivalIntent, in: CoreDataManager.shared.mainContext) else { return false } coordinator.viewPlan(plan) return true } // Else, go through the rest of method we saw earlier }
  110. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let arrivalIntent = userActivity.interaction?.intent as? ArrivalTimeIntent { guard let plan = Plan.fromArrivalIntent(arrivalIntent, in: CoreDataManager.shared.mainContext) else { return false } coordinator.viewPlan(plan) return true } // Else, go through the rest of method we saw earlier }
  111. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let arrivalIntent = userActivity.interaction?.intent as? ArrivalTimeIntent { guard let plan = Plan.fromArrivalIntent(arrivalIntent, in: CoreDataManager.shared.mainContext) else { return false } coordinator.viewPlan(plan) return true } // Else, go through the rest of method we saw earlier }
  112. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let arrivalIntent = userActivity.interaction?.intent as? ArrivalTimeIntent { guard let plan = Plan.fromArrivalIntent(arrivalIntent, in: CoreDataManager.shared.mainContext) else { return false } coordinator.viewPlan(plan) return true } // Else, go through the rest of method we saw earlier }
  113. EXPERT MODE: RESPONSES + INTENT EXTENSION

  114. None
  115. INTENT HANDLER SETUP public class ArrivalTimeIntentHandler: NSObject, ArrivalTimeIntentHandling { //

    Optional! public func confirm(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { // Figure out if the thing can be done } public func handle(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { // Do the thing! completion(.success) } }
  116. INTENT HANDLER SETUP public class ArrivalTimeIntentHandler: NSObject, ArrivalTimeIntentHandling { //

    Optional! public func confirm(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { // Figure out if the thing can be done } public func handle(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { // Do the thing! completion(.success) } }
  117. INTENT HANDLER SETUP public class ArrivalTimeIntentHandler: NSObject, ArrivalTimeIntentHandling { //

    Optional! public func confirm(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { // Figure out if the thing can be done } public func handle(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { // Do the thing! completion(.success) } }
  118. None
  119. None
  120. None
  121. INTENT HANDLER SETUP public class ArrivalTimeIntentHandler: NSObject, ArrivalTimeIntentHandling { public

    func handle(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { guard let plan = Plan.fromArrivalIntent(intent, in: CoreDataManager.shared.mainContext) else { if let destination = intent.destination { completion(.failureNoFuturePlansToDestination(destination)) } else { completion(ArrivalTimeIntentResponse(code: .failureNoDestination, userActivity: nil)) } return } completion(.success(destination: plan.destinationName, method: plan.methodName, origin: plan.originName, time: plan.formattedEndTime, date: .formattedEndDate)) } }
  122. INTENT HANDLER SETUP public class ArrivalTimeIntentHandler: NSObject, ArrivalTimeIntentHandling { public

    func handle(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { guard let plan = Plan.fromArrivalIntent(intent, in: CoreDataManager.shared.mainContext) else { if let destination = intent.destination { completion(.failureNoFuturePlansToDestination(destination)) } else { completion(ArrivalTimeIntentResponse(code: .failureNoDestination, userActivity: nil)) } return } completion(plan.toArrivalIntentResponse) } }
  123. None
  124. INTENT HANDLER SETUP public class ArrivalTimeIntentHandler: NSObject, ArrivalTimeIntentHandling { public

    func handle(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { guard let plan = Plan.fromArrivalIntent(intent, in: CoreDataManager.shared.mainContext) else { if let destination = intent.destination { completion(.failureNoFuturePlansToDestination(destination)) } else { completion(ArrivalTimeIntentResponse(code: .failureNoDestination, userActivity: nil)) } return } completion(plan.toArrivalIntentResponse) } }
  125. INTENT HANDLER SETUP public class ArrivalTimeIntentHandler: NSObject, ArrivalTimeIntentHandling { public

    func handle(intent: ArrivalTimeIntent, completion: @escaping (ArrivalTimeIntentResponse) -> Void) { guard let plan = Plan.fromArrivalIntent(intent, in: CoreDataManager.shared.mainContext) else { if let destination = intent.destination { completion(.failureNoFuturePlansToDestination(destination)) } else { completion(ArrivalTimeIntentResponse(code: .failureNoDestination, userActivity: nil)) } return } completion(plan.toArrivalIntentResponse) } }
  126. APP GROUPS AND SHARED CONTAINERS

  127. THINGS TO MOVE TO A CONTAINER > Core Data >

    Keychain > User Defaults > Data Caching
  128. SETUP INSTRUCTIONS

  129. SETUP INSTRUCTIONS ¯\_(ϑ)_/¯

  130. None
  131. None
  132. GALAXY BRAIN MODE: IntentsUI EXTENSION

  133. None
  134. None
  135. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  136. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  137. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  138. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  139. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  140. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  141. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  142. INTENT UI SETUP func configureView(for parameters: Set<INParameter>, of interaction: INInteraction,

    interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  143. None
  144. func configureView(for parameters: Set<INParameter>, of interaction: INInteraction, interactiveBehavior: INUIInteractiveBehavior, context:

    INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { var plan: Plan? = nil let moc = CoreDataManager.shared.mainContext if let departureIntent = interaction.intent as? DepartureTimeIntent { plan = Plan.fromDepartureIntent(departureIntent, in: moc) } else if let arrivalIntent = interaction.intent as? ArrivalTimeIntent { plan = Plan.fromArrivalIntent(arrivalIntent, in: moc) } guard let retrievedPlan = plan else { completion(false, parameters, .zero) return } let coordinator = EmbeddedPlanCoordinator(plan: retrievedPlan) coordinator.configureIn(viewController: self) completion(true, parameters, self.desiredSize) } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize }
  145. None
  146. func configureView(for parameters: Set<INParameter>, of interaction: INInteraction, interactiveBehavior: INUIInteractiveBehavior, context:

    INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { var plan: Plan? = nil let moc = CoreDataManager.shared.mainContext if let departureIntent = interaction.intent as? DepartureTimeIntent { plan = Plan.fromDepartureIntent(departureIntent, in: moc) } else if let arrivalIntent = interaction.intent as? ArrivalTimeIntent { plan = Plan.fromArrivalIntent(arrivalIntent, in: moc) } guard let retrievedPlan = plan else { completion(false, parameters, .zero) return } let coordinator = EmbeddedPlanCoordinator(plan: retrievedPlan) coordinator.configureIn(viewController: self) let size = CGSize(width: self.view.frame.width, height: coordinator.contentHeight) completion(true, parameters, size) }
  147. None
  148. None
  149. None
  150. None
  151. None
  152. func configureView(for parameters: Set<INParameter>, of interaction: INInteraction, interactiveBehavior: INUIInteractiveBehavior, context:

    INUIHostedViewContext, completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void) { var plan: Plan? = nil let moc = CoreDataManager.shared.mainContext if let departureIntent = interaction.intent as? DepartureTimeIntent { plan = Plan.fromDepartureIntent(departureIntent, in: moc) } else if let arrivalIntent = interaction.intent as? ArrivalTimeIntent { plan = Plan.fromArrivalIntent(arrivalIntent, in: moc) } guard let retrievedPlan = plan else { completion(false, parameters, .zero) return } let coordinator = EmbeddedPlanCoordinator(plan: retrievedPlan) coordinator.configureIn(viewController: self) let size = CGSize(width: self.view.frame.width, height: coordinator.contentHeight + 15) completion(true, parameters, size) }
  153. None
  154. None
  155. None
  156. None
  157. None
  158. None
  159. None
  160. None
  161. ! CARROT MODE: LOL CLIPBOARD WORKAROUNDS

  162. None
  163. None
  164. None
  165. None
  166. None
  167. None
  168. None
  169. None
  170. None
  171. UX FOR VOICE

  172. VoiceX

  173. VoiceX (TOO ELON MUSK)

  174. VUX

  175. VUX (SOUNDS TOO MUCH LIKE VOX)

  176. VX

  177. VX (IS NERVE GAS)

  178. UX FOR VOICE

  179. KEEP SUGGESTED INVOCATION PHRASES SHORT AND SIMPLE

  180. HEY SIRI, WHEN IS MY NEXT FLIGHT?

  181. None
  182. HEY SIRI, WHEN IS MY NEXT FLIGHT?

  183. WHEN IS MY NEXT FLIGHT?

  184. WHEN IS MY NEXT FLIGHT?

  185. WHEN IS MY NEXT FLIGHT?

  186. NEXT FLIGHT

  187. OBLIGATORY SUMMARY SLIDE

  188. OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation
  189. OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited
  190. OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited > Use NSUserActivity for basic shortcuts
  191. OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited > Use NSUserActivity for basic shortcuts > Use Custom Intents for complex shortcuts
  192. OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited > Use NSUserActivity for basic shortcuts > Use Custom Intents for complex shortcuts > Keep titles / invocation phrases short
  193. OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited > Use NSUserActivity for basic shortcuts > Use Custom Intents for complex shortcuts > Keep titles / invocation phrases short > Custom responses handle errors clearly
  194. OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited > Use NSUserActivity for basic shortcuts > Use Custom Intents for complex shortcuts > Keep titles / invocation phrases short > Custom responses handle errors clearly > Responses + Intent UI = users
  195. LINKS! > Siri Shortcuts ebook by Mohammad Azam https://gumroad.com/l/eHKUm/ SIRIshortcuts

    > A Beginner's Guide to developing custom intent siri shortcuts for iOS 12 by Peter Minarik https://medium.com/@pietropizzi/ a-beginners-guide-to-developing- custom-intent-siri-shortcuts-for- ios-12-a3627b7011af
  196. WWDC VIDEOS! > Session 211: Introduction to Siri Shortcuts https://developer.apple.com/

    videos/play/wwdc2018/211/ > Session 214: Building for Voice with Siri Shortcuts https://developer.apple.com/ videos/play/wwdc2018/214/
  197. MOAR WWDC VIDEOS! > Session 217: Siri Shortcuts On The

    Watch Face https://developer.apple.com/ videos/play/wwdc2018/217/ > Session 404: New Localization Workflows in Xcode 10 https://developer.apple.com/ videos/play/wwdc2018/404/
  198. MACSTORIES LINKS! > Shortcuts: A New Vision for Siri and

    iOS Automation https://www.macstories.net/ stories/shortcuts-a-new-vision- for-siri-and-ios-automation/ > Original Workflow Review https://www.macstories.net/ reviews/workflow-review- integrated-automation-for-ios-8/
  199. EXCAVATED LINKS! > Original Workflow Teaser video https://www.youtube.com/watch? v=fSh6Q0F4d9Q