A Better MVC, Part 4: Future Directions

Part 4 in a series on “fixing” Model-View-Controller:

  1. The Problems
  2. Fixing Encapsulation
  3. Fixing Massive View Controller
  4. Future Directions

There are other ways you can apply these principles to writing more maintainable apps.

View Controller-based Cells

One that I’m really interested in and am actively researching is how to use view controllers as cell content.

Cells can be really complicated things, architecturally. If you have any sort of dynamic content in a cell, you’re often faced with weird situations where you wonder “where does this logic belong?”. Do you put image loading in your view subclass? Allow your cell to handle complex business logic? Tack on additions to the list’s delegate protocol so you can message things back out (which then just complicates your list view controller)?

Using view controllers as cells helps answer these questions. It is natural to place this sort of code in the view’s controller, and the fact that the view happens to be a cell doesn’t really make a difference to the underlying principle.

Small finite lists

It’s really easy to make a view controller a cell when you have a list of a finite size. You have your “ListViewController”, and you give it an array of view controllers. It adds them as children, and then pulls out the appropriate one when its time to show a cell, and sticks the view controller’s view inside the cell’s contentView.

You can then apply the same principles of “flow view controllers” and “mini view controllers” to the content of the cell, and use child view controllers to manage portions or variations of the cell. I used to write a Mac app where I used this approach, and I could sometimes get upwards of 15 view controllers in a single cell. Granted, the cells were pretty complicated in what they could do, but none of these view controllers was longer than 100 lines of code. It made debugging issues trivially easy.

At this level of granularity in a finite list, you also probably aren’t very worried about view lifecycle callbacks, because you can pre-allocate everything.

Huge finite lists

Once you start moving past the point where you can pre-allocate everything, you have two main approaches you can take. The first approach (“huge finite lists”) is like what the current UITableView and UICollectionView API offer: you know up-front how many items are in a section. In this case, your ListViewController would likely have a similar looking datasource API as UITableView, where it progressively asks for view controllers and then uses the underlying “willBeginDisplayingCell:” etc callbacks to notify on lifecycle.

At this level you might also want to start thinking about view controller reuse, but I would probably avoid thinking about that unless I measured and determined that view controller deallocation and initialization was a measurable bottleneck.

Infinite lists

Then there’s the problem of infinity. A great example of this is the main screen of the Reddit app. You can scroll forever, and the content will just keep loading and loading… there’s no way to know up-front how much content there is. With this, you’ll be looking at loading in “slices” of the infinite view controller sequence, or taking a page (haha) out of UIPageViewController‘s book and using the “doubly-linked list” sort of API (“what’s the view controller after A? What’s the view controller after B? etc).

You’ll still have the underlying callbacks to help manage view lifecycle, and you’ll also still have to consider view controller reuse as an option.

Boilerplate

As you get in to this style of programming, you’ll find that you end up developing a decent amount of boilerplate around embedding view controllers. That is to be expected, because the view controller containment APIs tend to offer the bare minimum to do what you need.

As you find situations where the API can be improved, please request that these improvements be made in the system frameworks.

Conclusion

There’s a lot to digest in these posts. Some people may scream in horror at the idea of “view controllers as cells”, but it’s an idea worth exploring.

So, the huge TL;DR of this is:

  • Decompose your UI using view controllers
  • Use view controllers to manage sequence
  • View controllers don’t have to fill the screen

I’d love to hear your thoughts on this topic. Hit me up on twitter or via email and let’s chat!

A Better MVC, Part 3: Fixing Massive View Controller

Part 3 in a series on “fixing” Model-View-Controller:

  1. The Problems
  2. Fixing Encapsulation
  3. Fixing Massive View Controller
  4. Future Directions

The principle behind fixing Massive View Controller is to unlearn a concept that’s inadvertently drilled in to new developers’ heads:

1 View Controller ≠ 1 screen of content

From our very first iOS apps, we’re taught the idea that 1 view controller == 1 screen of content. We see this in every simple “make a list and push a detail view” app. However, as our apps grow in complexity, so do our screens of content, and the default notion that 1 view controller == 1 screen of content quickly leads to Massive View Controller.

We can save ourselves from Massive View Controller by realizing that a view controller “controls a view” and that view doesn’t have to fill the screen.

Example: The WWDC App

Disclaimer: While I used to be the lead engineer on the WWDC app, I have not seen the code in quite some time and do not know if it is actually implemented this way. I am simply describing how I would build the screen if I were to build it today.

