In iOS 9, UICollectionView learned to interactively re-order cells. Let's take a look.

Before we dive in, one quick note: If we just need re-orderable cells, adopting this functionality can be incredibly simple:

override func collectionView(collectionView: UICollectionView, moveItemAtIndexPath source: NSIndexPath, toIndexPath destination: NSIndexPath) {
  let person = crew.removeAtIndex(source.item)
  crew.insert(person, atIndex: destination.item)
}

UICollectionViewController's new property called installsStandardGestureForInteractiveMovement defaults to true, so once we implement delegate function above, we're good to go.

To customize things further, we'll need to manually call UICollectionView's new interactive movement functions. Let's look at a very rough idea of how we might make the picked up cell "hover" and the others "wiggle" like iOS's home screen.

func longPressed(gesture: UILongPressGestureRecognizer) {
  let location = gesture.locationInView(collectionView)
  movingIndexPath = collectionView.indexPathForItemAtPoint(location)

  switch(gesture.state) {
  case .Began:
    guard let indexPath = movingIndexPath else { break }
    setEditing(true, animated: true)
    collectionView.beginInteractiveMovementForItemAtIndexPath(indexPath)
    pickedUpCell()?.stopWiggling()
    animatePickingUpCell(pickedUpCell())
  case .Changed:
    collectionView.updateInteractiveMovementTargetPosition(location)
  case .Ended:
    collectionView.endInteractiveMovement()
    animatePuttingDownCell(pickedUpCell())
    movingIndexPath = nil
  default:
    collectionView.cancelInteractiveMovement()
    animatePuttingDownCell(pickedUpCell())
    movingIndexPath = nil
  }
}

override func setEditing(editing: Bool, animated: Bool) {
  super.setEditing(editing, animated: true)
  startWigglingAllVisibleCells()
}

We'll also start/stop wiggling when we dequeue cells. Lastly, we'll apply the same changes that are in animatePickingUpCell to the cell's layout attributes. To do this we can subclass UICollectionViewFlowLayout and override layoutAttributesForInteractivelyMovingItemAtIndexPath.

After all that's done, this is the outcome:

Here's a direct link to this example video, just in case.

Download the project at j.mp/bite104 to see a complete working example.

Update on March 22nd, 2019: Thanks to reader Le Zeng, the project download now supports Swift 4.2. Thanks so much!

Topics

#103: UIStackView in Code πŸš₯πŸ“

Topics

We covered UIStackView when it was first announced, way back in Bite #16. Today we'll look at how to use it in code to build a section header view:

We'll be stacking 3 regular views: a UIImageView, then a UILabel, and finally a UIButton

Nothing special about them besides fonts/colors, so we've created them off camera.

Here's our first attempt:

stackView = UIStackView(arrangedSubviews: [imageView, label, button])

stackView.axis = .Horizontal
stackView.translatesAutoresizingMaskIntoConstraints = false

addSubview(stackView)

addConstraintsWithVFL("|[stackView]|")
addConstraintsWithVFL("V:|[stackView]|")

Like in Bite #99, we'll use Auto Layout Visual Format Language to pin the stack view's edges to its superview. Look at that, not bad for our first try!

Those views are close talkers, let's add some spacing:

stackView.spacing = 10.0

Ack! What the heck is going on here?!

Let's break it down: In the default β€˜Fill' distribution mode, if views don't naturally fill the axis of the stack view, the stack view will resize one (or more) according to their hugging priority (covered in Bite #69).

We'll solve our issue by setting a low hugging priority on our label, signaling to the stack view that it be the one to stretch, not our image view.

titleLabel.setContentHuggingPriority(1, forAxis: .Horizontal)

Nice, much room-ier! Finally, let's use one more trick to make the stack view add some padding around its edges:

stackView.layoutMarginsRelativeArrangement = true
stackView.layoutMargins = UIEdgeInsetsMake(7.0, 7.0, 7.0, 7.0)

Success, matches our design perfectly! Download the project at j.mp/bite103

In Bite #101 we started working on a custom camera view controller.

Today we'll complete it by adding a way for users to capture a photo and do something with it. We'll start by making it easy to use. We'll make the whole screen a capture button by adding a tap gesture recognizer to our view:

let tapGR = UITapGestureRecognizer(target: self, action: "capturePhoto:")
tapGR.numberOfTapsRequired = 1
view.addGestureRecognizer(tapGR)

Next, we want to ask our output to capture a still image. Before we can, we'll need an AVCaptureConnection.

Connections were already implicitly created for us by our session. They represent the conceptual pipes that move data between inputs and outputs.

We grab a connection and use it to ask our output to capture a still image, asynchronously:

func capturePhoto(tapGR: UITapGestureRecognizer) {
  guard let connection = output.connectionWithMediaType(AVMediaTypeVideo) else { return }
  connection.videoOrientation = .Portrait

  output.captureStillImageAsynchronouslyFromConnection(connection) { (sampleBuffer, error) in
    guard sampleBuffer != nil && error == nil else { return }

    let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
    guard let image = UIImage(data: imageData) else { return }

    self.presentActivityVCForImage(image)
  }
}

In the closure, we'll do a safety check then convert the CMSampleBuffer we've been given into an NSData then a UIImage.

Lastly, we'll use UIActivityViewController (covered in Bite #71) to allow the user to do something with their new photo.

Download the project we built in Bites #101 & #102 at j.mp/bite102

We looked at allowing our users to capture photos/videos using UIImagePickerController in Bite #83. Now we'll take things to the next level by starting to create our own custom camera view controller. Today we'll get all the plumbing wired up and get the preview on the screen. Let's get started.

func setupSession() {
  session = AVCaptureSession()
  session.sessionPreset = AVCaptureSessionPresetPhoto

  let camera = AVCaptureDevice
    .defaultDeviceWithMediaType(AVMediaTypeVideo)

  do { input = try AVCaptureDeviceInput(device: camera) } catch { return }

  output = AVCaptureStillImageOutput()
  output.outputSettings = [ AVVideoCodecKey: AVVideoCodecJPEG ]

  guard session.canAddInput(input) && session.canAddOutput(output) else { return }

  session.addInput(input)
  session.addOutput(output)

  previewLayer = AVCaptureVideoPreviewLayer(session: session)
  previewLayer!.videoGravity = AVLayerVideoGravityResizeAspect
  previewLayer!.frame = view.bounds
  previewLayer!.connection?.videoOrientation = .Portrait

  view.layer.addSublayer(previewLayer!)

  session.startRunning()
}

We'll start with the "single view" template in Xcode. There are a number of different objects we'll need to setup and glue together, so we'll go into our view controller and add a function called setupSession. We'll call this in viewWillAppear(animated:).

First we'll instantiate an AVCaptureSession. It's sort of the central hub of all this. We can configure it with a number of different presets. We'll use a preset for taking high quality still photos. Now, our session needs some inputs and outputs.

We'll use defaulDeviceWithMediaType and pass in Video to get the default hardware device for capturing and recording images on the user's device (usually the the back camera). Then we'll try to create an AVCaptureDeviceInput from the device. Next up, an output.

Capture sessions can return us data in all sorts of interesting ways: Still images, videos, raw pixel data, and more. Here we'll set up an AVCaptureStillImageOutput and ask it for JPEG photos. We'll do one more safety check then add both our input and output to our session.

Finally, let's display our camera so the user can see what they're photographing.

We'll pass our session into a new AVCapturePreviewLayer and add it to our view. Then we just need to start the session. If we run the app we'll see it's starting to look like a camera, neat!

Tomorrow, we'll finish up by adding the ability to actually capture some photos.

Update: Part 2 is right here

Weekly Sponsor: imgix πŸŒ…

My continued thanks to imgix for sponsoring this week's Bites! imgix is real-time image resizing as a service. They allow us to resize, crop, and process images on the fly, simply by changing their URLs. Let's take a look.

There's client libraries for all sorts of languages and environments, but for basic usage we don't even really need one. That's part of what makes imgix so awesome, we can play around with size, crops, effects, even draw text or watermarks on images just by changing some URL parameters.

They use our existing image storage locations as a source (supports public web folders, S3 buckets, and Web Proxies). We can configure multiple image sources, set caching times, and set custom domains on their site. Response times for images average around 70ms. πŸš€

Let's look at using imgix in an iOS or OS X app. After signing up, we can use imgix's great Objective-C/Swift client library maintained by Sam Soffes. It's called imgix-objc (more info can be found at git.io/imgixobjc).

We'll create a client, and our first image URL. This will generate a signed image URL we can load anywhere.

let client = IGXClient(
  host: "littlebitesofcocoa.imgix.net",
  token: "REDACTED"
)

let imageURL = client.URLWithPath("bites/060-creating-a-cocoapod.png")

If we load the generated imageURL into a UIImageView we'll see our image.

We'll test out image processing by simply inverting the colors of our image:

client.invert = true
client.URLWithPath("bites/060-creating-a-cocoapod.png")

Boom, say hello to Little Bites of Cocoa night mode. πŸŒƒ

Fun! A more realistic use case would be handling Retina displays. We can store our images' original size in full-resolution, then use imgix's awesome dpr parameter (plus a width and height) to serve up perfect, crisp images on any iOS device (or any display on just about any device for that matter).

Stop maintaining your frankenstein ImageMagick setup and let imgix handle it. It's totally free to get started. Use the link littlebitesofcocoa.com/imgix so they know you heard about them here. πŸ‘

