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

Siri Shortcuts - NSSpain, Logroño, Spain, September 2018

Ellen Shapiro
September 13, 2018

Siri Shortcuts - NSSpain, Logroño, Spain, September 2018

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

Work-In-Progress sample Code: https://github.com/designatednerd/TravelPlanner

Ellen Shapiro

September 13, 2018
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. !

  2. 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()
  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 // IMPORTANT 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. 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
  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)" } // TODO: Y U NO WORK?! 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. DON'T TRY TO CRAM WAY TOO MUCH REPETITIVE INFORMATION INTO

    THE CONTENT DESCRIPTION ⬆ (LIKE THIS)
  19. 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
  20. 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
  21. 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 }
  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. DONATING AN INTERACTION private func donateEditTripIntent() { let intent =

    EditTripIntent() intent.name = self.trip.name intent.destination = self.trip.destination intent.suggestedInvocationPhrase = "Edit \(intent.destination ?? intent.name!) trip" let interaction = INInteraction(intent: intent, response: nil) interaction.donate { error in if let error = error { debugPrint("Error donating intent: \(error)") } } }
  26. DONATING AN INTERACTION private func donateEditTripIntent() { let intent =

    EditTripIntent() intent.name = self.trip.name intent.destination = self.trip.destination intent.suggestedInvocationPhrase = "Edit \(intent.destination ?? intent.name!) trip" let interaction = INInteraction(intent: intent, response: nil) interaction.donate { error in if let error = error { debugPrint("Error donating intent: \(error)") } } }
  27. DONATING AN INTERACTION private func donateEditTripIntent() { let intent =

    EditTripIntent() intent.name = self.trip.name intent.destination = self.trip.destination intent.suggestedInvocationPhrase = "Edit \(intent.destination ?? intent.name!) trip" let interaction = INInteraction(intent: intent, response: nil) interaction.donate { error in if let error = error { debugPrint("Error donating intent: \(error)") } } }
  28. DONATING AN INTERACTION private func donateEditTripIntent() { let intent =

    EditTripIntent() intent.name = self.trip.name intent.destination = self.trip.destination intent.suggestedInvocationPhrase = "Edit \(intent.destination ?? intent.name!) trip" let interaction = INInteraction(intent: intent, response: nil) interaction.donate { error in if let error = error { debugPrint("Error donating intent: \(error)") } } }
  29. DONATING AN INTERACTION private func donateEditTripIntent() { let intent =

    EditTripIntent() intent.name = self.trip.name intent.destination = self.trip.destination intent.suggestedInvocationPhrase = "Edit \(intent.destination ?? intent.name!) trip" let interaction = INInteraction(intent: intent, response: nil) interaction.donate { error in if let error = error { debugPrint("Error donating intent: \(error)") } } }
  30. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let intent = userActivity.interaction?.intent as? EditTripIntent { guard let trip = self.getTrip(from: intent) else { return false } self.coordinator.editTrip(trip) return true } // Rest of method we saw earlier }
  31. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let intent = userActivity.interaction?.intent as? EditTripIntent { guard let trip = self.getTrip(from: intent) else { return false } self.coordinator.editTrip(trip) return true } // Rest of method we saw earlier }
  32. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let intent = userActivity.interaction?.intent as? EditTripIntent { guard let trip = self.getTrip(from: intent) else { return false } self.coordinator.editTrip(trip) return true } // Rest of method we saw earlier }
  33. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let intent = userActivity.interaction?.intent as? EditTripIntent { guard let trip = self.getTrip(from: intent) else { return false } self.coordinator.editTrip(trip) return true } // Rest of method we saw earlier }
  34. AppDelegate INTENT SETUP func application(_ application: UIApplication, continue userActivity: NSUserActivity,

    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if let intent = userActivity.interaction?.intent as? EditTripIntent { guard let trip = self.getTrip(from: intent) else { return false } self.coordinator.editTrip(trip) return true } // Rest of method we saw earlier }
  35. INTENT HANDLER SETUP public class AddTripIntentHandler: NSObject, EditTripIntentHandling { //

    Optional! public func confirm(intent: EditTripIntent, completion: @escaping (EditTriptIntentResponse) -> Void) { // Figure out if the thing can be done } public func handle(intent: EditTripIntent, completion: @escaping (EditTripIntentResponse) -> Void) { // Do the thing! completion(.success) } }
  36. INTENT HANDLER SETUP public class AddTripIntentHandler: NSObject, EditTripIntentHandling { //

    Optional! public func confirm(intent: EditTripIntent, completion: @escaping (EditTriptIntentResponse) -> Void) { // Figure out if the thing can be done } public func handle(intent: EditTripIntent, completion: @escaping (EditTripIntentResponse) -> Void) { // Do the thing! completion(.success) } }
  37. INTENT HANDLER SETUP public class AddTripIntentHandler: NSObject, EditTripIntentHandling { //

    Optional! public func confirm(intent: EditTripIntent, completion: @escaping (EditTriptIntentResponse) -> Void) { // Figure out if the thing can be done } public func handle(intent: EditTripIntent, completion: @escaping (EditTripIntentResponse) -> Void) { // Do the thing! completion(.success) } }
  38. INTENT HANDLER SETUP public class AddTripIntentHandler: NSObject, EditTripIntentHandling { //

    Optional! public func confirm(intent: EditTripIntent, completion: @escaping (EditTriptIntentResponse) -> Void) { // Figure out if the thing can be done } public func handle(intent: EditTripIntent, completion: @escaping (EditTripIntentResponse) -> Void) { // Do the thing! completion(.success(name: name, destination: destination)) } }
  39. VUX

  40. VX

  41. OBLIGATORY SUMMARY SLIDE > Shortcuts can be a powerful tool

    for automation > your shortcuts are a bit more limited
  42. OBLIGATORY SUMMARY SLIDE > Shortcuts can be a powerful tool

    for automation > your shortcuts are a bit more limited > Use NSUserActivity for basic action shortcuts
  43. OBLIGATORY SUMMARY SLIDE > Shortcuts can be a powerful tool

    for automation > your shortcuts are a bit more limited > Use NSUserActivity for basic action shortcuts > Use Custom Intents for more involved shortcuts
  44. OBLIGATORY SUMMARY SLIDE > Shortcuts can be a powerful tool

    for automation > your shortcuts are a bit more limited > Use NSUserActivity for basic action shortcuts > Use Custom Intents for more involved shortcuts > Keep titles and suggested invocation phrases short
  45. OBLIGATORY SUMMARY SLIDE > Shortcuts can be a powerful tool

    for automation > your shortcuts are a bit more limited > Use NSUserActivity for basic action shortcuts > Use Custom Intents for more involved shortcuts > Keep titles and suggested invocation phrases short > Responses + Intent Extension + Intent UI = users
  46. OBLIGATORY SUMMARY SLIDE > Shortcuts can be a powerful tool

    for automation > Use NSUserActivity for basic action shortcuts > Use Custom Intents for more involved shortcuts > Keep titles and suggested invocation phrases short > Use custom responses to handle errors more clearly
  47. 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/
  48. 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/