Here’s a screenshot of the WWDC app:

WWDC app session details page

On this one screen, there are five main pieces of content:

  1. The video
  2. The title
  3. The description
  4. The contextual actions
  5. The related content

There is a lot going on here, and if this were to be implemented as a single view controller, it would be a massive view controller.

So, let’s not do that.

Instead, we’re going to make each one of those 5 areas its own view controller, all contained within the SessionDetailsViewController. The outer details view controller will own the general layout of the screen, as represented by some empty container views. These will all be in a UIScrollView that manages its content bounds through auto layout.

The SessionVideoViewController would be in charge of loading up the appropriate poster frame for the video and responding to the user tapping the play button. When the user taps that button, the view controller will delegate out that the user intends to watch the video. The parent SessionDetailsViewController can then pass that intention up the chain until it arrives at a semantically appropriate level for that intention to be translated in to the corresponding WatchVideoOperation.

The SessionTitleViewController and SessionDescriptionViewController will be pretty simple, since all they’ll have to do is observe the model object for changes and update the labels. There’s also no interaction to delegate back out.

The SessionActionsViewController would basically be a UIStackView of buttons. The buttons would be created based on inspecting the model object, and interacting with the buttons delegates back out the corresponding intention: “toggle favorite”; “leave feedback”; “begin download”; etc.

Finally, the RelatedSessionsViewController would be a side-scrolling collection view. Tapping on a related session would delegate back out that the user wants to view the session. The SessionDetailsViewController, as we learned in the previous post, would not be the one to perform that action, but would instead relay the intention up to a more semantically appropriate level (such as the view controller that owns the UINavigationController).

At the end of this exercise, we end up with more view controllers (six instead of one), but each one is relatively small. The video view controller loads a poster frame. The title and description view controllers observe the model for changes. The actions and related content view controllers are a little more complex, but each one is focused on a very specific set of actions, and neither is onerous to understand.

Example: The Reddit App

Disclaimer: I have no idea how the Reddit app is built.

For this example, let’s take a look at the Reddit app.

The Reddit app

There are a bunch of interesting things going on here, but I want to focus on the top part of this post screen that contains the actual post. A cursory survey through the app shows that there are several different styles that this post content can take, in addition to having a title:

  • Text
  • Link
  • Animated gif
  • Static image
  • Video

It would be crazy to try and build the entire post page as a single view controller. At the very least, you’d want a “post content” view controller, and a “comments” view controller. But you can go further.

Imagine that the top post content is a PostContentViewController. You still have a large view controller as you have to handle one of these 5 different kinds of post content (showing all text vs loading a web preview vs animating a gif vs showing an image vs an inline video vs a link to an external video…).

So instead, make your PostContentViewController a “flow” view controller, and then have a different view controller for each kind of content. When you have a TextPostContentViewController, you never have to worry about dealing with loading callbacks. A LinkPostContentViewController only has to deal with loading a preview. A GIFPostViewController only ever has to load a gif. It’s easy, and you PostContentViewController just has to pick the right one, embed it, and then handle the odd delegation of user intent.

By combining both principles (view controllers as flow and small view controllers), you can easily decompose your UI in to small, manageable, testable, isolated, and grokkable chunks.

A Better MVC, Part 2: Fixing Encapsulation

Part 2 in a series on “fixing” Model-View-Controller:

  1. The Problems
  2. Fixing Encapsulation
  3. Fixing Massive View Controller
  4. Future Directions

In order to fix the encapsulation violation we saw earlier, we need to understand a pretty simple principle:

In general, a view controller should manage either sequence or UI, but not both.

A view controller that manages sequence is one that I jokingly call a “Manager View Controllers” because 1) “manager” and “controller” aren’t overloaded enough already and 2) it still has the acronym “MVC”. In reality, this is a variation on the “Coordinator” pattern that has captured some of our imagination.

The idea behind the Coordinator pattern (or flow controllers or whatever you call them) is that, in order to maintain encapsulation, you need a higher-level object to decide what comes next. It “controls the flow” in your app.

Where I tend to diverge from the “traditional” flow controller implementation is that I believe that these sorts of controllers should really just be view controllers higher up in the parent view controller chain. This saves you from having to hack a new kind of responder chain object in to UIViewController, and it means you don’t end up with a third parallel hierarchy of control to maintain in sync with the other two (the View hierarchy and the view controller hierarchy).

