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

Levelling Up with iOS 8 (PDX)

Levelling Up with iOS 8 (PDX)

This is the version of the talk presented at CocoaConf PDX in May 2015.

sammyd

May 09, 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
  2. today • Fixing the things that broke • Alerts /

    ActionSheets • Popovers • Notifications • CoreLocation • Playing with the unloved shiny things • AVKit • Notification actions • Rotation • Testing
  3. notifications Local No further authorisation required Remote func registerForRemoteNotifications() UIUserNotification

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

    let settingsRequest = UIUserNotificationSettings(forTypes: requestedTypes, categories: nil) UIApplication.sharedApplication().registerUserNotificationSettings(sett ingsRequest)
  5. 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!) ...
  6. 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
  7. 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)
  8. 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)
  9. popovers UIModalPresentationStyle enum UIModalPresentationStyle : Int { case Popover ...

    } UIViewController var modalPresentationStyle: UIModalPresentationStyle var popoverPresentationController: UIPopoverPresentationController? { get }
  10. 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]! ... }
  11. popovers demo let popoverVC = storyboard? .instantiateViewControllerWithIdentifier("PopoverContentVC") as! UIViewController popoverVC.modalPresentationStyle

    = .Popover presentViewController(popoverVC, animated: true, completion: nil) if let popoverController = popoverVC.popoverPresentationController { popoverController.sourceView = sender popoverController.sourceRect = sender.bounds popoverController.permittedArrowDirections = .Any popoverController.delegate = self }
  12. core location CLLocationManager func requestAlwaysAuthorization() func requestWhenInUseAuthorization() class func authorizationStatus()

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

    launch the app • Geofencing / Region monitoring • Even if killed by user • Users will be asked to confirm “later” WhenInUse • Foreground only • Not allowed access to launch APIs • Banner displayed when app moves to background
  14. 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 }
  15. 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()
  16. 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()
  17. 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()
  18. 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() } }
  19. 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 } }
  20. notification actions UIMutableUserNotificationAction class UIMutableUserNotificationAction : UIUserNotificationAction { var identifier:

    String! var title: String! var activationMode: UIUserNotificationActivationMode var authenticationRequired: Bool var destructive: Bool }
  21. 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) }
  22. notification actions UIUserNotificationSettings class UIUserNotificationSettings : NSObject { convenience init(forTypes

    types: UIUserNotificationType, categories: Set<NSObject>?) var types: UIUserNotificationType { get } var categories: Set<NSObject>! { get } }
  23. 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)
  24. 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() }
  25. 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", } }
  26. rotation extension UIViewController { ... var interfaceOrientation: UIInterfaceOrientation { get

    } func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) }
  27. 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) }
  28. 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) }
  29. rotation protocol UIContentContainer : NSObjectProtocol { ... func viewWillTransitionToSize(size: CGSize,

    withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) func willTransitionToTraitCollection(newCollection: UITraitCollection, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) }
  30. 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) }
  31. 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 ... }) } }
  32. 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) }) } }
  33. 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! ... }
  34. 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) }
  35. 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 }
  36. 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) }
  37. 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) } }
  38. 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