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

Levelling Up with iOS 8

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.

sammyd

March 28, 2015
Tweet

More Decks by sammyd

Other Decks in Programming

Transcript

  1. Levelling
    taking advantage of the lesser-
    known features of iOS 8
    Up
    github.com/sammyd/iOS8-LevellingUp

    View Slide

  2. hi, i’m
    sam
    @iwantmyrealname

    View Slide

  3. shinobicontrols.com/ios8cocoa

    View Slide

  4. shinobicontrols

    View Slide

  5. shinobicontrols

    View Slide

  6. iOS 8

    View Slide

  7. iOS 8

    View Slide

  8. we can’t all be
    headliners

    View Slide

  9. things broke

    View Slide

  10. today
    • Fixing the things that broke
    • Alerts / ActionSheets
    • Popovers
    • Notifications
    • CoreLocation
    • Playing with the unloved shiny things
    • AVKit
    • Notification actions
    • Rotation
    • Testing

    View Slide

  11. disclaimers
    too much code

    View Slide

  12. disclaimers
    too many topics

    View Slide

  13. disclaimers
    too many slides

    View Slide

  14. disclaimers
    rubbish story

    View Slide

  15. #winning

    View Slide

  16. fixing stuff

    View Slide

  17. notifications

    View Slide

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

    View Slide

  19. notifications
    Local
    No further authorisation required
    Remote
    func
    registerForRemoteNotifications()
    UIUserNotification
    Represents the appearance of all notifications (badge / alert / sound)
    func registerUserNotificationSettings(notificationSettings:
    UIUserNotificationSettings)

    View Slide

  20. notifications
    let requestedTypes: UIUserNotificationType = .Alert | .Sound | .Badge
    let settingsRequest = UIUserNotificationSettings(forTypes:
    requestedTypes, categories: nil)
    UIApplication.sharedApplication().registerUserNotificationSettings(sett
    ingsRequest)

    View Slide

  21. notifications

    View Slide

  22. notifications

    View Slide

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

    View Slide

  24. notifications
    demo

    View Slide

  25. alerts / action sheets

    View Slide

  26. alerts & action sheets

    View Slide

  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!)
    ...

    View Slide

  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

    View Slide

  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)

    View Slide

  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)

    View Slide

  31. alerts & action sheets
    demo

    View Slide

  32. popovers

    View Slide

  33. popovers

    View Slide

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

    View Slide

  35. popovers
    UIModalPresentationStyle
    enum UIModalPresentationStyle : Int {
    case Popover
    ...
    }
    UIViewController
    var modalPresentationStyle: UIModalPresentationStyle
    var popoverPresentationController: UIPopoverPresentationController?
    { get }

    View Slide

  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]!
    ...
    }

    View Slide

  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)

    View Slide

  38. popovers

    View Slide

  39. popovers
    demo

    View Slide

  40. core location

    View Slide

  41. core location
    CLLocationManager
    func requestAlwaysAuthorization()
    func requestWhenInUseAuthorization()
    class func authorizationStatus() -> CLAuthorizationStatus
    CLAuthorizationStatus
    enum CLAuthorizationStatus : Int32 {
    ...
    case AuthorizedAlways
    case AuthorizedWhenInUse
    }

    View Slide

  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

    View Slide

  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
    }

    View Slide

  44. core location
    doesn’t work

    View Slide

  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()

    View Slide

  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()

    View Slide

  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()

    View Slide

  48. core location

    View Slide

  49. core location

    View Slide

  50. core location
    demo

    View Slide

  51. new shiny

    View Slide

  52. settings link

    View Slide

  53. link to settings
    UIApplicationOpenSettingsURLString

    View Slide

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

    View Slide

  55. link to settings

    View Slide

  56. link to settings
    demo

    View Slide

  57. AVKit

    View Slide

  58. AVKit
    AVPlayerViewController
    class AVPlayerViewController : UIViewController {
    var player: AVPlayer!
    var showsPlaybackControls: Bool
    var videoGravity: String!
    ...
    }

    View Slide

  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()
    }
    }

    View Slide

  60. AVKit

    View Slide

  61. AVKit
    demo

    View Slide

  62. notification actions

    View Slide

  63. notification actions

    View Slide

  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 }
    }

    View Slide

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

    View Slide

  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)
    }

    View Slide

  67. notification actions
    UIUserNotificationSettings
    class UIUserNotificationSettings : NSObject {
    convenience init(forTypes types: UIUserNotificationType, categories:
    Set?)
    var types: UIUserNotificationType { get }
    var categories: Set! { get }
    }

    View Slide

  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)

    View Slide

  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()
    }

    View Slide

  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",
    }
    }

    View Slide

  71. notification actions

    View Slide

  72. notification actions

    View Slide

  73. notification actions
    demo

    View Slide

  74. rotation

    View Slide

  75. rotation
    extension UIViewController {
    ...
    var interfaceOrientation: UIInterfaceOrientation { get }
    func willRotateToInterfaceOrientation(toInterfaceOrientation:
    UIInterfaceOrientation, duration: NSTimeInterval)
    func didRotateFromInterfaceOrientation(fromInterfaceOrientation:
    UIInterfaceOrientation)
    func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation:
    UIInterfaceOrientation, duration: NSTimeInterval)
    }

    View Slide

  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)
    }

    View Slide

  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)
    }

    View Slide

  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)
    }

    View Slide

  79. rotation
    protocol UIContentContainer : NSObjectProtocol {
    ...
    func viewWillTransitionToSize(size: CGSize,
    withTransitionCoordinator coordinator:
    UIViewControllerTransitionCoordinator)
    func willTransitionToTraitCollection(newCollection:
    UITraitCollection, withTransitionCoordinator coordinator:
    UIViewControllerTransitionCoordinator)
    }

    View Slide

  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)
    }

    View Slide

  81. rotation
    UIViewControllerTransisionCoordinatorContext
    protocol UIViewControllerTransitionCoordinatorContext : NSObjectProtocol
    {
    ...
    @availability(iOS, introduced=8.0)
    func targetTransform() -> CGAffineTransform
    }

    View Slide

  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
    ...
    })
    }
    }

    View Slide

  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)
    })
    }
    }

    View Slide

  84. rotation
    demo

    View Slide

  85. async testing

    View Slide

  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()})
    })
    }

    View Slide

  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!
    ...
    }

    View Slide

  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)
    }

    View Slide

  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
    }

    View Slide

  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)
    }

    View Slide

  91. async testing
    demo

    View Slide

  92. performance tests

    View Slide

  93. test performance
    class XCTestCase : XCTest {
    ...
    func measureBlock(block: (() -> Void)!)
    }

    View Slide

  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)
    }
    }

    View Slide

  95. test performance

    View Slide

  96. test performance

    View Slide

  97. test performance
    demo

    View Slide

  98. where next?

    View Slide

  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

    View Slide

  100. where next?
    shinobicontrols.com/ios8cocoa

    View Slide

  101. where next?
    nshipster.com
    asciiwwdc.com

    View Slide

  102. invest time

    View Slide

  103. questions?

    View Slide

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

    View Slide