Using container view controllers as sequence “coordinators” makes a whole lot of things really easy. For example, consider a screen where you want to load a piece of remote content. But while it’s loading, you want a spinner to show up. If the content fails to load, you want an error screen and a “try again” button.

Implementing this as a single view controller would leave you in a place where you’re on the road to Massive View Controller. You’d have a view controller that’s probably hitting the network and then trying to rationalize which of the three different states it should be in (Error, Loading, or Success), then is responsible for making sure the right set of UI elements are visible and getting the model information into the proper set of out outlets.

Instead, abstract out the sequence from the UI. The sequence is the owner of the flow between the three states, and each of the states is its own view controller.

The ShowRemoteContentViewController (the owner of the sequence) has an empty view, and it embeds the proper content view controller depending on which state it should be in:

  • Show the ErrorViewController if your networking object reported back an error. The ErrorViewController delegates back out when the user taps on a “Try Again” button, which causes the delegate (the ShowRemoteViewController) to transition to the loading state while hitting the network
  • The LoadingViewController is an empty view controller that shows a loading indicator. It is literally zero lines of code, unless you want a simple init() method, in which case it is 4.
  • The MessageViewController is the view controller that handles the “success” state. It basically populates its UI from the injected model object, and delegates back out when the user taps a “Load Another” button.

A simple iOS project (Xcode 9.1, Swift 4) showing this in action is available here: iOS Architectures.zip.

We’ve taken something that was really complicated (a single object managing UI and state and flow) and turned it in to something eminently understandable; no view controller is longer than 100 lines of code long. Each one is isolated from everything else. A good use of delegation means that testing is trivial: we load up the UI, fake a tap on the button, and assert that the delegate method is invoked. There’s almost no cognitive load to understand any individual view controller. Even the “more complicated” ShowRemoteContentViewController is simple, because it’s just flipping back and forth between a couple of different child view controllers in reaction to some delegate method invocations or the network loading. It’s all really simple, and there are no violations of encapsulation.

The same pattern holds true when you’re dealing with more structured UI. If we imagine now a list UI, where tapping a list item brings up a detail view, we can apply this same principle.

First off, we don’t want the list knowing about the detail view controller or how to show it. So, we simply make it delegate back out to someone else what the user is intending to do. The list UI receives model objects from “the outside”, so that’s what it should send back. Therefore, in our implementation of didSelectRowAtIndexPath:, we simply translate the index path in to the corresponding model object, and then message the delegate what the user intention was: listViewController:userDidSelectModelObject: → “The user wants to look at this model object”.

So, who then is the delegate? You could argue that it might be the direct parentViewController, but in this case, the parent is likely a UINavigationController. The UINavigationController is already in charge of maintaining a stack of view controllers with its own UI (the navigation bar), so this doesn’t seem like a good candidate. Instead, let’s put the UINavigationController inside a container view controller, which we’ll call the ListFlowViewController.

The ListFlowViewController creates and embeds the plain UINavigationController in itself and gives it the root screen (the list view controller). Then, it makes itself the delegate of the list, because it’s the ListFlowViewController that created the list and (likely) knows what the model objects mean. Then when the user selects an item in the list, the ListFlowViewController receives the corresponding model object, knows how to turn it into the detail screen, and gives that screen to the UINavigationController to push. We again preserve proper encapsulation principles.

This same pattern also holds true on iPad (or regular width phones) where you deal with UISplitViewController. Having a container view controller own the split view means you have a natural place for the “master” view controller to message about the user intent, and handle how to appropriate display the detail view controller. This same container view controller could also decide to entirely eschew a split view controller if the app transitions back to a compact width size class.

The huge advantage of this approach is that system features come free. Trait collection propagation is free. View lifecycle callbacks are free. Safe area layout margins are generally free. The responder chain and preferred UI state callbacks are free. And future additions to UIViewController are also free.

In exchange, you have to suffer through cleaner code, smaller view controllers, and a few more delegate protocols, which unfortunately just make your code more isolated and testable. How on earth will you survive?? 🙃

A Better MVC, Part 1: The Problems

Part 1 in a series on “fixing” Model-View-Controller:

  1. The Problems
  2. Fixing Encapsulation
  3. Fixing Massive View Controller
  4. Future Directions

I recently ran across a great article by @radiantav called “Much ado about iOS architecture“. It addresses a topic that has been on my mind a lot. I gave a talk about it at the recent Swift by Northwest called “A Better MVC”. These blog posts attempt to capture the main points of my talk.

