Topics

#39: CGPath Basics 📈

Topics

CGPaths are how CoreGraphics describes the bezier paths it uses for drawing to the screen. They can go a long way to unlocking some of the power of CoreGraphics, so let’s take a look:

We're going to create a custom disclosure indicator to use on our UITableView rows. We’ll create a CGPath, and add two lines to it that make up the arrow shape.

This kind of CoreGraphics code is largely imperative. You tell the system what to do in a bunch of steps.

@IBDesignable
class DisclosureIndicatorView : UIView {
  override func drawRect(rect: CGRect) {
    let path = CGPathCreateMutable()

    // move to top-left
    CGPathMoveToPoint(path, nil, bounds.origin.x, bounds.origin.y)

    // add line to middle-right
    CGPathAddLineToPoint(path, nil, bounds.size.width, CGRectGetMidY(bounds))

    // move to bottom-left
    CGPathMoveToPoint(path, nil, bounds.origin.x, bounds.size.height)

    // add another line to middle-right
    CGPathAddLineToPoint(path, nil, bounds.size.width, CGRectGetMidY(bounds))

    let context = UIGraphicsGetCurrentContext()
    CGContextAddPath(context, path)

    UIColor.lightGrayColor().setStroke()
    CGContextSetLineWidth(context, 2.0);

    CGContextDrawPath(context, kCGPathStroke)
  }
}

We tell CoreGraphics to move to the top-left point in our view, then we ask the system to add line, then move again, and so on.

Finally we add the path to our context, and draw the path to the screen using a light gray color, and a 2-pixel wide line.

Pro Tip: Add @IBDesignable to your view so you can preview it in Interface Builder! (shown below)

Topics

#38: Gesture Recognizer Basics 👆

Topics

Gesture Recognizers are an powerful way to build interactions into your app. Let's look at the basics with a couple of example use cases:

Two Finger Long Press

Here we'll build a gesture that fires when the user touches the screen with two fingers for 1 second.

func handleLongPressGesture(gr: UILongPressGestureRecognizer) {
  sendHeartbeat()
}

let longPressGR = UILongPressGestureRecognizer(
  target: self, 
  action: "handleLongPressGesture:"
)

// we allow user some 'wiggle' so the recognizer
// doesn't fail if they move slightly while touching

longPressGR.allowableMovement = 80

longPressGR.minimumPressDuration = 1 // in seconds
longPressGR.numberOfTouchesRequired = 2

digitalTouchView.addGestureRecognizer(longPressGR)

Make a View Draggable

Ever wanted to enable a view to be dragged around the screen? A pan gesture recognizer does the trick:

func handlePanGesture(gr: UIPanGestureRecognizer) {
  guard gr.state != .Ended && gr.state != .Failed else { return }

  gr.view!.center = gr.locationInView(gr.view!.superview!)
}

let panGR = UIPanGestureRecognizer(
  target: self,
  action: "handlePanGesture:"
)

panGR.minimumNumberOfTouches = 1
panGR.maximumNumberOfTouches = 1

moveableView.addGestureRecognizer(panGR)

Topics

#37: Touch ID 👍

Topics

Protecting data with Touch ID is a great way to improve your app.
Let's take a look at how to do it:

Save to Keychain

First you need to save something to the keychain, and protect it with the User Presence access control flag.

func savePassword(username: String, password: String) {
  let passwordData = password.dataUsingEncoding(NSUTF8StringEncoding)
  guard let data = passwordData else { return }

  let accessControl = SecAccessControlCreateWithFlags(
    kCFAllocatorDefault, 
    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
    .UserPresence // this part enables Touch ID support
    , nil
  )

  SecItemAdd([
    String(kSecAttrService) : serviceID,
    String(kSecClass) : kSecClassGenericPassword,
    String(kSecAttrAccessControl) : accessControl.takeUnretainedValue(),
    String(kSecAttrAccount) : username,
    String(kSecValueData) : data
  ], nil)
}

Retrieve From Keychain

Then when you retrieve, supply a prompt message and iOS will show the familiar Touch ID alert before executing the Keychain query:

func passwordForUsername(username: String) -> String? {
  let query = [
    String(kSecAttrService) : serviceID,
    String(kSecClass) : kSecClassGenericPassword,
    String(kSecReturnData) : true,
    String(kSecMatchLimit) : kSecMatchLimitOne,
    String(kSecUseOperationPrompt) : "Authenticate to Login",
    String(kSecAttrAccount) : username
  ]

  // Working with the Security Framework is still kind of ugly in Swift:

  var passwordData: Unmanaged<AnyObject>? = nil
  SecItemCopyMatching(query, &passwordData)

  if let opaque = passwordData?.toOpaque() {
    return NSString(
      data: Unmanaged<NSData>.fromOpaque(opaque).takeUnretainedValue(),
      encoding: NSUTF8StringEncoding
    ) as? String
  }

  return nil
}

Topics

#36: Today View Widgets 📟

Topics

Creating a Today View Widget for your app is a great way to give users a quick way to access timely information or controls. Let's make one!

Add the Widget Target

Implement Regular Table View Controller Stuff

class TodayViewController: UITableViewController, NCWidgetProviding {
  var bites = [Bite]()

  // not shown here because boring:
  // numberOfRowsInSection returns bites.count
  // cellForRowAtIndexPath just sets cell.textLabel.text = bite.title
}

Conform to NCWidgetProviding Protocol

func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
  Bite.fetchLatest { (bites, error) in
    guard error == nil else { completionHandler(.Failed); return }
    self.bites = bites

      self.tableView?.reloadData()
      self.preferredContentSize = self.tableView!.contentSize

    completionHandler(.NewData)
  }
}

Success!

Topics

#35: Async 🔀

Topics

Grand Central Dispatch is a wonderful way to write asynchronous code in your app. It's actual API however, can sometimes be a bit, well, let's call it "dry". Async is a Swift library by Tobias Due Munk that adds some much-welcomed syntactic sugar to Grand Central Dispatch. Async's core API is simple enough to be understood in a single line of code, so let's look at some other fun things it can do:

Chain Blocks

Async.background {
  // Runs on the background queue
}.main {
  // Runs on the main queue, but only after
  // the previous block returns
}

Delayed Execution

Async.main(after: 0.5) {
  // executed on the main queue after 500 ms
}.background(after: 0.4) {
  // executed on the background queue
  // 400 ms after the first block completes
}

Cancel Blocks

let computeThings = Async.background {
  computeSomethingBig()
}

let computerOtherThings = computeThings.background {
  // this sucker is about to get cancelled
}

Async.main {
  computeThings.cancel() // this won't do anything

  // you can only cancel blocks that haven't yet started executing!

  computerOtherThings.cancel()
  // this second block *would* in fact get cancelled,
  // since it hasn't started executing yet
}

More info about Async can be found at git.io/async

Topics

#34: Swift Generics 🐦

Topics

Generics were new to many iOS developers when they debuted in Swift 1.0 last year. They are a way of specifying parts of your code can work with different kinds of objects of a given type. but you don't want to hardcode one single type. What if you wanted to say something like “this function accepts any object of some type and returns an object of that same type, but I don't care what type that is.” It ends up looking like this:

func aGenericFunction<SomeType>(thing: SomeType) -> SomeType {
  return thing
}

aGenericFunction("Hello") // returns the String "Hello"

Whoa! Where did those < > brackets come from? That's how you tell the Swift compiler that you are writing a generic function. The SomeType can be any name you want, and you can use it in your code in place of a concrete type. What if you wanted your new generic function to only accept types that conformed to the a protocol? Simple:

func aGenericFunction<T : Requestable>(thing: T) -> T {
  return thing
}

Functions are great, but what about types themselves? The concepts behind Generics work exactly the same there. Let's look at this by making a generic DataSource struct that will power a table view controller:

struct DataSource<ObjectType> {
  var objects = [ObjectType]()

  func objectAtIndex(index: Int) -> ObjectType {
    return objects[index]
  }
}

Now we can add a property to our view controllers like this:

class SpaceshipsViewController : UITableViewController {
  var dataSource = DataSource<Spaceship>()
}

Topics

#33: Photos Framework Basics 🗻

Topics

The Photos Framework is the modern equivalent to the old Assets Library Framework. It's how you access and interact with the user's photo library on iOS and OS X. Here are a few examples of things you can do with it:

Retrieve All Albums

let fetchOptions = PHFetchOptions()

let albums = PHAssetCollection.fetchAssetCollectionsWithType(
  .Album,
  subtype: .Any,
  options: fetchOptions
)

Mark a Photo As A Favorite

PHPhotoLibrary.sharedPhotoLibrary().performChanges({
  let request = PHAssetChangeRequest(forAsset: photo)
  request.favorite = true
}, completionHandler: { (successful, error) in })

