UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to create a custom Xcode template for coordinators

Make iOS apps with the coordinator pattern quicker than ever

Paul Hudson       @twostraws

Xcode comes with a selection of default project templates, and while they might well enough as sample code they work less well as templates – you normally end up doing quite a bit of cleaning to make the project empty so you can start building on it.

In this article I’ll show you how you can build your own Xcode templates so you’ll never have this problem again. We’re going to be building a template that works with the coordinator pattern – a marvelous invention of Soroush Khanlou that helps move navigation code out of view controllers. However, the techniques you learn can be applied to any kind of Xcode template, whether or not you use coordinators.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Setting up a basic template

First, we need to navigate to where Xcode stores its templates. In Finder, press Cmd+Shift+G to bring up the “Go to the folder…” prompt, and enter in this path: ~/Library/Developer/Xcode/Templates/.

If you end up in a folder called “Templates”, you’re good to go. If you end up in a folder called “Xcode” it means you don’t have a Templates folder yet, so the first step is to create one: right-click in the window, choose New Folder, name it Templates, then navigate into it.

Xcode has two kinds of templates, and stores them in different folders. The first is a file template, which is what you see when you choose File > New > File. The second is a project template, which is what you see when you choose File > New > Project.

Both of these template types are stored in the Templates folder we just navigated to, inside special subfolders called “File Templates” and “Project Templates” – please make those now too.

Next, we need a template to use as a foundation. This is helpful because templates have various settings that must be precisely correct – starting with an existing one helps avoid confusion and basic errors.

So, back in Finder please press Cmd+Shift+G again, this time entering the following very long path: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Application.

That will take you to where Xcode’s iOS templates are, and you’ll see all the usual suspects of everyday Xcode in there: Augmented Reality App, Master-Detail App, Single View App, and more. You can start with whatever you want – for example, cloning the Game template and removing all the junk that gets put in there would be nice! – but if you’re building UIKit apps chances are you’ll want to start with the simplest: Single View App.xctemplate. So, please copy that to the Project Templates folder we made a few minutes ago.

The least you need to change

Xcode gives us a lot of control over how our templates look and work, but there are four things you must change in order to avoid problems.

First, change the name of your xctemplate file itself, from Single View App to something else. For example, you might want to create a template that uses coordinators out of the box, so you’d rename this thing to “Coordinated App.xctemplate”.

Second, open the TemplateInfo.plist file inside the template directory, and change the string under Identifier. It will be com.apple.dt.unit.singleViewApplication by default, but you should change it to be unique. Following the previous example, you might use com.hackingwithswift.dt.unit.coordinatedApplication.

Third, change the Description string in the same property list file – it’s just a little further down. You might say “This template provides a starting point for an application that uses coordinators.”

Finally, save your property list changes and replace the two graphics TemplateIcon.png and TemplateIcon@2x.png. These are shown as icons in the Xcode UI, so changing them to something more customized will help you recognize your custom template faster.

Make sure you quit and relaunch Xcode before looking for your custom template.

Adding custom files

So far this hasn’t been complicated, but that changes here: customizing your Xcode template is a bizarrely backwards experience, made worse by an impressive lack of documentation from Apple.

I’m building a template for coordinator apps here, so I want my template to include the basic files to make that happen. For me, that means including a Storyboarded protocol that lets me instantiate view controllers easily, a Coordinator protocol that lets me define what a coordinator looks like, and a bare bones MainCoordinator that can start up our app.

First, create a new Swift file in your template directory, calling it Storyboarded.swift. Here’s the code it needs:

import UIKit

protocol Storyboarded { }

extension Storyboarded where Self: UIViewController {
    static func instantiate() -> Self {
        let storyboardIdentifier = String(describing: self)
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        return storyboard.instantiateViewController(withIdentifier: storyboardIdentifier) as! Self
    }
}

Now create a Coordinator.swift file in the template directory, give it this code:

import UIKit

protocol Coordinator {
    var navigationController: UINavigationController { get set }
    var children: [Coordinator] { get set }

    func start()
}

Finally, create a third file called MainCoordinator.swift, giving it this code:

import UIKit

class MainCoordinator: Coordinator {
    var children = [Coordinator]()
    var navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func start() {
        // let vc = ViewController.instantiate()
        // vc.coordinator = self
        // navigationController.pushViewController(vc, animated: false)
    }
}

Tip: I commented out the body of the start() method because it won’t work just yet – we’ll come back to this later.

I’m not going to explain what all that code does – I’ve already explained it in my article on using the coordinator pattern with iOS apps.

Adding files to the template folder isn’t enough for Xcode to actually use them in new projects. Instead, two more steps are required: listing them as nodes in the project, then telling Xcode where they should exist.