Topics

#100: Just Getting Started πŸš€

Topics

At one point or another, we've all heard some form of "Oh you make apps? I'm interested in that, but I'm having trouble just getting started". Today we'll take a look at the specific steps to create our first app and where to go from there. Let's dive in:

We'll need a Mac. We'll open the Mac App Store and search for "Xcode", then install it. (It's free).

Once it's installed, we'll open Xcode. We'll allow it to verify it's installation, and install any components it needs to.

Then we'll go to the File menu and choose File > New > Project… We'll leave the Master-Detail iOS Application selected and click Next. We'll give our new app a name: "Foodstagram". We'll enter "com.somename" as the organization identifier, select Swift as the language, and click Next. Finally, we'll choose where to save our new project, then click Create.

When our project opens, we'll hit the little β–Ά button in the top left corner. Xcode will build our new app and launch it in the Simulator.

Hey it's an app! Hit the + button to add some entries, then edit to delete them.

Congratulations, our new app-venture (sorry, had to) has begun! Next, we need to learn how to learn. When first starting out, much of our time will be spent Googling for things we don't know yet. This is completely normal. Let's start right now.

We'll look at going from idea to implementation. Our idea: "The top bar of the app should be green."

We'll Google for how to do it, using as specific a phrase as we can given what we know so far:

"ios swift change top bar background color".

We'll read the first result from stackoverflow. The first answer contains some possibly helpful code snippets, but we'll read a few more to be sure. It seems the rest of the answers all suggest a similar piece of code talking about "appearance". Sounds promising. One even mentions "color" and "green", so we'll copy it for pasting later:

UINavigationBar.appearance().barTintColor = UIColor.greenColor()

Where do we even put this code? Back to Google.

We'll keep reading our search results until we find a coderwall post from Eranga Bandara that answers our question: "Add following code to didFinishLaunchingWithOptions function in AppDelegate.swift".

Perfect, we'll do just that and click the AppDelegate file in Xcode, then paste our copied code at the end of the specified function.

We can check if it worked by clicking the β–Ά button again. Success! Our top bar is now green!

We can't learn everything this way, so our next step is to try to find something to teach us the rest of the basics. There's plenty of great resources out there, including one that's just getting started. πŸ˜‰πŸ«

In Bite #98, we learned how to work with Auto Layout in code. While this helped us gain a ton of flexibility, our code is now a bit... verbose. For simple layouts this won't be a big deal, but for anything complex we would quickly rack of hundreds of lines of code for just our layout. Today we'll look at one solution for this issue: Auto Layout's Visual Format Language. Let's take a look.

Before jumping into code, let's look at the Visual Format Language itself. It's essentially just a string that the system parses and turns into an array of NSLayoutConstraint objects:

"|-[header]-|"

The two pipes on the outside represent the superview. The dashes represent spacing, in this case, the system-standard spacing. Views are described with a name in brackets.

When parsed, this would create 2 new constraints: 1 leading and 1 trailing. It would keep the header the standard spacing away from the left and right edges of the superview. To let the system know our views by name we put them in a dictionary:

let views = [ "header": header ]

If we wanted to use a specific spacing amount we could describe that within dashes on the edges of our Visual Format Language:

"|-20-[header]-20-|"

That's fine for simple spacing, but what if we needed to reference lots of numeric values? We can name those and put them in a dictionary just like our views:

"|-edgeSpacing-[header]-edgeSpacing-|"

Now that we have a basic understanding, we can add our first set of constraints:

container.addConstraints(
  NSLayoutConstraint.constraintsWithVisualFormat(
    "|-[header]-|",
    options: NSLayoutFormatOptions(rawValue: 0),
    metrics: nil,
    views: [ "header": header ]
  )
)

We can prepend a H: or V: to describe constraints along the horizontal or vertical axes. If we don't prepend either, horizontal is assumed.

We can use parenthesis to describe a view's width or height:

"V:|[header(180)]"

Let's add 2 more constraints to pin our header to the top, and give it a fixed height:

container.addConstraints(
  NSLayoutConstraint.constraintsWithVisualFormat(
    "V:|[header(headerHeight)]",
    options: NSLayoutFormatOptions(rawValue: 0),
    metrics: [ "headerHeight": 180.0 ],
    views: [ "header": header ]
  )
)

We've only scratched the surface of what's possible with Visual Format Language. In the future we'll look at configuring inequalities, constraint priorities, and more.

Topics

#98: Auto Layout in Code πŸ“πŸ“

Topics

Auto Layout works great in Interface Builder, but it's often helpful to have the flexibility and clarity of wiring up constraints in code. Let's dive in.

We'll add a view and set translatesAutoresizingMaskIntoConstraints to false. Normally Interface Builder does this automatically under the hood, but since we're working in code we'll need to set it ourselves. Don't want any funky autoresizing constraints in there meddling around.

let logo = UIView()

logo.translatesAutoresizingMaskIntoConstraints = false

container.addSubview(logo)

When learning to work with Auto Layout in code, it can be helpful to remember that constraints are essentially just linear equations:

viewA.property = multiplier * viewB.property + constant

To express centering a view inside it's superview, we could write it as:

view.center.x = 1.0 * superview.center.x + 0.0
view.center.y = 1.0 * superview.center.y + 0.0

Let's look at how to express the same thing (centering our logo view inside it's superview) using NSLayoutConstraint objects in code:

container.addConstraints([
  NSLayoutConstraint(item: logo, attribute: .CenterX, relatedBy: .Equal, toItem: container, attribute: .CenterX, multiplier: 1, constant: 0),
  NSLayoutConstraint(item: logo, attribute: .CenterY, relatedBy: .Equal, toItem: container, attribute: .CenterY, multiplier: 1, constant: 0)
])

Whew! That's a long constructor. The neat part though, is from left-to-right it almost reads like the equations from before. Lastly, we'll assign a fixed size to our logo view. Since there's only 1 view involved, the equation is much simpler:

view.height = 0.0 * nothing + constant.

And finally, in code this looks like:

container.addConstraints([
  NSLayoutConstraint(item: logo, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 115),
  NSLayoutConstraint(item: logo, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 70)
])

Topics

#97: EventKit Alarms πŸ“…β°

Topics

EventKit Alarms are how we configure notifications and alerts that will be triggered to remind the user about calendar events or reminders.

Like in Bite #96, we'll use Timepiece (Bite #3) to help us compose dates. We'll create an event for when our cookies will be done baking. The event will start in 12 minutes, and we'll add a new EKAlarm to it that triggers 5 seconds before the event. Alarms can be created with at absolute or relative times.

let e = EKEvent(eventStore: eventStore)

e.startDate = NSDate() + 12.minutes
e.endDate = e.startDate + 30.seconds
e.calendar = eventStore.defaultCalendarForNewEvents
e.title = "Cookies are Done! πŸͺ"

e.addAlarm(EKAlarm(relativeOffset: -5.0))

try eventStore.saveEvent(e, span: .ThisEvent)

Interestingly, OS X is actually ahead of iOS here. EKAlarm on OS X **has more **properties for configuring a sound to play, an email address to notify and more.

Now when we get to Disneyland, the alarm will remind us to head over to . (Just an example, in real life we'd never need the reminder).

Things get more fun when adding alarms to reminders. Let's finish by adding an alarm to a new reminder that triggers when the user arrives somewhere:

let reminder = EKReminder(eventStore: eventStore)

reminder.title = "Ride Star Toursβ€œ
reminder.calendar = eventStore.defaultCalendarForNewReminders()

let location = EKStructuredLocation(title: "Disneyland")
location.geoLocation = CLLocation(
  latitude: 33.8120918, longitude: -117.9189742
)

let alarm = EKAlarm()

alarm.structuredLocation = location
alarm.proximity = .Enter

reminder.addAlarm(alarm)

try eventStore.saveReminder(reminder, commit: true)

Topics

#96: EventKit Basics πŸ“…

Topics

EventKit is how we access and interact with a user's calendars and events. It has APIs to manage events, reminders, alarms and even participants. Today we'll take a look at the basics. Let's get started.

Authorization

Before doing anything fun, we'll need to check the current authorization status, then request permission if needed.

switch EKEventStore.authorizationStatusForEntityType(.Event) {
case .Authorized:
  fetchCalendars()
case .NotDetermined:
  requestAccessToCalendars()
case .Restricted, .Denied:
  showAccessDeniedAlert()
}

To do anything interesting, we'll need an EKEventStore.

let eventStore = EKEventStore()

Apple suggests we keep a long-lived instance around, so we'll keep ours in a property. Now we can write our function:

eventStore.requestAccessToEntityType(.Event) { (granted, error) in
  guard granted else { return }
  self.fetchCalendars()
}

Fetching Calendars & Events

let calendars = eventStore.calendarsForEntityType(.Event)

Lastly, we'll use the handy function to put new events where the user expects:

We'll use Timepiece (Bite #3) to help us compose dates. Then we'll create a predicate to look for events in the last week, across all the user's calendars.

Adding an Event

let event = EKEvent(eventStore: eventStore)

event.title = "Drop off Carbonite Shipment"
event.startDate = "3031-02-15".dateFromFormat("yyyy-MM-dd")
event.calendar = eventStore.defaultCalendarForNewEvents

try eventStore.saveEvent(event, span: .ThisEvent)
Page 28 of 38