Changes must happen asynchronously when working with the Photos Framework so we use a PHAssetChangeRequest to mark a photo as a favorite.

Retrieve an Image at Specific Size

let manager = PHCachingImageManager()

manager.requestImageForAsset(asset,
  targetSize: CGSizeMake(50.0, 50.0),
  contentMode: PHImageContentMode.AspectFill,
  options: nil,
  resultHandler: {
    (image: UIImage?, userInfo: [NSObject : AnyObject]?) in
    // yay image!
  }
)

We didn't specify any options there, but you can use the PHImageRequestOptions to customize if you'd like to allow loading images over the network, as well as get a progress callback as they are downloaded.

Topics

#32: Core Image Basics 🌄

Topics

CoreImage has long been a staple on OS X, and was added to iOS a few years ago. It's an incredibly feature-packed image processing API that can apply just about any type of filter or image manipulation you can dream of. Let's take a look at applying a simple color tint to an image:

func applyFilterToImage(image: UIImage) -> UIImage {
  guard let inputImage = image.CIImage else { return image }

  let tintColor = CIColor(red: 0.55, green: 0.33, blue: 0.22)

  let filter = CIFilter(
    name: "CIColorMonochrome",
    withInputParameters: [
      "inputImage" : inputImage,
      "inputColor" : tintColor,
      "inputIntensity" : 1.0
    ]
  )

  return UIImage(CIImage: filter!.outputImage)
}

CoreImage works on CIImages not UIImages, so we convert our image to a CIImage, and use guard since the CIImage property on UIImage is optional. CoreImage also doesn't use UIColor, so we create a CIColor instead.

Then the fun part, we create our filter by name. CoreImage has literally 100's of different filters available, and instead of subclasses, you instantiate them by name.

CIFilters also don't have explicit class properties, you instead supply values for "Input Parameters" on your filter.

Lastly, we apply the filter by simply asking for it's outputImage.

Topics

#31: CloudKit Operations ⛅️🏥

Topics

Diving deeper into CloudKit today we'll look at some more advanced ways of interacting with your app's data using operations. CloudKit's NSOperation-based API allows for things like batch saves/changes, ‘paging' through data and more.

Here's a few of the things you can do:

Advanced Querying

let predicate = NSPredicate(value: true)
let query = CKQuery(
  recordType: "Spaceship", 
  predicate: predicate
)

let operation = CKQueryOperation(query: query)

operation.desiredKeys = ["topSpeed", "name"]
operation.resultsLimit = 30

let container = CKContainer.defaultContainer()
let publicDB = container.publicCloudDatabase

publicDB.addOperation(operation)

Batch Saves/Changes

Here I'm using CKModifyRecordsOperation to seed my app with example data during development.

var objects = [Spaceship]()

for i in 0...100 {
  objects.append(Spaceship(
    name: "Test Spaceship"
  ))
}

let operation = CKModifyRecordsOperation(
  recordsToSave: objects.map { $0.toRecord() },
  recordIDsToDelete: nil
)

publicDB.addOperation(operation)

Paging

CKQueryOperation‘s queryCompletionBlock provides an optional CKQueryCursor. You can store this and supply it to your next query to load the next logical 'page' of records.

Progress

One of the great things about CKModifyRecordsOperation and CKQueryOperation is their ability to report progress at a per-record level.

Topics

#30: UI Testing 🚸

Topics

Testing is incredibly important in ensuring our app arrives to our users in as good a state as possible.

Xcode has had great unit testing support for a few releases now, but testing our UI has always been a challenge.

Instruments added support for it a while back, but it’s never felt like a first class citizen... Until now!

Xcode 7 brings us an awesome, fully baked set of UI testing tools. We can even make Xcode write much of your test code for us by simply interacting with our app.

Here's an example test created using the default Master-Detail template in Xcode. It presses the add button 3 times, then verifies that 3 cells now exist in the table view:

func testExample() {
    let app = XCUIApplication()

    let masterNavigationBar = app.navigationBars["Master"]
    let addButton = masterNavigationBar.buttons["Add"]

    addButton.tap()
    addButton.tap()
    addButton.tap()

    XCTAssert(app.tables.childrenMatchingType(.Cell).count == 3)
}

Recording UI Tests couldn't be easier. We just write an new empty test function, put our cursor in the middle of it, and press record.

UI Testing uses Accessibility under the hood to identify and retrieve UI elements at runtime. Just one more reason to make our apps as accessible as possible!

Page 35 of 38