I apologize beforehand that there aren’t really any pictures in this. They’d definitely make the subject matter clearer, but I don’t feel like spending the time to make them. You’ll just have to use your imagination.

The “Problems” with MVC

The main reason that people decry MVC is that they tend to run afoul of two major problems when using it:

  1. MVC, as taught by Apple sample code, encourages you to violate encapsulation principles, which ends up leading to spaghetti code.
  2. Without proper discipline, your view controllers end up being huge, leading to the joke that “MVC” means “Massive View Controller”.

Violating Encapsulation

It’s pretty common to come across a UITableViewDelegate method that looks something like this:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let maybeItem = query?.object(at: indexPath)
    guard let item = maybeItem else { return }
    let detail = MyCustomDetailViewController(item: item)
    show(detail, sender: self)
}

There are two huge encapsulation violations that are occurring here.

The first is this line:

let detail = MyCustomDetailViewController(item: item)

The principles of encapsulation indicate that a thing should only really “know” about objects that it contains, and then only about one layer of abstraction down. However, this line, which constructs a subsequent screen-full of information, requires the view controller to know about the sequence of data flow in its parent abstraction.

In other words, this list isn’t just a list, but must also being aware of the context in how it’s used.

The second violation is similar to the first:

show(detail, sender: self)

Again, this violates the whole “don’t know your context” principle. And, it should be noted, these critiques hold true whether you’re manually creating and showing a detail view controller, or invoking a segue and using prepareForSegue:sender:.

Massive View Controller

It is sadly pretty common to come across a view controller that is thousands of lines long. Our view controllers quickly become cluttered with networking callbacks, delegate methods, data source methods, IBActions, trait collection reactions, model observation, and of course, our business logic. Heaven forbid if you’re doing any sort of progressive loading and want to swap in an indicator to show while things are loading… that will usually add in a couple hundred lines of code as you deal with swapping views around, managing yet another stateful property, and so on.

We end up with this situation because we don’t apply enough discipline to our view controllers. They’re convenient places to dump code. But they quickly become unmaintainable, fragile, and inherently untestable. Who wants to be the poor soul who has to debug some weird state going wrong in a 5,000+ line file?

Working around MVC

In my experience, these are pretty much the two fundamental reasons why developers find other architectural patterns so appealing. As a result, we turn to other architectural patterns to try and compensate. We reach for MVVM, or React, or MVP, or FRP, or VIPER, or insert-new-architecture-here.

To be clear, there is nothing inherently wrong with any of these patterns. They all solve problems in unique and interesting ways, and it is good to study them and learn the principles they teach. However, I’ve found that building apps based on these patterns tends to pay negative dividends in the long run.

All of these patterns tend to be “strangers” to UIKit and AppKit. Because of this difference, you end up with additional hurdles to clear when things change.

When your team members change, you have additional work to teach new developers about not just the business logic of your app, but often a whole new architectural pattern. This requires more up-front investment, and a longer lead time before team members can start being productive.

When the operating system changes, you have additional work to try and shoehorn new features in to architecturally-native concepts. You have to plumb through size classes, safe layout margins, dynamic type, localization, locale changes, preferred attributes (status bar style, etc), lifecycle events, state preservation and restoration, and who knows what else when new paradigms get inevitably added as iOS updates each year.

When your requirements change, you can sometimes be caught in the situation of having to fork someone else’s library, wait for them to catch up, or hope that they’ll accept your pull request. I won’t go in to the pros and cons of third-party dependencies here, but an architectural dependency has even more “gotchas” than a normal dependency, because of the way it can underpin your entire app.

Each step of the way, you may be celebrating that your code is clean and concise. However, you drift further and further away from what the system frameworks are providing, which means adopting new systems features requires more code to bring it to where you are.

Wouldn’t it be nice if you could just get it for free? Wouldn’t it be awesome if you could just use UIKit and AppKit and have it all “just work”? Wouldn’t it be nice to live in a world where you thought 300 lines in a view controller was excessively long?

In the next post, we’ll look at how we can fix the first problem.

Keynote’s awesome Outline Mode

So, I love Keynote.app. It’s one of my all-time favorite apps. During my time at Apple, I got to use the app a lot and was constantly amazed by how powerful and capable it is. I grew very used to its precision and elegance, and its overall ease-of-use.

