Levelling Up with iOS 8

Ddd6d3bac7772fa67fc5e312a18bdaec?s=47 sammyd
March 28, 2015

Levelling Up with iOS 8

iOS 8 was somewhat overshadowed by the introduction of a massive white bird in an orange sky. There were loads of cool new things in iOS 8, and this talk is a walk through some of the more obscure of these.

Ddd6d3bac7772fa67fc5e312a18bdaec?s=128

sammyd

March 28, 2015
Tweet

Transcript

  1. Levelling taking advantage of the lesser- known features of iOS

    8 Up github.com/sammyd/iOS8-LevellingUp
  2. hi, i’m sam @iwantmyrealname

  3. shinobicontrols.com/ios8cocoa

  4. shinobicontrols

  5. shinobicontrols

  6. iOS 8

  7. iOS 8

  8. we can’t all be headliners

  9. things broke

  10. today • Fixing the things that broke • Alerts /

    ActionSheets • Popovers • Notifications • CoreLocation • Playing with the unloved shiny things • AVKit • Notification actions • Rotation • Testing
  11. disclaimers too much code

  12. disclaimers too many topics

  13. disclaimers too many slides

  14. disclaimers rubbish story

  15. #winning

  16. fixing stuff

  17. notifications

  18. notifications Local No authorisation required Remote func registerForRemoteNotificationTypes (types: UIRemoteNotificationType)

  19. notifications Local No further authorisation required Remote func registerForRemoteNotifications() UIUserNotification

    Represents the appearance of all notifications (badge / alert / sound) func registerUserNotificationSettings(notificationSettings: UIUserNotificationSettings)
  20. notifications let requestedTypes: UIUserNotificationType = .Alert | .Sound | .Badge

    let settingsRequest = UIUserNotificationSettings(forTypes: requestedTypes, categories: nil) UIApplication.sharedApplication().registerUserNotificationSettings(sett ingsRequest)
  21. notifications

  22. notifications

  23. notifications UIApplication func currentUserNotificationSettings() -> UIUserNotificationSettings! UIApplicationDelegate optional func application(application:

    UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings)
  24. notifications demo

  25. alerts / action sheets

  26. alerts & action sheets

  27. alerts & action sheets UIAlertView init(title: String?, message: String?, delegate:

    AnyObject?, cancelButtonTitle: String?) func show() UIActionSheet init(title: String?, delegate: UIActionSheetDelegate?, cancelButtonTitle: String?, destructiveButtonTitle: String?) func showFromToolbar(view: UIToolbar!) func showFromTabBar(view: UITabBar!) ...
  28. alerts & action sheets UIAlertController class UIAlertController : UIViewController {

    convenience init(title: String?, message: String?, preferredStyle: UIAlertControllerStyle) ... } func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) Alerts UIAlertControllerStyle.Alert Action Sheet UIAlertControllerStyle.ActionSheet
  29. alerts & action sheets UIAlertAction class UIAlertAction : NSObject, NSCopying

    { convenience init(title: String, style: UIAlertActionStyle, handler: ((UIAlertAction!) -> Void)!) ... } UIAlertActionStyle enum UIAlertActionStyle : Int { case Default case Cancel case Destructive } UIAlertController func addAction(action: UIAlertAction)
  30. alerts & action sheets demo let alert = UIAlertController(title: “Alert",

    message: "The alert controller", preferredStyle: .Alert) let dismiss = { (action: UIAlertAction!) in self.dismissViewControllerAnimated(true, completion: nil) } alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: dismiss)) alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: dismiss)) alert.addTextFieldWithConfigurationHandler { textField in textField.placeholder = "Sample text field" } presentViewController(alert, animated: true, completion: nil)
  31. alerts & action sheets demo

  32. popovers

  33. popovers

  34. popovers UIPopoverController init(contentViewController viewController: UIViewController) •Not adaptive •Presentation is now

    handled by UIPresentationController
  35. popovers UIModalPresentationStyle enum UIModalPresentationStyle : Int { case Popover ...

    } UIViewController var modalPresentationStyle: UIModalPresentationStyle var popoverPresentationController: UIPopoverPresentationController? { get }
  36. popovers UIPopoverPresentationController class UIPopoverPresentationController : UIPresentationController { unowned(unsafe) var delegate:

    UIPopoverPresentationControllerDelegate? var permittedArrowDirections: UIPopoverArrowDirection var sourceView: UIView! var sourceRect: CGRect var barButtonItem: UIBarButtonItem! var passthroughViews: [AnyObject]! ... }
  37. popovers demo let popoverVC = storyboard? .instantiateViewControllerWithIdentifier("PopoverContentVC") as! UIViewController popoverVC.modalPresentationStyle

    = .Popover if let popoverController = popoverVC.popoverPresentationController { popoverController.sourceView = sender popoverController.sourceRect = sender.bounds popoverController.permittedArrowDirections = .Any popoverController.delegate = self } presentViewController(popoverVC, animated: true, completion: nil)
  38. popovers

  39. popovers demo

  40. core location

  41. core location CLLocationManager func requestAlwaysAuthorization() func requestWhenInUseAuthorization() class func authorizationStatus()

    -> CLAuthorizationStatus CLAuthorizationStatus enum CLAuthorizationStatus : Int32 { ... case AuthorizedAlways case AuthorizedWhenInUse }
  42. core location Always • Foreground and background • Able to

    launch the app • Geofencing / Region monitoring • Even if killed by user • Users will be asked to confim “later” WhenInUse • Foreground only • Not allowed access to launch APIs • Banner displayed when app moves to background •
  43. core location CLLocationManager override func viewDidLoad() { super.viewDidLoad() // Prepare

    the location manager locationManager.delegate = self locationManager.desiredAccuracy = 20 // Need to ask for the right permissions locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() // Prepare the map view mapView.delegate = self }
  44. core location doesn’t work

  45. core location CLLocationManager /* * requestWhenInUseAuthorization * * Discussion: *

    When +authorizationStatus == kCLAuthorizationStatusNotDetermined, * calling this method will trigger a prompt to request "when-in-use" * authorization from the user. If possible, perform this call in response * to direct user request for a location-based service so that the reason * for the prompt will be clear. Any authorization change as a result of * the prompt will be reflected via the usual delegate callback: * -locationManager:didChangeAuthorizationStatus:. * * If received, "when-in-use" authorization grants access to the user's * location via -startUpdatingLocation/-startRangingBeaconsInRegion while * in the foreground. If updates have been started when going to the * background, then a status bar banner will be displayed to maintain * visibility to the user, and updates will continue until stopped * normally, or the app is killed by the user. * * "When-in-use" authorization does NOT enable monitoring API on regions, * significant location changes, or visits, and -startUpdatingLocation will * not succeed if invoked from the background. * * When +authorizationStatus != kCLAuthorizationStatusNotDetermined, (ie * generally after the first call) this method will do nothing. * * If the NSLocationWhenInUseUsageDescription key is not specified in your * Info.plist, this method will do nothing, as your app will be assumed not * to support WhenInUse authorization. */ @availability(iOS, introduced=8.0) func requestWhenInUseAuthorization()
  46. core location CLLocationManager /* * requestWhenInUseAuthorization * * Discussion: *

    When +authorizationStatus == kCLAuthorizationStatusNotDetermined, * calling this method will trigger a prompt to request "when-in-use" * authorization from the user. If possible, perform this call in response * to direct user request for a location-based service so that the reason * for the prompt will be clear. Any authorization change as a result of * the prompt will be reflected via the usual delegate callback: * -locationManager:didChangeAuthorizationStatus:. * * If received, "when-in-use" authorization grants access to the user's * location via -startUpdatingLocation/-startRangingBeaconsInRegion while * in the foreground. If updates have been started when going to the * background, then a status bar banner will be displayed to maintain * visibility to the user, and updates will continue until stopped * normally, or the app is killed by the user. * * "When-in-use" authorization does NOT enable monitoring API on regions, * significant location changes, or visits, and -startUpdatingLocation will * not succeed if invoked from the background. * * When +authorizationStatus != kCLAuthorizationStatusNotDetermined, (ie * generally after the first call) this method will do nothing. * * If the NSLocationWhenInUseUsageDescription key is not specified in your * Info.plist, this method will do nothing, as your app will be assumed not * to support WhenInUse authorization. */ @availability(iOS, introduced=8.0) func requestWhenInUseAuthorization()
  47. core location CLLocationManager * If the NSLocationWhenInUseUsageDescription key is not

    * specified in your Info.plist, this method will do nothing, * as your app will be assumed not to support WhenInUse * authorization. */ @availability(iOS, introduced=8.0) func requestWhenInUseAuthorization()
  48. core location

  49. core location

  50. core location demo

  51. new shiny

  52. settings link

  53. link to settings UIApplicationOpenSettingsURLString

  54. link to settings let settingsUrl = NSURL(string: UIApplicationOpenSettingsURLString) UIApplication.sharedApplication().openURL(settingsUrl!)

  55. link to settings

  56. link to settings demo

  57. AVKit

  58. AVKit AVPlayerViewController class AVPlayerViewController : UIViewController { var player: AVPlayer!

    var showsPlaybackControls: Bool var videoGravity: String! ... }
  59. AVKit class AVKitViewController: AVPlayerViewController { override func viewDidLoad() { super.viewDidLoad()

    // Do any additional setup after loading the view. let url = NSBundle.mainBundle().URLForResource("countdown_new", withExtension: "mov") player = AVPlayer(URL: url) // Some configuration showsPlaybackControls = true // AVFoundation pipeline controls player.play() } }
  60. AVKit

  61. AVKit demo

  62. notification actions

  63. notification actions

  64. notification actions UIUserNotificationAction class UIUserNotificationAction : NSObject, NSCopying, NSMutableCopying, NSSecureCoding,

    NSCoding { var identifier: String! { get } var title: String! { get } var activationMode: UIUserNotificationActivationMode { get } var authenticationRequired: Bool { get } var destructive: Bool { get } }
  65. notification actions UIMutableUserNotificationAction class UIMutableUserNotificationAction : UIUserNotificationAction { var identifier:

    String! var title: String! var activationMode: UIUserNotificationActivationMode var authenticationRequired: Bool var destructive: Bool }
  66. notification actions UIUserNotificationCategory class UIUserNotificationCategory : NSObject, NSCopying, NSMutableCopying, NSSecureCoding,

    NSCoding { var identifier: String! { get } func actionsForContext(context: UIUserNotificationActionContext) -> [AnyObject]! } UIMutableUserNotificationCategory class UIMutableUserNotificationCategory : UIUserNotificationCategory { var identifier: String! func setActions(actions: [AnyObject]!, forContext context: UIUserNotificationActionContext) }
  67. notification actions UIUserNotificationSettings class UIUserNotificationSettings : NSObject { convenience init(forTypes

    types: UIUserNotificationType, categories: Set<NSObject>?) var types: UIUserNotificationType { get } var categories: Set<NSObject>! { get } }
  68. notification actions let cancelAction = UIMutableUserNotificationAction() cancelAction.identifier = cancelNotificationString cancelAction.destructive

    = true cancelAction.title = "Cancel" cancelAction.activationMode = .Background cancelAction.authenticationRequired = false ... let category = UIMutableUserNotificationCategory() category.identifier = notificationActionDemoString category.setActions([cancelAction, askAgainAction], forContext: .Minimal) let requestedTypes = UIUserNotificationType.Alert let settingsRequest = UIUserNotificationSettings(forTypes: requestedTypes, categories: [category]) UIApplication.sharedApplication() .registerUserNotificationSettings(settingsRequest)
  69. notification actions UIApplicationDelegate func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification

    notification: UILocalNotification, completionHandler: () -> Void) { if let identifier = identifier { switch identifier { case cancelNotificationString: println("You cancelled") case askAgainNotificationString: println("You restarted!") default: break } } completionHandler() }
  70. notification actions Local let notification = UILocalNotification() notification.fireDate = NSDate(timeIntervalSinceNow:

    3) notification.alertBody = "Asking you now" notification.category = notificationActionDemoString UIApplication.sharedApplication() .scheduleLocalNotification(notification) Remote { "aps" : { "alert" : "You’re invited!", "category" : "AskMeNotificationString", } }
  71. notification actions

  72. notification actions

  73. notification actions demo

  74. rotation

  75. rotation extension UIViewController { ... var interfaceOrientation: UIInterfaceOrientation { get

    } func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) }
  76. rotation extension UIViewController { ... @availability(iOS, introduced=2.0, deprecated=8.0) var interfaceOrientation:

    UIInterfaceOrientation { get } @availability(iOS, introduced=2.0, deprecated=8.0, message="Implement viewWillTransitionToSize:withTransitionCoordinator: instead") func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) @availability(iOS, introduced=2.0, deprecated=8.0) func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) @availability(iOS, introduced=3.0, deprecated=8.0, message="Implement viewWillTransitionToSize:withTransitionCoordinator: instead") func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) }
  77. rotation extension UIViewController { ... @availability(iOS, introduced=2.0, deprecated=8.0) var interfaceOrientation:

    UIInterfaceOrientation { get } @availability(iOS, introduced=2.0, deprecated=8.0, message="Implement viewWillTransitionToSize:withTransitionCoordinator: instead") func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) @availability(iOS, introduced=2.0, deprecated=8.0) func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) @availability(iOS, introduced=3.0, deprecated=8.0, message="Implement viewWillTransitionToSize:withTransitionCoordinator: instead") func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) }
  78. rotation extension UIViewController { ... @availability(iOS, introduced=2.0, deprecated=8.0) var interfaceOrientation:

    UIInterfaceOrientation { get } @availability(iOS, introduced=2.0, deprecated=8.0, message="Implement viewWillTransitionToSize:withTransitionCoordinator: instead") func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) @availability(iOS, introduced=2.0, deprecated=8.0) func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) @availability(iOS, introduced=3.0, deprecated=8.0, message="Implement viewWillTransitionToSize:withTransitionCoordinator: instead") func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) }
  79. rotation protocol UIContentContainer : NSObjectProtocol { ... func viewWillTransitionToSize(size: CGSize,

    withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) func willTransitionToTraitCollection(newCollection: UITraitCollection, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) }
  80. rotation UIViewControllerTransitionCoordinator protocol UIViewControllerTransitionCoordinator : UIViewControllerTransitionCoordinatorContext, NSObjectProtocol { func animateAlongsideTransition(

    animation: ((UIViewControllerTransitionCoordinatorContext!) -> Void)!, completion: ((UIViewControllerTransitionCoordinatorContext!) -> Void)!) -> Bool func animateAlongsideTransitionInView(view: UIView!, animation: ((UIViewControllerTransitionCoordinatorContext!) -> Void)!, completion: ((UIViewControllerTransitionCoordinatorContext!) -> Void)!) -> Bool func notifyWhenInteractionEndsUsingBlock( handler: (UIViewControllerTransitionCoordinatorContext!) -> Void) }
  81. rotation UIViewControllerTransisionCoordinatorContext protocol UIViewControllerTransitionCoordinatorContext : NSObjectProtocol { ... @availability(iOS, introduced=8.0)

    func targetTransform() -> CGAffineTransform }
  82. rotation override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransitionToSize(size,

    withTransitionCoordinator: coordinator) let targetTransform = coordinator.targetTransform() if !CGAffineTransformIsIdentity(targetTransform) { coordinator.animateAlongsideTransition({ context in UIView.animateWithDuration(context.transitionDuration()) { for monkey in self.monkeyLabels { monkey.transform = CGAffineTransformInvert(targetTransform) } } }, completion: { context in ... }) } }
  83. rotation override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { ...

    if !CGAffineTransformIsIdentity(targetTransform) { coordinator.animateAlongsideTransition({ ... }, completion: { context in UIView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0, options: .allZeros, animations: { for monkey in self.monkeyLabels { monkey.transform = CGAffineTransformIdentity } }, completion: nil) }) } }
  84. rotation demo

  85. async testing

  86. async testing public func someKindOfAsyncMethod(completionHandler: () -> ()) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT

    , 0), { sleep(3) dispatch_async(dispatch_get_main_queue(), { completionHandler()}) }) }
  87. async testing class XCTestExpectation : NSObject { func fulfill() }

    extension XCTestCase { func expectationWithDescription(description: String!) -> XCTestExpectation! func waitForExpectationsWithTimeout(timeout: NSTimeInterval, handler handlerOrNil: XCWaitCompletionHandler!) func keyValueObservingExpectationForObject(objectToObserve: AnyObject!, keyPath: String!, expectedValue: AnyObject!) -> XCTestExpectation! ... }
  88. async testing func testBasicAsyncMethod() { // Check that we get

    called back as expected let expectation = expectationWithDescription("Async Method") raceController.someKindOfAsyncMethod({ expectation.fulfill() }) waitForExpectationsWithTimeout(5, handler: nil) }
  89. async testing raceController.startRace(5, horseCrossedLineCallback:{ (horse:Horse) in // Deal with the

    number of horses self.numberOfHorsesCurrentlyRunning -= 1 if self.numberOfHorsesCurrentlyRunning == 0 { self.resetButton.enabled = true }
  90. async testing func testResetButtonEnabledOnceRaceComplete() { let expectation = keyValueObservingExpectationForObject( viewController.resetButton,

    keyPath: “enabled", expectedValue: true) // Simulate tapping the start race button viewController.handleStartRaceButton(viewController.startRaceButton) // Wait for the test to run waitForExpectationsWithTimeout(5, handler: nil) }
  91. async testing demo

  92. performance tests

  93. test performance class XCTestCase : XCTest { ... func measureBlock(block:

    (() -> Void)!) }
  94. test performance func testMovingAveragePerformance() { // This is an example

    of a performance test case. calculator.windowSize = 1000 self.measureBlock() { // Put the code you want to measure the time of here. let randomArray = self.RandomDouble(10000) let result = self.calculator.calculateMovingAverage(randomArray) XCTAssertEqual(result.count, 9000) } }
  95. test performance

  96. test performance

  97. test performance demo

  98. where next?

  99. where next? Custom fonts in Interface Builder Designated initialisers in

    Swift / ObjC Nullability annotations in ObjC New detectors in CoreImage NSFormatter Auto hiding of navigation/tab bars Custom CIFilter Kernels Live rendering in Interface Builder Layout margins CoreMotion Location notifications NSCalendar additions
  100. where next? shinobicontrols.com/ios8cocoa

  101. where next? nshipster.com asciiwwdc.com

  102. invest time

  103. questions?

  104. questions? @iwantmyrealname github.com/sammyd/iOS8-LevellingUp speakerdeck.com/sammyd shinobicontrols.com/ios8cocoa