Weekly Sponsor: Hired 🏒

My continued thanks to the folks at Hired for sponsoring this week's bites. Finding a good job can be a daunting task. Today we'll take a look at how we can use Hired to save our sanity, and find a great job in the process. Let's dive in.

We'll start by entering our email and answering some basic questions:

That's it. Hired will then work with 2,500 pre-screened companies (both big and small) in 12 major metro areas to try find us a great job. Software engineers and designers on Hired can get 5+ job offers in a single week. Each offer will include salary and equity details upfront.

If we get a job through Hired, they'll give us a $2,000 "thank you" bonus! If we use the link littlebitesofcocoa.com/hired to sign up, Hired will double that, giving us a $4,000 bonus when we accept a job!

More Reasons to Use Hired

Full time or contract opportunities are available
View offers and accept or reject them without talking to anyone.
Completely FREE + no obligations ever.

Hired is the real deal. They have solved some very real problems and fixed this historically messy process. Looking for a job? Don't waste time, sign up for Hired today so you can get back to work!

It's another Fastlane Friday here on LBOC. Today we're looking at deliver, another awesome tool in the Fastlane suite. It can save us tons of time and effort when it comes to uploading our app's metadata, screenshots and binary to iTunes Connect. Let's take it for a spin:

We'll start by installing deliver:

gem install deliver

Then we'll setup deliver for our project by cd'ing into its directory and running:

deliver init

We'll be asked for our iTunes Connect login and then our app's App ID. What happens next it down-right magical .

deliver will download all our app's metadata (name, description, keywords, release notes, icons, etc.), by language, from iTunes Connect as text files. Then it will organize them into text files inside a new metadata directory. Now we can edit these anytime and run deliver to update iTunes Connect with the edited values. Nice!

In addition to our app's metdata, deliver will also download all our app's screenshots (by language and device) and organize them into a new screenshots directory for us. Again, we can change/update them on disk then simply run deliver again and they'll be uploaded to iTunes Connect.

It gets better. Why don't we just do everything at once and update our app's metadata and screenshots, upload its binary and submit it for review all at once? With deliver, all it takes is:

deliver --ipa "Spaceships.ipa" --submit_for_review

Believe it or not, deliver can do even more. We can pass in tons of options for configuring things like automatic release vs. manual, price tiers, add pre-defined answers to all those compliance questions we're asked during submissions, and more. We can also create a Deliverfile to easily reuse our options every time.

More info about deliver can be found at git.io/fastlane-deliver

Interface Builder is awesome. Assembling our interfaces in it can be a great way to work. Sometimes though, we want to create custom views that we draw and manage ourselves in code. It'd be nice if these custom views could work just as well inside Interface Builder as they do in our code. Today we'll look at two awesome features that debuted with Xcode 6 called @IBInspectable and @IBDesignable. Let's dive in:

@IBInspectable var borderWidth: CGFloat = 0 {
  didSet { layer.borderWidth = borderWidth }
}

When crafting a custom view, we can make a property β€œinspectable” in Interface Builder by prefixing its definition with @IBInspectable. Now, when we drag an instance of our custom view onto the canvas in Interface Builder we'll be able to change our properties with native controls!

This technique even works with extensions! Tired of not being able to configure things like a UIButton's borders in IB? Let's add a tiny extension to UIButton to make it more inspectable:

extension UIButton {
  @IBInspectable var borderWidth: CGFloat {
    get { return layer.borderWidth }
    set { layer.borderWidth = borderWidth }
  }
}

This is great so far, but what we really want is to be able to see the effects of our property edits live in the IB canvas. We can prefix any view's definition with @IBDesignable to tell IB to render the view, live, right on the canvas.

@IBDesignable
class SpeedometerView : UIView {
  var pieChartView: PieChartView

  @IBInspectable var meterColor: UIColor = UIColor.yellowColor() {
    didSet { pieChartView.segmentColor = meterColor }
  }