So, first look for the <key>Nodes</key> line a line or two under <key>Swift</key>. You’ll see an array of strings inside there, all starting with ViewController.swift, like this:

<string>ViewController.swift:implementation:methods:viewDidLoad:super</string>

We need to list our three filenames in there. So, please add these below the others in the array:

<string>Storyboarded.swift</string>
<string>Coordinator.swift</string>
<string>MainCoordinator.swift</string>

Second, we need to tell Xcode where those files should be placed in the project. This is done lower down in the property list, under where it says “Definitions”. You’ll already see one there for Main.storyboard, but we’re going to add three more definitions for each of our three extra files. We’re also going to add a Group key, which lets us organize these files into groups rather than just dumping them straight into the root of the project.

Modify the Definitions part of your property list to this:

<key>Definitions</key>
<dict>
    <key>Base.lproj/Main.storyboard</key>
    <dict>
        <key>Path</key>
        <string>Main.storyboard</string>
        <key>SortOrder</key>
        <integer>99</integer>
    </dict>
    <key>Storyboarded.swift</key>
    <dict>
        <key>Group</key>
        <array>
            <string>Protocols</string>
        </array>
        <key>Path</key>
        <string>Storyboarded.swift</string>
    </dict>
    <key>Coordinator.swift</key>
    <dict>
        <key>Group</key>
        <array>
            <string>Protocols</string>
        </array>
        <key>Path</key>
        <string>Coordinator.swift</string>
    </dict>
    <key>MainCoordinator.swift</key>
    <dict>
        <key>Group</key>
        <array>
            <string>Coordinators</string>
        </array>
        <key>Path</key>
        <string>MainCoordinator.swift</string>
    </dict>
</dict>

That’s enough for our template to start being useful. So, make sure you’ve quit Xcode fully, then launch it and make a new project. All being well you should still see Coordinated App in the list of options – if you don’t it means you made a mistake in your property list, and rather than telling you Xcode is just playing silent.

Working with ancestors

Xcode’s template system is designed to be modular, where one new template can build upon other templates to inherit functionality. If you scroll up to the top of your property list, you’ll see the following:

<key>Ancestors</key>
<array>
    <string>com.apple.dt.unit.storyboardApplication</string>
    <string>com.apple.dt.unit.coreDataCocoaTouchApplication</string>
</array>
<key>Concrete</key>
<true/>

Those two keys work together to create the inheritance system: the first defines an array of templates to pull in data from (in this case, storyboards and Core Data), and the second marks our current template as being concrete. A concrete template is one that Xcode will show inside its UI, which means you can create your own non-concrete templates to inherit from if you want.

I don’t know about you, but I have a complicated relationship with storyboards: I recognize their usefulness sometimes, but I also like to write UI in code when it makes sense, and I certainly don’t want to use segues that force my application flow to be baked into my UI design.

When using coordinators, this means I still want to have a Main.storyboarded file around so I can use storyboarded UI when needed, but I definitely don’t want my app to launch from a storyboard.

So, for this template I’m going to remove the com.apple.dt.unit.storyboardApplication ancestor, like this:

<key>Ancestors</key>
<array>
    <string>com.apple.dt.unit.coreDataCocoaTouchApplication</string>
</array>
<key>Concrete</key>
<true/>

Now, even though we still have Main.storyboard as a file in our project template, it will no longer be copied into the finished project. This is because the com.apple.dt.unit.storyboardApplication ancestor included that file in its list of nodes, so we need to do that by hand now.

So, modify the end of the Nodes array to this:

<string>Storyboarded.swift</string>
<string>Coordinator.swift</string>
<string>MainCoordinator.swift</string>
<string>Base.lproj/Main.storyboard</string>

That will include the storyboard back in the project, even without the ancestor template.

But now we have a problem: iOS uses the storyboard to bootstrap applications, and by removing the storyboard ancestor we also lost the UIMainStoryboardFile Info.plist key that does all the hard work.

Fortunately, that’s a good thing – I like my coordinators to be right in at the base of my apps, which means they need to be responsible for creating and showing the initial user interface.

Most of the code for our template actually comes from the other ancestor: com.apple.dt.unit.coreDataCocoaTouchApplication, which itself gets most of its code from com.apple.dt.unit.cocoaTouchApplicationBase. I’d like you to open the property list for that second one – it’s in the Cocoa Touch App Base.xctemplate folder.

This is a much longer property list, but there’s one specific line we’re looking for. So, press Cmd+F and search for this text:

// Override point for customization after application launch.

That’s the comment you’ve probably read a thousand times before – it’s from AppDelegate.swift, forming most of the body of didFinishLaunchingWithOptions:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    return true
}

Directly above that property list line you’ll also see one for AppDelegate.swift:implementation:properties:window that inserts var window: UIWindow?.

