Buddybuild is a CI, CD and user feedback platform built for mobile development teams - sign up for free and start building better apps!

This time of year it's common to have multiple versions of Xcode installed.

Today we're going to learn how to tell our system which version of Xcode's tools to use when working with Xcode from the command line. But first we'll check out a helpful tool to actually install Xcode from command line.

Let's dive in.

The first tool we'll look at can be used to install Xcode versions directly from the command line. It's an alternative to using the Mac App Store (or just managing downloads manually).

It's called xcode-install and it can be found on Github right here.

We can install it with:

gem install xcode-install`

Once that's finished, we can list the versions of Xcode that are available to install from the command line like this:

xcversion list

Which at the time of this writing will print:

8
8.1
8.2
8.2.1 (installed)
8.3 (installed)
8.3.1
8.3.2
8.3.3 (installed)
9 beta 4

By default, xcode-install only prints the last few major versions.

(Sidenote: For fun and nostalgia try running xcversion list --all to print all the available versions of Xcode going all the way back to 4.3 for OS X Lion 😱).

We can install a version like this:

xcversion install "9 beta 4"

We'll be prompted for our Apple Developer credentials which will be stored in our Keychain, and then the version will be download, installed, and moved into place, all without leaving the command line.

Neat!

More info about xcode-install can be found here on Github.

Next we'll need a way to switch to the new version we just installed.

The tool we'll be working with is already installed on our Mac and is called xcode-select.

It's a straightforward, single-purpose utility that essentially controls which path on disk gets run when we run xcrun, xcodebuild, etc. from our command line.

Let's first check out which version of Xcode we're currently using:

xcode-select --print-path

Which (by default) will print:

/Applications/Xcode.app/Contents/Developer

(Note: This is what's printed for versions of Xcode installed from the App Store).

Next, lets take a look at all of the versions of Xcode we currently have installed.

Here we're going to grep for Xcode inside of our /Applications directory:

ls /Applications | grep Xcode

Which (for example) will print:

Xcode-7.3.1.app
Xcode-8.2.1.app
Xcode-8.app
Xcode-9-beta-4.app
Xcode.app

All we need to do to override the default Xcode path is pass the path of one of these Xcode directories in with the --switch flag:

sudo xcode-select --switch /Applications/Xcode-9-beta-4.app

We'll need the sudo since this xcode-select is working on a system-wide level.

Also, note how we've omitted the /Contents/Developer bit from the path, xcode-select will infer that for us).

Now when we run xcrun, xcodebuild, etc. from our command line we'll be using the Xcode 9 Beta version of each tool.

Nice.

Today we're going to take a look at how we can control (and interact with) the iOS Simulator from the command line.

There's no need to install anything new though. The tool we're going to be using is already on our Mac, hiding inside the xcrun command, which gets installed with Xcode.

It's called simctl.

Let's begin!

First thing we'll want to do is tell simctl to print out a list of our current iOS Simulator devices. We can do that by running:

xcrun simctl list devices

This will print a list of all the simulated devices and a UDID value with each:

-- iOS 10.3 --
iPhone 7 (C4481459-5BB1-4CE1-9BE0-CF0FEA351299) (Booted)
iPhone 7 Plus (ADCB6F99-5ADD-49B1-83AE-5391D845C4D0) (Shutdown)
iPhone SE (FB0899C0-5812-492E-80D9-9DE517554C12) (Shutdown)
iPad Pro (9.7 inch) (45F47977-9A03-4DD1-8FD0-289F7936FE98) (Shutdown)
iPad Pro (10.5-inch) (C3C909DC-BF70-4D67-BF7E-A41A0CF4AF56) (Shutdown)
...

We can see by the (Booted) notation next to the first device that simctl has identified the iOS Simulator we had open at the time of running the command.

We can pass that UDID (C4481459-5BB1-4CE1-9BE0-CF0FEA351299) to other commands to target our currently running iOS Simulator.

For shorthand though, we can also just pass the term booted to target the currently running iOS Simulator.

(Note that in Xcode 9 multiple iOS Simulators can be running at once. If we pass booted in this case, simctl will just choose one for us).

Whew. Ok with that introduction out of the way let's try this thing out.

First up, one of the best features of simctl, opening URLs.

To open a URL in the iOS Simulator, from the command line, all we need to run is:

xcrun simctl openurl booted "https://littlebitesofcocoa.com"

If we look at our iOS Simulator after running that, we'll see Safari open up and load the page.

Neat!

Even better, this works just as well with custom URL schemes:

xcrun simctl openurl booted "spaceships://ships/123"

Very cool.

Next, let's add some photos and videos.

We can use the addmedia subcommand to import media to the iOS Simulator:

xcrun simctl addmedia booted ~/Desktop/images/image1.png ~/Desktop/images/image2.jpg ~/Desktop/images/image3.jpg

We can include one or more file paths here. It supports images, videos, and even Live Photos.

After running, the files will be imported and appear in the Photo Library:

Next: iCloud Syncing.

We can explicitly force a sync of iCloud using this command:

xcrun simctl icloud_sync booted

What's nice about this is we'll even get errors printed if (for example) iCloud isn't yet configured in this simulated device:

An error was encountered processing the command (domain=BRCloudDocsErrorDomain, code=2):
The operation couldn’t be completed.
(BRCloudDocsErrorDomain error 2 - Logged out - iCloud Drive is not configured)

This next one's a doozy. We can use simctl to record and stream live video, and capture screenshots of any screen of our iOS Simulator.

First let's grab a screenshot:

xcrun simctl io booted screenshot ~/Desktop/screenshot.png

Neat. Easy enough, next let's try recording a movie:

xcrun simctl io booted recordVideo --type=mp4 ~/Desktop/movie.mp4

Full quality movie file is available here, for the curious.

The recordVideo subcommand's output can be | (piped) into other commands or even to a TCP or UDP socket for live streaming. This works on all iOS, tvOS, and even watchOS Simulators. Very cool.

Last but not least, we can print out the path for app's installation directory on disk:

xcrun simctl get_app_container booted com.spaceships.app
/Users/example/Library/Developer/CoreSimulator/Devices/C4481459-5BB1-4CE1-9BE0-CF0FEA351299/data/Containers/Bundle/Application/49A59051-1404-431C-8B65-B589EC0F6267/Spaceships.app

Nice.

To save us a step, we can | (pipe) that path output to pbcopy to put the value on our clipboard for easy pasting later:

xcrun simctl get_app_container booted com.spaceships.app | pbcopy

There's plenty more that simctl can do. To see the long list of available commands and features, we can run:

xcrun simctl

We can add, remove and even reset/erase simulated devices, interact with a device's pasteboards, launch apps with environment variables and much more.

That's all for now, happy simulating!

-- Update: Tuesday July 25th, 2017 --

Friend of the pod Bites? (and creator of the fantastic Fastlane suite of tools, (Bites)) Felix Krause writes in with a great addition regarding the --json flag:

We can pass the --json (-j for shorthand) flag to any of the simctl commands that print information to print it in JSON format.

This is perfect for | (pipe)-ing to other commands, or for consumption by a host process/program:

xcrun simctl list runtimes --json
{
  "runtimes" : [
    {
      "buildversion" : "14E8301",
      "availability" : "(available)",
      "name" : "iOS 10.3",
      "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-10-3",
      "version" : "10.3.1"
    },
    {
      "buildversion" : "14V243",
      "availability" : "(available)",
      "name" : "watchOS 3.2",
      "identifier" : "com.apple.CoreSimulator.SimRuntime.watchOS-3-2",
      "version" : "3.2"
    }
  ]
}

Topics

#312: Asset Catalog Improvements 🎨

Topics

Asset Catalogs have been around for a few Xcode releases. They're a great way for us to organize and configure graphical assets (among many other things) for our app. Today we'll check out the improvements to Asset Catalogs in Xcode 9. Let's dive in.

First up, colors.

Yep, we can now define and organize named colors inside of an Asset Catalog!

We can select "New Color Set", and then use all of the normal features of Asset Catalogs, including new Wide Gamut support.

Then, in our code we can reference these colors like this:

view.backgroundColor = UIColor(named: "wood")

Neat!

The second big addition we're going to look at is "real" vector-based asset support.

In past Xcode releases, we were able to add image assets to our catalogs with a format of PDF. This worked great, but under the hood, Xcode would simply render our asset at the @1x, @2x, and @3x sizes and save non-vector (i.e. png images) into our app's bundle.

In Xcode 9 however, we're given a beautiful new checkbox called "Preserve Vector Data".

This means that if our image is loaded in our code, and we ask it to display at a a larger size than it is by default, it will be scaled up at runtime by the system.

This means we can now write code like this without any quality loss at render time:

let normal  = UIImageView(image: UIImage(named: "cocoapod"))
normal.tintColor = UIColor(named: "covfefe")

view.addSubview(normal)

let big  = UIImageView(image: UIImage(named: "cocoapod"))
big.tintColor = UIColor(named: "cream")
big.frame = CGRect(x: 50, y: 200, width: normal.bounds.size.width * 2, height: normal.bounds.size.height * 2)

view.addSubview(big)

Very, very cool.

Weekly Sponsor: Zendesk 💡

We're welcoming back one of our favorite sponsors this week, it's Zendesk!

None of us have ever built a flawless app.

Chances are, no matter how good we think our app's user experience is, there's probably something users will need help with while they're in our app.

Yet in many apps, getting help is reduced to a "contact us" button that launches an compose email screen, or a just drops the user on a web page.

By forcing folks out of the app to get help, small problems can turn into big annoyances, and those will almost certainly turn into negative App Store reviews and poor ratings.

With Zendesk's Mobile SDKs, we can join the makers of Angry Birds, Venmo and Swiftkey in bringing native, in-app support functionality to our apps quickly and easily.

Our users can view help and support content, and even submit tickets to support without ever leaving our app. Neat!

Tickets go into Zendesk and can include technical details about the user and their device, history with our apps, and more.

Best of all, it's included with Zendesk at no extra charge.

We can use Zendesk's "out-of-the-box" iOS UI components to get up and running quickly, or we can build your own UI with SDK API Providers.

A huge thanks to Zendesk for sponsoring!

Topics

#311: Round Corner Improvements ✅

Topics

Today we're going to talk about corners. Specifically, rounded ones. In iOS 11, Apple has improved the way we can specify and work with the rounding of our views' corners. Let's take a look.

Let's begin with where we were before iOS 11.

Before iOS 11, configuring a UIView to have round corners went like this:

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8

The clipsToBounds ensures the "clipping" takes place, and the next rounds all four corners with a radius of 8:

Pretty straightforward.

This approach works great for rounding all four corners of a view. But what if we wanted to round only some of the four corners? Previously we'd need to drop down and create our own mask layer, giving it a path we created manually. Bummer.

iOS 11 makes this way easier.

Meet CACornerMask!

Now, we can use the same code as before, with one small addition:

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]

Now we can use the new CACornerMask option set to specify which corners we'd like rounded. Here we've asked for just the bottom two corners to be rounded with an 8 point radius:

Neat!

Before we go, there's one last thing to know about round corners in iOS 11:

They are now completely animatable! 🎉

In previous releases, this next bit of code wouldn't do anything, but in iOS 11 this works exactly as expected:

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 16

UIViewPropertyAnimator(duration: 1, curve: .linear) {
  view.layer.cornerRadius = 0
}.startAnimation()

Very cool.

Weekly Sponsor: Zendesk 💡

We're welcoming back one of our favorite sponsors this week, it's Zendesk!

None of us have ever built a flawless app.

Chances are, no matter how good we think our app's user experience is, there's probably something users will need help with while they're in our app.

Yet in many apps, getting help is reduced to a "contact us" button that launches an compose email screen, or a just drops the user on a web page.

By forcing folks out of the app to get help, small problems can turn into big annoyances, and those will almost certainly turn into negative App Store reviews and poor ratings.

With Zendesk's Mobile SDKs, we can join the makers of Angry Birds, Venmo and Swiftkey in bringing native, in-app support functionality to our apps quickly and easily.

Our users can view help and support content, and even submit tickets to support without ever leaving our app. Neat!

Tickets go into Zendesk and can include technical details about the user and their device, history with our apps, and more.

Best of all, it's included with Zendesk at no extra charge.

We can use Zendesk's "out-of-the-box" iOS UI components to get up and running quickly, or we can build your own UI with SDK API Providers.

A huge thanks to Zendesk for sponsoring!

Topics

#310: Screen Edges in iOS 11 📲

Topics

Continuing our look at the new tidbits and goodies from WWDC 2017, today we'll learn about the changes to screen edge behavior in iOS 11. Let's dive in.

What we're really talking about here is the behavior when a user drags from offscreen. This could be the user trying to pull down Notification Center from the top, or bring up Control Center from the bottom of the screen.

In past iOS releases, the system has looked at the visibility of the status bar to determine how to behave in these cases.

If we configured one of our view controllers to hide the status bar, the system would show a little "tab" UI with an arrow that the user would need to drag a second time before Notification Center or Control Center would be shown:

Keying off of the visibility of the status bar probably isn't the best way for us to "tell" the system what to do here.

In iOS 11, we've been given a wonderful new way to describe how our app should behave when the user performs these gestures.

Now, all we need to do is override this function in our view controlller:

override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
  return .bottom
}

This way, we can describe exactly which edges of the screen we'd like to allow the system gestures to behave "normally", and which we'd like to have defer for a second drag. Neat!

Finally, if the state of our view controller ever changes enough to warrant a change in this behavior, we can call the following new function to update it:

setNeedsUpdateOfScreenEdgesDeferringSystemGestures()

Topics

#309: UIFontMetrics 📐

Topics

Happy WWDC 2017! Today we're beginning our look at the incredibly large list of updates and improvements announced this week with UIFontMetrics. Let's jump in.

We'll start with the problem we're trying to solve. It's all about Dynamic Type.

Our users can adjust their preferred Dynamic Type value in Settings.app to display the text in our apps larger and more prominently.

This works great when we want to use the system default font, since we can simply write some code like this:

let font = UIFont.preferredFont(forTextStyle: .headline)

This will return a UIFont that is appropriate for the given text style, adjusted by size and weight to match the user's Dynamic Type setting.

But what about custom fonts? In the past this was a bit cumbersome and we often had to resort to ugly hacks.

Enter UIFontMetrics! We can use this new type in iOS 11 to ask the system to scale our font size for us:

let headlineMetrics = UIFontMetrics(forTextStyle: .headline)
let fontBeforeScaling = UIFont(name: "Chicago", size: 16.0)
let font = headlineMetrics.scaledFont(for: fontBeforeScaling)

Neat!

There's always one more fun trick tucked away in UIFontMetrics, and that is scaling arbitrary values. This is great for helping us size our UI elements (for example buttons or headers) to accomodate dynamically sized fonts that live inside:

let headlineMetrics = UIFontMetrics(forTextStyle: .headline)
let heightBeforeScaling = 44.0
let height = headlineMetrics.scaledValue(forValue: heightBeforeScaling)

No more large text in tiny buttons. Very cool!

Today we'll continue looking at UICollectionView's more advanced capabilities by checking out a little-known feature hiding inside UICollectionViewController.

It allows us to automatically animate the transition between layouts when pushing on to a navigation controller stack. Neat!

Let's give it a try.

We'll start with the same simple baseline collection view controller we used in Bite #306, and Bite #307, SquaresViewController.

It looks like this:

class SquaresViewController: UICollectionViewController {
  var items = [Item]()

  override func collectionView(
    _ collectionView: UICollectionView, 
    numberOfItemsInSection section: Int
  ) -> Int {
    return items.count
  }

  override func collectionView(
    _ collectionView: UICollectionView, 
    cellForItemAt indexPath: IndexPath
  ) -> UICollectionViewCell {
    let cell = collectionView
      .dequeueReusableCell(
        withReuseIdentifier: "ItemCell", 
        for: indexPath
      )

    cell.backgroundColor = items[indexPath.item].color

    return cell
  }
}

Nothing fancy, just standard UICollectionViewController stuff.

Next, we'll subclass it twice. Once for our "small" cells:

class SmallViewController : SquaresViewController {
  init() {
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: 50, height: 50)

    super.init(collectionViewLayout: layout)

    items = (0...50).map { _ in Item(color: .random()) }
  }

We'll fill it up with some random items, like we did previously, here in our "small" subclassed view controller.

Then, when a user selects one of the cells, we'll push on a "big" view controller, setting its items property to our own:

  override func collectionView(
    _ collectionView: UICollectionView, 
    didSelectItemAt indexPath: IndexPath
  ) {
    let bigVC = BigViewController()

    bigVC.items = items

    navigationController?
      .pushViewController(bigVC, animated: true)
  }
}

Again, nothing really too crazy here. Lastly, before we can try it out, lets make our BigViewController:

class BigViewController : SquaresViewController {
  init() {
    let layout = UICollectionViewFlowLayout()

    layout.itemSize = CGSize(width: 100, height: 100)

    super.init(collectionViewLayout: layout)
  }

We'll make our layout's itemSize property a little larger than before, then we'll override collectionView(_:didSelectItemAt:) one more time.

This time we'll simply call popViewController:

  override func collectionView(
    _ collectionView: UICollectionView, 
    didSelectItemAt indexPath: IndexPath
  ) {
    navigationController?.popViewController(animated: true)
  }
}

If we build and run now, everything works as expected, but there's no fancy transition happening. Just a regular navigation controller push.

Let's fix this.

We'll add one line of code to SmallViewController's init function, setting useLayoutToLayoutNavigationTransitions to false:

class SmallViewController : SquaresViewController {
  init() {
    let layout = UICollectionViewFlowLayout()    
    layout.itemSize = CGSize(width: 50, height: 50)

    super.init(collectionViewLayout: layout)

    useLayoutToLayoutNavigationTransitions = false
    // .. 
  }

  // ..

Then we'll do set the same property to the true in BigViewController:

class BigViewController : SquaresViewController {
  init() {
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: 100, height: 100)

    super.init(collectionViewLayout: layout)

    useLayoutToLayoutNavigationTransitions = true
  }

  // ..

That's it. UIKit will notice that we've set our BigViewController's useLayoutToLayoutNavigationTransitions to true, and will automatically animate the transition between the two layouts.

Note that the automatic transition animation that UIKit renders is smart enough to make sure the selected cell is still visible after we push in. A nice touch.

It's always fun to discover the handy little behaviors hiding out in to UIKit.

Finally, a couple of important notes:

First, the same UICollectionView is actually reused when all of this magic happens. The pushed on view controller will not create its own. This may or may not matter for any given app, but good to know.

Second, the root view controller (SmallViewController in our case) will still be set as the delegate and dataSource when the new view controller is pushed on.

If we needed to change this behavior, we could conform to the UINavigationControllerDelegate protocol and change these values each time a different view controller is about to be be shown. The code might look something like this:

extension SquaresViewController : UINavigationControllerDelegate {
  func navigationController(
    _ navigationController: UINavigationController, 
    willShow viewController: UIViewController, 
    animated: Bool
  ) {
    guard let squaresVC = viewController as? SquaresViewController else { return }

    squaresVC.collectionView?.delegate = squaresVC
    squaresVC.collectionView?.dataSource = squaresVC
  }
}

That's all for today, have a specific UICollectionView question you'd like answered? Send it along!.

Download the Xcode project we built in this Bite right here.

Weekly Sponsor: PSPDFKit 📑

We're welcoming back one of our favorite sponsors this week, it's PSPDFKit! PSPDFKit delivers an intuitive & seamless SDK for integrating PDF functionality into apps on iOS, Android, and the Web.

PDF files have been around a long time. Over that time they evolved to become an incredibly versatile and functional format. They're also wide-ranging in terms of how they're used. Comic books, Apartment leases, flight manuals, etc. All powered by PDFs.

Being able to take advantage of all this great functionality in our apps would be huge for our users.

With it, we can beautifully display PDFs, and allow our users to intuitively annotate documents, and much, much more.

Let's get started.

We'll head over to PSPDFKit's wonderful documentation area and follow the Getting Started steps to integrate the Framework directly, or through a system like CocoaPods.

Once we've got PSPDFKit in our app, we can start playing around.

PSPDFKit provides tons of great APIs for programmtically interacting with PDF files, as well as a bunch of polished, (and incredibly customizable) user interfaces to allow our users to read and manipulate PDF files themselves.

Let's try opening a PDF, and displaying it:

let document = PSPDFDocument(url: documentURL)

let controller = PSPDFViewController(document: document, configuration: PSPDFConfiguration { builder in
    builder.thumbnailBarMode = .scrollable
    builder.scrollDirection = .horizontal
})

We create a new PSPDFDocument, then a new view controller to show it. We use PSPDFKit's awesome PSPDFConfiguration type to neatly build up our settings.

From here we can simply present it like any other view controller. Neat!

PSPDFKit stands out with an awesome, well thought-out API. It's clear that they care deeply about API design, and developer experience.

Before we go, let's try out another couple features. First up, Annotations:

// Create a new PSPDFDocument
let document = PSPDFDocument(url: documentURL)

// Create a link annotation, and set its URL
let linkAnnotation = PSPDFLinkAnnotation(url: URL(string: "https://pspdfkit.com")!)

// Position the annotation in the document
let boundingBox = CGRect(x: 200, y: 400, width: 50, height: 300)
linkAnnotation.boundingBox = boundingBox

// Customize the link annotation's appearance
PSPDFLinkAnnotationView.appearance().borderColor = .blue

// Add the newly created annotation to the document
document.add([linkAnnotation])

Very cool, Next up, Forms. Our users can of course fill out any form fields in documents they open, but we can actually fill them out programmtically too:

let document = PSPDFDocument(url: documentURL)

guard let annotations = document.annotationsForPage(at: 0, type: .widget) else { return }

for annotation in annotations {
    switch annotation {
    case let textFieldFormElement as PSPDFTextFieldFormElement:
        guard let fieldName = textFieldFormElement.fieldName else { return }
        textFieldFormElement.contents = String(format: "Test %@", arguments: [fieldName])

    case let buttonFormElement as PSPDFButtonFormElement:
        buttonFormElement.toggleButtonSelectionStateAndSendNotification(true)
    default:
        break
    }
}

Very cool. We iterated through the form fields and filled them with some example content, and toggled the selected state for any buttons in the form.

We have truly only scratched the surface of what's possible with PSPDFKit here.

PSPDFKit offers a ton of other great features like indexed search, digital signatures, and even document editing! They also provide wonderfully fast and responsive support.

Be sure to check out PSPDFKit's extensive Guides area to find complete documentation and tons of extensive guides.

Thanks to PSPDFKit for their support of LBOC!

Page 1 of 37