One of the really cool tricks I learned about Keynote was to quickly build slides using Outline Mode, like so:

Keynote Outline Mode

Outline mode is a powerful way to build slides, because it allows you to focus on the structure and flow of your content, without getting distracted too much by the visual aspect. In the early phases of building a deck, this is really important.

However… the visual aspect is still there. There are these huge slides sitting right next to your outline and you cannot make them go away. It is really easy to get distracted from what you’re doing and start focusing on text alignment, font sizes, animations, slide styles, and so on.

This morning I had the idea of “well, what about other outline modes, like the one in Pages?”. So I popped open Pages and started writing an outline and copied it to my pasteboard:

Building an outline in Pages

With this outline in hand (er, on my pasteboard), I headed back to Keynote and tried pasting it into the Outline view:

Paste an outline in to Keynote

A couple clicks to make sure the slides had the proper style (by reapplying the master style to the slides), and I had generated an entire presentation in a matter of seconds. At this point, with an outline built, I can easily create my Keynote presentation and start polishing it and adding in all the visual fanciness.

If you build presentations but get distracted by the slides, this could make your life a whole lot easier.

iOS Feature Wish: Contact Provider Extensions

For a while now, there’s been a major feature of iOS Contacts that I’ve felt has been sorely lacking, and that’s the ability for third-parties to provide their own contact card data. Here’s what I mean…

We live in a world where we have a whole bunch of different social circles that we run in, and it seems like almost every circle has a different way of keeping track of how to communicate with those people. I’ve got Twitter, LinkedIn, Snapchat, and Facebook. I’ve got a company directory, my nextdoor.com account, and even an app for congregants of a nearby church.

None of these people show up in Contacts.app, and that really bothers me.

Sometimes I want to call someone, but I can’t just go to the phone app and start typing their name. I have to remember how I know them, go to the relevant social networking app, hope my credentials are still valid, find their info, and hope there’s a phone number in there.

Sometimes I want to text or email someone, but I can’t just go to Messages or Mail and start typing their name. I have to remember how I know them, go to the relevant social networking app, hope my credentials are still valid, find their info, and hope there’s a phone number or email address in there.

I really wish iOS offered a way for these apps (Facebook, Next Door, Twitter, LinkedIn, etc) to “donate” their contact information to the system database in a non-permanent way. (By “non-permanent” I mean “don’t just dump it in to my iCloud contacts and call it good”; deleting the app would cause that info to disappear, and the app could update it on-demand) There would need to be some pretty intelligent merging that happens, but generally it’s pretty safe to assume that an individual with a certain phone number and email address is probably the same individual as another with the same phone number and email address. You’d also have to consider how to handle apps that provide unboundedly-large data sets (like the corporate directory for a 50,000+ employee company). But, these are solvable problems.

Functionality like this would also enable what I consider to be the “holy grail” of contact services: a service where I can expose a certain subset of my contact information to specific groups of people. Facebook kind of lets you do this with your profile, but also carries a whole lot of extra baggage that a lot of people find undesirable.

In my opinion, the days of manually filling out and sharing vCards belong firmly in the past, yet that is just about the “state of the art” today. We have the technology to make far superior services, but just need a way to integrate that with the standard iOS (and macOS) experience.

Disclaimer

Despite my recently-ended employment at Apple, I have zero idea of whether they have any plans to do this.

Also, I did file a bug report about this, but I do not have the bug number anymore. Please file your own copy! 🙏
Update: it’s been verified that my original bug report about this is rdar://17750541. Go forth and dupe!

Every beginning has an end

It seems like it was only the other day when I wrote the post about leaving the Evangelism team.

But, a lot has happened in the two years since then. We bought a piece of land and built a house. The kids are both in school full-time. My wife has a job that she loves.

Working from home has enabled some hugely significant changes in our lives. It vastly simplified the process of my wife getting a job. We don’t have to wonder about after-school care for our kids. My relationship with my kids has drastically changed, too. I’m no longer the guy they see for an hour or two in the morning and an hour or two in the evening. Our family life has become radically different from how it used to be, and all for the better.

However, working from home has also had its costs. For one, Apple is not really built to accommodate employees who work from home; there’s understandably a huge amount of inertia around the “everyone in the same office” mentality. A few teams are experimenting with trying to change this, but this kind of progress takes time.