We need to modify both of these two, without changing all the others. Fortunately, that’s as easy as copying those definitions into the Definitions section of our own property list file, then editing as much as we want.

So, copy the following lines from Apple’s property list file:

<key>AppDelegate.swift:implementation:properties:window</key>
<string>var window: UIWindow?</string>
<key>AppDelegate.swift:implementation:methods:applicationdidFinishLaunchingWithOptions:body</key>
<string>// Override point for customization after application launch.</string>

Now look for the Definitions section in your template. It should end like this:

    <key>MainCoordinator.swift</key>
    <dict>
        <key>Group</key>
        <array>
            <string>Coordinators</string>
        </array>
        <key>Path</key>
        <string>MainCoordinator.swift</string>
    </dict>
</dict>

Please paste Apple’s line before that final </dict>, like this:

    <key>MainCoordinator.swift</key>
    <dict>
        <key>Group</key>
        <array>
            <string>Coordinators</string>
        </array>
        <key>Path</key>
        <string>MainCoordinator.swift</string>
    </dict>
    <key>AppDelegate.swift:implementation:properties:window</key>
    <string>var window: UIWindow?</string>
    <key>AppDelegate.swift:implementation:methods:applicationdidFinishLaunchingWithOptions:body</key>
    <string>// Override point for customization after application launch.</string>
</dict>

That won’t change anything, of course, because we’re using the same text as Apple’s template. But with that in place we can now customize them to work with coordinators.

First, we need a second property to store an instance of our main coordinator. So, change the window property to this:

<string>var window: UIWindow?
var coordinator: MainCoordinator?</string>

Yes, you can have line breaks freely – just treat everything inside the <string> tag as raw code.

Second, we need to create an instance of UIWindow at the correct size for our screen, give it a navigation controller backed by our main coordinator, then make it visible. This is code you probably saw a lot in the days before storyboards, but it’s not so common any more!

Anyway, replace the applicationdidFinishLaunchingWithOptions:body key with this:

<string>// Override point for customization after application launch.
let navController = UINavigationController()
coordinator = MainCoordinator(navigationController: navController)
coordinator?.start()

window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navController
window?.makeKeyAndVisible()</string>

That has successfully replaced Xcode’s default storyboard set up with a custom one powered by coordinators – but we’re not quite done yet.

Configuring the initial view controller

At this point we have an application template that is backed by coordinators, and calls the start() method on our main coordinator so it can take whatever action is needed to start up.

However, if you remember I commented out the code in our start() method:

func start() {
    // let vc = ViewController.instantiate()
    // vc.coordinator = self
    // navigationController.pushViewController(vc, animated: false)
}

To make that work, we need the ViewController class to have a coordinator property, and to conform to the Storyboarded protocol we created earlier.

To make the first of those work we need another entry to our definitions list. Start by finding this near the end of your property list:

window?.makeKeyAndVisible()</string>

Below that we're going to add a definition for a coordinator property, to be added to ViewController.swift:

<key>ViewController.swift:implementation:properties:coordinator</key>
<string>weak var coordinator: MainCoordinator?</string>

Now we need to add a node to actually use that. So, find this line in your property list:

<string>ViewController.swift:implementation(___FILEBASENAME___: UIViewController)</string>

And add this directly below:

<string>ViewController.swift:implementation:properties:coordinator</string>

The second change is to make ViewController conform to the Storyboarded protocol, which takes two steps:

  1. The view controller must use “ViewController” as its storyboard identifier.
  2. We need to add a conformance to Storyboarded in our template property list file.

To do the first step, open Main.storyboard in Xcode, the select the view controller. If you go to the identity inspector you’ll see that it already has its class name property set to ViewController, but you need to set its storyboard identifier to the same thing. So, make that “ViewController” then save your change.

To do the second step, look for this line in your property list’s nodes array:

<string>ViewController.swift:implementation(___FILEBASENAME___: UIViewController)</string>

We need to add Storyboarded to that file, so modify the line to this”

<string>ViewController.swift:implementation(___FILEBASENAME___: UIViewController, Storyboarded)</string>

Now that the main view controller conforms to Storyboarded, we can finally uncomment the code in MainCoordinator:

    func start() {
        let vc = ViewController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: false)
    }

And that’s our template complete – it puts us in a great position for adding more coordinators and more view controllers.

Where next?

One thing we didn’t cover here is the Objective-C part of the template – if you wanted to make this template complete, you should probably fill in that part of the template with something meaningful!

If you’d like to experiment further, I can highly recommend blog posts from Sophia Lazarova and Keith Harrison – you might get some fresh ideas there because they make templates to do different things, and I certainly picked up quite a few tips.

If you'd like to learn more about coordinators, I have articles that will help:

I can also highly recommend Soroush Khanlou's talk at NSSpain, where he introduced coordinators for the first time: Presenting Coordinators.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.6/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.