AutoLayout Myths, Table View Performance, and Side-by-Side iPad App Multi-tasking
With the prospect of a new iPhone form factor and/or side-by-side iPad app multi-tasking on the horizon, iOS app designers and developers are likely to face new challenges when building their apps. Since it was introduced, a lot of people have championed AutoLayout as the cure for these challenges. This post is going to debunk that myth. Or more fairly, this post will show the limits of what problems AutoLayout is able to solve.
For the purposes of this post, let’s assume you’re an iOS app developer planning the architecture for an app like Unread. We have to plan for scrollable table views with as many as 20,000 rows. Each row has a dynamic height based on several factors:
- The length of the title, blog name, and article summary.
- The presence or absence of an image thumbnail.
- The user’s chosen font size.
So, given a container with width w, how should we layout our elements?
As the developer, we’ve been handed some design mockups which we’ll use to write code that calculates the size and position of all our elements. Given a container with width w1, our layout might look like this:
The math and logic required to calculate the position of all these interface elements can potentially become very complex. It is this problem that AutoLayout is designed to solve. AutoLayout makes it easier to write and debug code that calculates the layout of a set of interface elements for a given container size.
It’s important to note that you can’t assume that the container width will always be equal to w1. Apps might have to handle at least two possible container widths: portrait and landscape. So what does our layout look like for a second width w2?
This layout is much different from the first one. AutoLayout makes it easier to calculate the correct positions of all these elements. But as the app developer, your problems are much bigger than just calculating a layout for a given container. You also have to deal with extrapolating that calculation across thousands of model objects and many possible containers. This problem is much more difficult and is beyond the scope of AutoLayout.
Cell Heights and Performance
Notice that the second layout above produces a different total height than the first. What are the implications now that your app has two layouts with two different heights?
When a table view is loading, it needs to know how many rows it will display and how tall each individual row will be. For designs that produce dynamic row heights (as the design above does), these metrics can be very expensive to calculate. You don’t want your app to pause for several seconds while calculating these heights. So what are your options?
Use the new estimated row height API. – On first blush this seems like the best answer. The problem is that
estimatedHeightForRowAtIndexPathhas many bugs. I’ve gone into greater detail on them in another blog post, but the short version is that any app feature that requires accuracy (such as tapping the status bar to scroll to the top of a list) simply won’t work with a table view that implements that method. So this is not a good solution.
Pre-calculate and cache all row heights. – Using iOS’ ability to perform work in the background, your app can calculate the height of every table view row ahead of time. Then your table view will load “immediately,” without pausing the execution of the main queue.
Pre-calculation is a better solution, if you can pull it off. It’s difficult to do correctly. There are many factors to consider:
- How many layouts must you pre-calculate for? Portrait and Landscape? Are there others? Every new layout multiplies the total amount of pre-calculation that must be performed.
- What if the user changes themes or font sizes while a long-running pre-calculation is underway on a background queue?
- If you’re caching the results of the background calculation, what kinds of events would invalidate that cache? Date changes? Content changes?
- What if a table view will have 20,000 items in a single list? Is it possible to perform all that work on an older device like an iPad 2, even on a background queue? It could take 15 to 30 seconds to do all that work on an older device. Will you then have to re-write your model collections to support paging in new content as needed?
iPad Multi-tasking & Performance
In the example above, your app was already strained to achieve good performance with only two layouts of static widths. If iOS 8 allows iPad apps to enter a multi-tasking mode with dynamic widths, this would exponentially increase the difficulty of achieving good table view performance. Pre-calculation would be practically impossible.
Assuming the estimated row height API is still buggy and pre-calculation is impossible, the only other alternative would be for devs to start over from the beginning with a design plan that doesn’t allow elements with dynamic heights to also have dynamic widths. This would make good performance achievable, but it would defeat the purpose of a flexible app container. It is for this reason that I think if Apple adds iPad app multitasking, it will only be by scaling app containers without changing the underlying logical widths (768 or 1024 points for portrait or landscape respectively).
The point to remember is this: AutoLayout makes it easier to calculate a single layout for a single container, but it is irrelevant to the challenge of efficiently calculating a large number of layouts for multiple possible containers.