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

iOS 8 App Extensions

iOS 8 App Extensions

A presentation originally delivered at Pragma Mark in Milan, Italy on October 4th, 2014,

023a6a37e8177cb2f84a236bbce643cf?s=128

Ben Scheirman

October 03, 2014
Tweet

Transcript

  1. Extensions in iOS 8 (like peanut butter and chocolate)

  2. Ben Scheirman @subdigital benscheirman.com

  3. ChaiOne Houston, TX @chaione

  4. NSScreencast

  5. Extensions!

  6. Your App + Other Apps

  7. Your App + 's Apps

  8. Extension Types Available

  9. Today Extension Add Widget to Notification Center

  10. Share Extension Share Content to Your App

  11. Action Extension Act Upon Content

  12. Keyboard Photo Editing Document Provider Not covered here :(

  13. Extension Architecture

  14. Isolated Process

  15. Independent Lifecycle

  16. Delivered with Applications

  17. None
  18. None
  19. Extensions are Just View Controllers

  20. The Ever-Important Info.plist

  21. NSExtension top level dictionary

  22. NSExtensionAttributes nested dictionary

  23. NSExtensionPrincipalClassName Provide a class to instantiate if not using storyboards.

  24. NSExtensionMainStoryboard Provide the name of your extension's storyboard.

  25. NSExtensionPointIdentifier Indicates the type of extension to the OS.

  26. What is an Activation Rule?

  27. Supported Activation Rules NSExtensionActivationSupportsAttachmentsWithMaxCount NSExtensionActivationSupportsAttachmentsWithMinCount NSExtensionActivationSupportsFileWithMaxCount NSExtensionActivationSupportsImageWithMaxCount NSExtensionActivationSupportsMovieWithMaxCount NSExtensionActivationSupportsText NSExtensionActivationSupportsWebURLWithMaxCount

    NSExtensionActivationSupportsWebPageWithMaxCount
  28. or a Predicate

  29. Default is TRUEPREDICATE

  30. Don't Ship with this!

  31. SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers

    UTI-CONFORMS-TO "com.adobe.pdf" ).@count == $extensionItem.attachments.@count ).@count == 1
  32. Info.plist Lots more options, read: https://developer.apple.com/library/prerelease/ iOS/documentation/General/Reference/ InfoPlistKeyReference/Articles/ SystemExtensionKeys.html

  33. Sharing Code Between App and Extensions

  34. Easy way: just add a class to multiple targets!

  35. None
  36. Though, you probably want to create a Framework

  37. None
  38. None
  39. Sharing Storyboards?

  40. Let's build a today extension

  41. Step 1: Add Extension Target

  42. None
  43. Step 2: Design the Widget

  44. None
  45. Step 3: Share Common Code

  46. None
  47. Step 4: Wire it up

  48. None
  49. Let's see it in action!

  50. None
  51. How do I keep them in sync?

  52. Sharing Data between App and Extension

  53. Can Share: · NSUserDefaults · CoreData · Files · Background

    Transfer Sessions
  54. Create an App Group must start with "group."

  55. Create for containing app Create same group for extension

  56. None
  57. Setup Shared NSUserDefaults var userDefaults: NSUserDefaults { get { return

    NSUserDefaults(suiteName: "group.com.nsscreencast.coffeetracker") } }
  58. Add to CoffeeTracker var numberOfCups: Int { get { return

    userDefaults.integerForKey("cups") } set { userDefaults.setInteger(newValue, forKey: "cups") updateDisplay() } }
  59. Handle Periodic Background Updates func widgetPerformUpdateWithCompletionHandler(completionHandler: (NCUpdateResult) -> ()) {

    coffeeTracker.updateDisplay() // If an error is encountered, use NCUpdateResult.Failed // If there's no update required, use NCUpdateResult.NoData // If there's an update, use NCUpdateResult.NewData completionHandler(NCUpdateResult.NewData) }
  60. Share Extensions

  61. · Share content to another app / service · Use

    System Sharing UI or Custom · Upload using shared background session
  62. Introducing... FoodBook

  63. Step 1: Add a framework for our shared code

  64. None
  65. Useful for putting networking code or view controllers common to

    app and extension
  66. Step 2: Add a Share Extension Target

  67. None
  68. Step 3: Update Info.plist

  69. None
  70. None
  71. Step 4: Edit View Controller

  72. (brace yourself)

  73. Import MobileCoreServices import MobileCoreServices

  74. Add a property to store the image being shared var

    image: UIImage?
  75. Get the Item Provider override func viewDidLoad() { var itemProvider:

    NSItemProvider? }
  76. Get the Item Provider override func viewDidLoad() { var itemProvider:

    NSItemProvider? if let items = extensionContext?.inputItems { } }
  77. Get the Item Provider override func viewDidLoad() { var itemProvider:

    NSItemProvider? if let items = extensionContext?.inputItems { if let item = items.first as? NSExtensionItem { } } }
  78. Get the Item Provider override func viewDidLoad() { var itemProvider:

    NSItemProvider? if let items = extensionContext?.inputItems { if let item = items.first as? NSExtensionItem { if let attachment = item.attachments?.first as? NSItemProvider { itemProvider = attachment } } } }
  79. Pull an image out of the item provider override func

    viewDidLoad() { // ... let imageType = kUTTypeImage as NSString if itemProvider?.hasItemConformingToTypeIdentifier(imageType) == true { } }
  80. Pull an image out of the item provider override func

    viewDidLoad() { // ... let imageType = kUTTypeImage as NSString if itemProvider?.hasItemConformingToTypeIdentifier(imageType) == true { itemProvider?.loadItemForTypeIdentifier(imageType, options: nil) { (item, error) in }) } }
  81. Pull an image out of the item provider override func

    viewDidLoad() { // ... let imageType = kUTTypeImage as NSString if itemProvider?.hasItemConformingToTypeIdentifier(imageType) == true { itemProvider?.loadItemForTypeIdentifier(imageType, options: nil) { (item, error) in if error == nil { let url = item as NSURL let imageData = NSData(contentsOfURL: url) self.image = UIImage(data: imageData) } else { println("ERROR: \(error)") } }) } }
  82. Step 5: Perform the upload

  83. override func didSelectPost() { FoodBookApiClient.upload(image) { self.extensionContext!.completeRequestReturningItems([], completionHandler: nil) }

    }
  84. Let's see it in action!

  85. None
  86. Can also bring your own view controller

  87. Action Extensions

  88. Act upon content user is looking at

  89. None
  90. Access to Entire DOM

  91. Supply JavaScript that formats the input and handles the output

  92. None
  93. Action.js var Action = function() {}; Action.prototype = { run:

    function(arguments) { }, finalize: function(arguments) { } }; var ExtensionPreprocessingJS = new Action
  94. run: function(arguments) { arguments.completionFunction({ "host": window.location.host, "path": window.location.pathname }) },

  95. App will receive PropertyList Item Type

  96. func beginRequestWithExtensionContext(context: NSExtensionContext) { self.extensionContext = context var itemProvider: NSItemProvider?

    if let item = context.inputItems.first as? NSExtensionItem { if let attachment = item.attachments?.first as? NSItemProvider { itemProvider = attachment } } let itemType = String(kUTTypePropertyList) if itemProvider?.hasItemConformingToTypeIdentifier(itemType) == true { itemProvider?.loadItemForTypeIdentifier(itemType, options: nil) { item, error in let dictionary = item as NSDictionary NSOperationQueue.mainQueue().addOperationWithBlock { let preprocessingResults = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as [NSObject:AnyObject] self.itemLoadCompletedWithPreprocessingResults(preprocessingResults) } } } else { self.doneWithResults(nil) } }
  97. let dictionary = item as NSDictionary NSOperationQueue.mainQueue().addOperationWithBlock { let results

    = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as NSDictionary self.itemLoadCompletedWithPreprocessingResults(results)
  98. func itemLoadCompletedWithPreprocessingResults(results: NSDictionary) { let host = results["host"] as String

    let path = results["path"] as String let newURL = "http://cat.\(host).meowbify.com\(path)" doneWithResults(["newURL": newURL]) }
  99. Prepare the results to go back to the JavaScript object

  100. func doneWithResults(results: NSDictionary?) { if let finalizeResults = results {

    var resultsDictionary = [NSExtensionJavaScriptFinalizeArgumentKey: finalizeResults] let itemType = kUTTypePropertyList as NSString var resultsProvider = NSItemProvider(item: resultsDictionary, typeIdentifier: itemType) var resultsItem = NSExtensionItem() resultsItem.attachments = [resultsProvider] extensionContext?.completeRequestReturningItems([resultsItem], completionHandler: nil) } else { extensionContext?.completeRequestReturningItems(nil, completionHandler: nil) } extensionContext = nil }
  101. Handle the results in JavaScript

  102. finalize: function(arguments) { window.location = arguments["newURL"]; }

  103. OMG, can we run it yet?

  104. None
  105. Questions?

  106. Thank you! Slide links: http://speakerdeck.com/subdigital/ios8-app-extension Code links: https://github.com/subdigital/pragma-mark-2014 Ben Scheirman

    ben@scheirman.com @subdigital @nsscreencast