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

    SIRI SHORTCUTS SWIFT HEROES | TORINO, ITALY | NOVEMBER 2018

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

    !

  3. 3.
  4. 7.
  5. 8.
  6. 9.
  7. 10.
  8. 11.
  9. 14.
  10. 15.
  11. 16.
  12. 17.
  13. 18.
  14. 20.
  15. 22.
  16. 30.
  17. 32.
  18. 34.
  19. 35.
  20. 38.
  21. 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()
  22. 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()
  23. 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()
  24. 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()
  25. 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()
  26. 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()
  27. 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()
  28. 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()
  29. 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()
  30. 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()
  31. 54.
  32. 55.
  33. 56.
  34. 57.
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 65.
  42. 66.
  43. 68.

    DON'T TRY TO CRAM WAY TOO MUCH REPETITIVE INFORMATION INTO

    THE CONTENT DESCRIPTION ⬆ (LIKE THIS)
  44. 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
  45. 70.
  46. 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
  47. 72.
  48. 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 }
  49. 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 }
  50. 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 }
  51. 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 }
  52. 79.
  53. 81.
  54. 82.
  55. 84.
  56. 85.
  57. 86.
  58. 87.
  59. 88.
  60. 89.
  61. 90.
  62. 91.
  63. 92.
  64. 93.
  65. 94.
  66. 95.
  67. 96.
  68. 97.
  69. 98.
  70. 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)") } }
  71. 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)") } }
  72. 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)") } }
  73. 102.
  74. 103.
  75. 104.
  76. 105.
  77. 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)") } }
  78. 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)") } }
  79. 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 }
  80. 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 }
  81. 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 }
  82. 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 }
  83. 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 }
  84. 114.
  85. 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) } }
  86. 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) } }
  87. 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) } }
  88. 118.
  89. 119.
  90. 120.
  91. 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)) } }
  92. 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) } }
  93. 123.
  94. 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) } }
  95. 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) } }
  96. 127.

    THINGS TO MOVE TO A CONTAINER > Core Data >

    Keychain > User Defaults > Data Caching
  97. 130.
  98. 131.
  99. 133.
  100. 134.
  101. 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 }
  102. 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 }
  103. 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 }
  104. 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 }
  105. 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 }
  106. 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 }
  107. 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 }
  108. 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 }
  109. 143.
  110. 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 }
  111. 145.
  112. 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) }
  113. 147.
  114. 148.
  115. 149.
  116. 150.
  117. 151.
  118. 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) }
  119. 153.
  120. 154.
  121. 155.
  122. 156.
  123. 157.
  124. 158.
  125. 159.
  126. 160.
  127. 162.
  128. 163.
  129. 164.
  130. 165.
  131. 166.
  132. 167.
  133. 168.
  134. 169.
  135. 170.
  136. 172.
  137. 174.

    VUX

  138. 176.

    VX

  139. 181.
  140. 189.

    OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited
  141. 190.

    OBLIGATORY SUMMARY SLIDE > Shortcuts are a great tool for

    automation > your shortcuts are a bit more limited > Use NSUserActivity for basic shortcuts
  142. 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
  143. 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
  144. 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
  145. 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
  146. 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
  147. 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/
  148. 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/
  149. 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/