Additionally, there are other costs that come with working at Apple. I can’t put my own apps on the store. I can’t speak at conferences. I can’t be an involved, vocal member of the open source developer community. I can’t do my own stuff on the side. When you’re working in Cupertino, these restrictions aren’t so bad. When you work from home, they chafe.

So, after seven years working at the mothership, I’ve decided that it’s time for me to move on. My last day is the 16th. I’ll take a very short break, and then start a new job at Snapchat on the 23rd. They’re opening a new R&D office a couple miles down the road from me, and I’m excited to help get it off the ground.

I’m also really excited to openly participate in the dev community again. I’ve got some Swift-Evolution ideas I want to propose, and I’m also thrilled to be speaking at the Swift by Northwest conference in Seattle at the end of the month.

It’s been the experience of a lifetime working for Apple. I’ve been able to be a part of something that I barely dared to dream of as a kid and a teenager, and it’s not anything I’ll ever forget.

Changes

Several months ago, our family decided to make a drastic change. After nearly five years living in the Bay Area, we decided it was time for us to leave. The exact reasons for this are somewhat lengthy and personal, but they boil down to two main things: Housing prices and proximity to family.

During our time in Silicon Valley, we were extremely fortunate to rent a home from a couple who gave us a great deal on rent. But the reality was that even with a comparatively “cheap” rent, there were still times when we felt like we were barely breaking even. We wanted to be saving money to someday buy a home in which we could raise our kids, but the market near San Jose is such that saving money is extremely difficult. It’s fairly typical (I believe) for 50% or more of your paycheck to go to rent. Depending on where you live, it may be much more than that.

We want to buy a home. We want the freedom of making a home truly ours. When renting, there’s always the hesitation that you perhaps might not want to alter this or that, because it’s ultimately not your property. In the home we were renting, I felt reasonably comfortable installing a new thermostat, but even things like smart outlets felt a bit invasive. Painting the walls, installing AC, landscaping, remodeling the kitchen… all things we would’ve liked to have done, but which were ultimately not our decision.

Couple this with home prices (it’s hard to get any sort of home in the valley for under $800k), and the reality of the situation was that we were never going to afford to buy a home there. The homes we could’ve afforded would’ve meant a 1 hour+ commute each way. With the tech industry what it is, sacrificing another hour away from my family each day was a really hard sell.

The other main reason is much simpler: we want our kids to grow up near their cousins and aunts and uncles. We love our family. In California, the nearest such relatives were a twelve-hour drive away.

So, we made the decision to leave California. We moved near the end of July, and leaving was really bittersweet for us. We loved our time in California, but we couldn’t see ourselves staying there long term and being happy. We have since settled near Salt Lake City, as almost all of our extended family is in this area. Grandparents, aunts, uncles, cousins… they’re all pretty much less than an hour away now.

Unfortunately, this has required a change to my role at Apple. I have loved my (nearly) three years on the Evangelism team, and I hope that someday circumstances would allow me to go back. John, Jake, Paul, Curt, Josh, Stefan, Allan, Other Paul, Mark, Mark, Assana, Rachel, Mike, Stephen, Craig, and David… you’re the best co-workers a guy could hope for, and I’m going to miss working with you. I miss it already.

But, I’m extremely grateful to have found a position on the Maps client team, and am excited to dig in on Monday.

Incrementing Build Numbers in Xcode

(This was originally posted in April of 2009 and has been reposted here for posterity)

I admit, I’m a sucker for numbers. When I release a project, it’d be kind of cool to know how many times I’ve built it. With that in mind, and with a good deal of help from my friend Quinn and a couple of online tutorials, I was able to come up with a neat little script that can do just that.

Here’s what you need to do:

  1. Select the target you’re working with, and add a new “Run Script Phase” to it
  2. Paste the following code into the script box:
    #!/bin/bash
    buildNumber=$(/usr/libexec/PlistBuddy -c "Print DDBuildNumber" Info.plist)
    buildNumber=$(($buildNumber + 1))
    /usr/libexec/PlistBuddy -c "Set :DDBuildNumber $buildNumber" Info.plist
    
  3. Compile with impunity

The idea is simple: It uses your project’s Info.plist file to store an integer value under the key “DDBuildNumber”. Every time you build, it grabs that number, increments it, and saves it back to the plist.

A caveat: It seems like Xcode is caching Info.plist, so you may not see the change reflected immediately in Xcode. However, if you open Info.plist from the Finder (or just Quick Look it), you’ll see that the number has changed.