Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Ellen Shapiro

November 09, 2018
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

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

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

  3. 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()
  4. 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()
  5. 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()
  6. 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()
  7. 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()
  8. 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()
  9. 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()
  10. 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()
  11. 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()
  12. 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()
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. DON'T TRY TO CRAM WAY TOO MUCH REPETITIVE INFORMATION INTO

    THE CONTENT DESCRIPTION ⬆ (LIKE THIS)
  20. 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
  21. 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
  22. 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 }
  23. 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 }
  24. 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 }
  25. 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 }
  26. 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)") } }
  27. 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)") } }
  28. 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)") } }
  29. 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)") } }
  30. 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)") } }
  31. 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 }
  32. 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 }
  33. 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 }
  34. 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 }
  35. 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 }
  36. 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) } }
  37. 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) } }
  38. 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) } }
  39. 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)) } }
  40. 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) } }
  41. 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) } }
  42. 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) } }
  43. THINGS TO MOVE TO A CONTAINER > Core Data >

    Keychain > User Defaults > Data Caching
  44. 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 }
  45. 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 }
  46. 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 }
  47. 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 }
  48. 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 }
  49. 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 }
  50. 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 }
  51. 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 }
  52. 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 }
  53. 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) }
  54. 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) }
  55. VUX

  56. VX

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

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

    automation > your shortcuts are a bit more limited > Use NSUserActivity for basic shortcuts
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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/
  65. 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/
  66. 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/