  // ...

When we combine the two techniques we unlock a much more intuitive (and fast) way to build custom views. We can even implement a special function called prepareForInterfaceBuilder() on our view where we can configure it with example data that will be drawn only when it's rendered in IB.

If anything goes wrong, we can debug our view by setting a breakpoint like normal, then choosing Editor > Debug Selected Views from Xcode's menu.

With additions like UIStackView, Auto Layout has matured into quite a powerful system for managing how our views are arranged on screen. When things go wrong though, it can sometimes be difficult to diagnose the specific cause of the issue. Today we'll look at a few techniques for making sense of the madness caused by... Auto Layout Bugs! 🐞πŸ”ͺπŸ”ͺπŸ”ͺ

Most issues arise from "Unsatisfiable Layouts". That's fancy-talk for "two or more constraints you gave Auto Layout conflict with each other." The solution will of course be different in every case, but here's some sensible things to ask ourselves when an error first occurs:

  • Is translatesAutoresizingMaskIntoConstraints set to false on the views we're adding constraints to?

  • Are the priorities of each of constraint, as well as content hugging and compression resistance priorities (Bite #69) what we expect them to be?

  • "Can this required constraint work at a 999 priority?" Remember, Auto Layout will try to get as close to our desired result as possible, while still satisfying all other constraints.

Identifiers

constraint.identifier = "image-fixed-width"

Identifiers help us more easily spot the important bits in those giant log outputs Auto Layout loves to show us. They can be added in code or in Interface Builder.

Log Specific Constraints

profileHeaderView.constraintsAffectingLayoutForAxis(.Vertical)

When debugging complex layouts, it can sometimes be helpful to look at only the constraints involving a specific problem view or area. We can use this function to grab an array of the constraints affecting a particular axis. Neat.

Brain Surgery

When all else fails, don't be afraid to go in and start temporarily commenting-out or disabling constraints then observe the results. This can often lead to unexpected insights into how constraints are behaving.

Practice!

A great way to fight Auto Layout issues is to try to catch them before they happen. That means becoming more familiar with Auto Layout, which means practice. For example: When a question or issue comes up, create a new 'dummy' Xcode project. Throw some views and constraints in there and try it out. Tweak some priorities, observe their effects in the isolated environment. When in doubt, try it out!

Topics

#117: Pace & Cadence πŸ‘£

Topics

We covered retrieving step counts for a user using CMPedometer back in Bite #41. Today we're going to look at some interesting additions to CMPedometer in iOS 9. They involve determining at what rate the user is moving, as well as what kind of "cadence" their movement has. Let's dive in:

First let's look at what pace and cadence are referring to. In Core Motion's world. "Pace" represents the amount of time (in seconds) it takes a user to go one meter. "Cadence" on the other hand describes the number of steps the user is current taking each second.

Now that we know what we're working with, let's wire up a simple view controller to display our current pace and cadence. We'll start by adding two functions to start and stop pedometer updates. We'll star them in viewWillAppear: and stop them in viewWillDisappear:.

We'll make sure cadence and pace are supported on this device then start updates just like we did in Bite #41. Since we're looking for real-time data instead of historical data, we'll pass in a new NSDate (essentially "now"). Then, we'll make sure we've gotten back valid data and set the text of our labels. To test it out, we can install the app on our phone, and walk

around a bit. Success! It's fun to imagine the possibilities here: A music app that plays different songs depending on how fast a user is jogging, or an app that estimates your arrival time based on your current pace, neat!

func startUpdatingPaceAndCadence() {
  guard CMPedometer.isCadenceAvailable() && CMPedometer.isPaceAvailable() else {
    showPaceAndCadenceUnsupportedAlert(); return
  }

  pedometer.startPedometerUpdatesFromDate(NSDate()) { data, error in
    guard let data = data where error == nil else {
      self.showErrorRetrievingDataAlert()
      return
    }

    if let pace = data.currentPace, let cadence = data.currentCadence {
      self.paceLabel.text = pace.stringValue
      self.cadenceLabel.text = cadence.stringValue
    }
  }
}

Download the example we built here at j.mp/bite117 to try it out on your own device.

Topics

#116: Instructions πŸŽ“

Topics

Teaching users how to use our apps is incredibly important. We should strive to do this through familiar UI patterns, intuitive flows, good error handling, etc. Sometimes though, we just need to explain what's going on.

Today we'll look at a fantastic new project from FrΓ©dΓ©ric Maquin called Instructions that can help us do just that. It's a library for easily adding "coach marks" to our apps. Let's dive in.

We'll start by adding a new CoachMarksController property to our view controller, and setting it as the data source for it:

let coachMarksController = CoachMarksController()

override func viewDidLoad() {
  super.viewDidLoad()
  coachMarksController.datasource = self
}

Next, we'll extend our view controller so it conforms to the CoachMarksControllerDataSource protocol. Instructions is highly customizable. It has support for custom body and arrow views, positions, highlights, and more. Let's keep things simple here and add a regular coach mark for a button in our app.

func numberOfCoachMarksForCoachMarksController(coachMarksController: CoachMarksController) -> Int {
  return 1
}

func coachMarksController(coachMarksController: CoachMarksController, coachMarksForIndex index: Int) -> CoachMark {
  return coachMarksController.coachMarkForView(launchButton)
}

func coachMarksController(coachMarksController: CoachMarksController, coachMarkViewsForIndex index: Int, coachMark: CoachMark) -> (bodyView: CoachMarkBodyView, arrowView: CoachMarkArrowView?) {
  let coachViews = coachMarksController.defaultCoachViewsWithArrow(true, arrowOrientation: coachMark.arrowOrientation)

  coachViews.bodyView.hintLabel.text = "This button launches the spaceship, proceed with caution!"
  coachViews.bodyView.nextLabel.text = "Got it"

  return (bodyView: coachViews.bodyView, arrowView: coachViews.arrowView)
}

It's good practice to let users who don't need our coach marks skip them. Instructions has us covered. We can setup a "skip view" on our controller. We'll use the default one, which shows in the nav bar:

let skipView = CoachMarkSkipDefaultView()
skipView.setTitle("Skip", forState: .Normal)
coachMarksController.skipView = skipView

Finally we'll start the whole flow in viewDidAppear:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  coachMarksController.startOn(self)
}

Success! When we launch our app, the coach marks are now shown:

More info about Instructions can be found at git.io/instructions

Weekly Sponsor: Hired 🏒

A big thanks to the folks at Hired for sponsoring this week's bites. Finding a good job can be a daunting task. Today we'll take a look at how we can use Hired to save our sanity, and find a great job in the process. Let's dive in.

We'll start by entering our email and answering some basic questions:

That's it. Hired will then work with 2,500 pre-screened companies (both big and small) in 12 major metro areas to try find us a great job. Software engineers and designers on Hired can get 5+ job offers in a single week. Each offer will include salary and equity details upfront.

If we get a job through Hired, they'll give us a $2,000 "thank you" bonus! If we use the link littlebitesofcocoa.com/hired to sign up, Hired will double that, giving us a $4,000 bonus when we accept a job!

More Reasons to Use Hired

Full time or contract opportunities are available
View offers and accept or reject them without talking to anyone.
Completely FREE + no obligations ever.

Hired is the real deal. They have solved some very real problems and fixed this historically messy process. Looking for a job? Don't waste time, sign up for Hired today so you can get back to work!

Topics

#115: Generating Thumbnails from Videos πŸ“Ό

Topics

It's quite common for an app to need to display one or more thumbnails (small still-image previews) of what's in a video. However, depending on where the video is coming from, we might not have easy access to pre-made thumbnail(s) for it. Let's look at how we can use AVAssetImageGenerator to grab our own.

We start with a simple NSURL for the video, this can be local or remote. We'll create an AVAsset with it and that to a new AVAssetImageGenerator object. We'll configure the generator to apply preferred transforms so our thumbnails are in the correct orientation.

import AVFoundation

if let asset = AVAsset(URL: videoURL) {
  let durationSeconds = CMTimeGetSeconds(asset.duration)
  let generator = AVAssetImageGenerator(asset: asset)

  generator.appliesPreferredTrackTransform = true

  let time = CMTimeMakeWithSeconds(durationSeconds/3.0, 600)
  var thumbnailImage: CGImageRef

  generator.generateCGImagesAsynchronouslyForTimes([NSValue(CMTime: time)]) {
    (requestedTime: CMTime, thumbnail: CGImage?, actualTime: CMTime, result: AVAssetImageGeneratorResult, error: NSError?) in
    self.videoThumbnailImageView.image = UIImage(CGImage: thumbnail)
  }
}

Then we just create a new CMTime that describes at which point in the video we'd like to capture a thumbnail image. (We use 1/3 of the way through here. Finally, we'll kick off the actual thumbnail generation process and wait for it to complete. We only requested a single frame here, but we can ask for as many as our app needs. The closure will be called once per generated thumbnail.

Topics

#114: Creating New Apps with Produce πŸ“

Topics

Starting new projects can be incredibly fun. Performing all the tasks that aren't building our app can be incredibly not-so-fun. Today we'll continue our look at the Fastlane suite of tools (from Bite #110) by checking out Produce, a command-line tool for creating and managing apps on iTunes Connect and the Developer Portal. Let's take a look.

Let's make a new app. Before we do though, we'll install produce:

gem install produce

Now, let's create our app, we'll run:

produce

Then we'll be asked for 4 values. We'll enter each when prompted (We could also specify these as arguments to the produce command).

Your Apple ID Username: **hidden**
App Identifier (Bundle ID): com.magnus.littlebitesofcocoa
App Name: Little Bites of Cocoa
Initial version number (e.g. '1.0'): 1.0

Produce will work its magic and when it's done, our app will have been completely created and setup on both iTunes Connect and the Developer Portal. Very cool.

Capabilities

Produce can also help us easily enable (or disable) things like Associated Domains, iCloud, Passbook, etc. Let's enable CloudKit for our app:

produce enable_services --icloud cloudkit -a com.magnus.littlebitesofcocoa

Nice! So much quicker than clicking around the Developer Portal.

App Groups

Last but not least, we'll be creating extensions for app that share data so let's create a new App Group and put our new app into it:

produce group -g group.littlebitesofcocoa -n "Little Bites of Cocoa"
produce associate_group -a com.magnus.littlebitesofcocoa group.littlebitesofcocoa

More info about Produce can be found at git.io/produce

We covered how to analyze the performance of our app using the Time Profiler Instrument in Bite #68, now we'll take a look at how to do the same for how quickly our app draws its content to the device's display. Let's get started.

First, the basics: iOS wants to render content to the display 60 times per second. If our code can't keep up with that speed, some frames won't be drawn, and our app's UI will appear to lag and "skip" as things move around on screen. Even the slightest dip below 60 frames per second can make an app feel quite sluggish. One of the main places in our code this can be a challenge is when scrolling complex table and collection views. Let's look at how Instruments can help us identify and track down the cause of slow rendering views in our app:

We'll start Profiling like in Bite #68. When Instruments launches, we'll select the Core Animation Instrument and click Profile. Unlike before, we'll see two different gauges at the top of our screen.

We're starting to unlock some of the power of Instruments here. The Core Animation Instrument measures our app's rendering performance simultaneously with our code's performance. We'll scroll our table view for a bit then find a part of the graph where we dip below 60 fps. We'll drag to select it, then select the Time Profiler gauge. Now we can use the techniques we learned in Bite #68 to track down the offending slow function(s).

Now the really fun stuff: We can click the in the lower right-hand sidebar to show the Debug Options panel. These options alter how our app is drawn in real-time to help find problems. Here's a couple of the most helpful ones:

Color Blended Layers will tint views red that are being blended. Try to avoid non-opaque views or views with a clear background color.

Color Offscreen-Rendered Yellow will tint views that have been rendered twice - once on and once off-screen, yellow.

Remember, speed is a feature. Rendering speed is no different. Fast drawing means happy users!

Page 26 of 38