Making Grids Great in iOS 8

I’ve spent the past few months updating all of my apps to be compatible with iOS 8. In the case of Halftone 2, I wanted to make sure that it took full advantage of the new iPhone 6 and iPhone 6 Plus screen dimensions at launch. All I can say is: thank goodness I fell in love with Auto Layout years ago! With over 90,000 lines of original code, I’d have been lost without my NSLayoutConstraints. By the way, if you’re just starting to adapt your apps, your first project-wide search should be for “320”.

One of the components that I share across apps is a custom photo picker. Like many photo pickers, it displays a grid of thumbnails in a UICollectionView. The Auto Layout constraints on the collection view were keeping it pinned to the edges of the new iPhone 6 and iPhone 6 Plus screens, but the orderly grid of thumbnails had turned into a grid with all the extra space distributed between the columns.

This animation shows the custom photo picker on a 4-inch iPhone 5s, then on a 4.7-inch iPhone 6. You can see that UICollectionViewFlowLayout has allocted the extra space between the columns.

Hard Coding?

I was tempted to fire up Excel and calculate the perfect margin and gutter (inner padding) sizes for all of the known screen dimensions. Then, I could simply apply the correct set of measurements by detecting the screen width. Nice and easy! And that’s when the developer voice in the back of my head started to shame me.

Yeah. That little developer voice is right. Hard-coding is almost always a bad idea. Also, what if the next iPad has new screen dimensions? Or what if it has the previously-rumored split screen mode? Those resizable simulators are installed with Xcode for some good reason, right? And what about the Apple Watch? Or size classes and UITraitCollections? Or all of the talk at WWDC 2014 about building Adaptable UIs? It’s almost like Apple is trying to tell us something.

A Better Way

Typically, when I’m calculating margin and gutter sizes in Excel, I’m looking for numbers that have a harmonious relationship. Perhaps a margin of zero paired with a reasonbly small gutter. Or, if that doesn’t work, how about a margin that’s equal to the gutter? Or, worst case, a margin that’s 1.5x the size of the gutter? I realized that I usually have a set of acceptable relationships that I try to achieve.

So, I decided to build a system that would allow me to express a list of desired gutter-to-margin relationships and have it calculate a result that had the best/tightest spacing. That’s what JBSpacer does.

The optimal spacing that JBSpacer calculates can be used to configure or build almost any grid. For most of my cases, though, I use one of the convenience methods to automatically and accurately configure a UICollectionFlowLayout.

This animation shows the difference between standard UICollectionViewFlowLayout behavior (if I had changed nothing) on an iPhone 6 and the same layout with JBSpacer calculations applied:

@3x

By default, JBSpacer performs all of its calculations to snap to the current screen scale (@1x, @2x, @3x, etc.). It didn’t take long to realize that the @3x screen scale of the iPhone 6 Plus brings with it a host of issues related to floating point math.

If you’re not familiar with floating point numbers, you can start with the knowledge that a float represents only an approximation of a real number (albeit a very close approximation), and when you perform operations on a float, you can accumulate error. In some applications, this loss of precision might not matter. But when you’re trying to account for every single pixel (not point) in a UICollectionView at @3x, you can easily throw off its spacing.

As a simple example, 0.3334 + 0.6667 = 1.0001. If you were expecting 1/3 + 2/3 = 1, you’ll be sad to learn that even that small difference can cause UICollectionViewFlowLayout to assume you needed more than a single point (3 pixels in this case), and it will adjust according…err…frustratingly.

If you’re doing any sub-point/pixel-level calculations for the new @3x screen scale, you owe it to yourself to learn about floating point math. Here’s a simple start.

Wolfram Alpha

We interrupt this blog post for a developer tip that I keep forgetting to include in one of my posts (and no, this isn’t a sponsored ad). Most software developers have at least a moderate grasp of mathematics, but there are times when you can’t quickly figure out how to express, reduce, or solve an expression, and nobody on Stack Overflow seems to have the exact same problem. At least I find myself in this situation.

For JBSpacer, I needed to quickly calculate an ideal gutter size based on the following:

  • a = available space (e.g. width of a UICollectionView)
  • c = count of items on a single row
  • s = size of each item
  • g = gutter size
  • r = ratio of gutter size to margin size

I knew I could easily express a (the available space) with the following expression:

((g * r) * 2) + (c * s) + ((c – 1) * g) = a

Basically, (g * r) is the margin size, and since there’s a margin on both the left and right, it’s multiplied by 2. Next, I add the total size taken up by all items in a single row: (c * s). Finally, I account for the gutter between each item with ((c – 1) * g), meaning that there’s always one fewer gutter than the number of items in a single row.

So, here’s my developer tip: to solve for g (the gutter size), I can use the following Wolfram Alpha query:

solve ((g * r) * 2) + (c * s) + ((c - 1) * g) = a for g

From there, it’s a simple matter of converting the result into code. And…done!

If I’ve piqued your interest, be sure to look at some of the other Wolfram Alpha examples. You’ll be amazed at how much time this can save you.

JBSpacer on GitHub

If you’d like to use JBSpacer in your own project, head on over to the JBSpacer page on GitHub. Or, if you prefer CocoaPods, add this to your Podfile:

pod 'JBSpacer', '~> 1.0'

I hope that this helps you create good looking grids in iOS 8 for the new device sizes.

Best of luck!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *