With the launch of Xcode 7 we were offered a sweet deal of cool new features like free on-device development, Swift 2.0, Objective-C upgrades, and some sugar in testing and debugging.
Yeah great, but if you had developed with Xcode 7 for a bit longer you would have probably noticed that in order to use a third party library, you now have to manually add the search path to it. It’s not that hard, but when you have to do it often it becomes annoying. This was kind of a pain for my team and our clients as well. Before Xcode 7 Framework Search Paths were filled automatically, but with the update this changed. A lot of our clients were surprised and inconvenienced by the requirement to fill Framework Search Paths manually now.
In order to satisfy our clients and ease their pain, we started looking for a solution to this search paths issue. Soon we came up with the idea to create a custom project template with added search paths and link to the framework.
However, most of the information on this topic is outdated. Basically almost all public articles are covering the project templates in Xcode 4, and you definitely need to spend some time searching around the web to get all the information you need.
After struggling with this myself, I decided to write this post and synthesize the steps to create a custom project template for Xcode 7, so that it could be easier for all of you. Enjoy!
First, let’s take a look into the Xcode file structure and the default project templates.
Go in Applications, find the Xcode icon, right-click and choose Open Package Contents.
Тhis path leads to the Mac OS project templates:
/Developer/Library/Xcode/Templates/Project Templates/Mac
and this one to the iOS project templates:
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS
Before we dive in, let’s set some goals here. First, we are going to build a project template which resembles the Single View Application template provided by Apple. After that we'll talk about customizations.
Alright, let’s see what is going on in the original Single View Application. Then we'll make one of our own.
Open up:
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Applications
and choose Single View Application.xctemplate.
As you can see, we have a storyboard, an image and a .plist file.
Open the TemplateInfo.plist file with Xcode, which will display it as a table with keys and values, or with any text editor which will display the file content using XML.
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<
plist
version
=
"1.0"
>
<
dict
>
<
key
>Kind</
key
>
<
string
>Xcode.Xcode3.ProjectTemplateUnitKind</
string
>
<
key
>Identifier</
key
>
<
string
>com.apple.dt.unit.singleViewApplication</
string
>
<
key
>Ancestors</
key
>
<
array
>
<
string
>com.apple.dt.unit.storyboardApplication</
string
>
<
string
>com.apple.dt.unit.coreDataCocoaTouchApplication</
string
>
</
array
>
Maybe the most important thing here is the Identifier key, whose value is com.apple.dt.unit.singleViewApplication.
Every template has an identifier key which consists of a prefix and the name of the template. Apple uses a com.apple.dt.unit prefix, so every template provided by them has it. For our custom template we have to provide our own identifier prefix.
There is also an Ancestors key, and if you are thinking this refers to inheritance, you are absolutely right. The whole deal is that the Xcode templates are organized in a sort of a tree structure, and the Ancestors key defines the parents of the current template. In fact, this is the purpose of the identifier—it allows for referring to the template in order to make possible the creation of hierarchies when there are shared properties between different templates. For example, if you follow the ancestors of the Single View Application template, you will have the following hierarchy:
Each template inherits the properties defined in its ancestors and adds specific ones on top of them. So, the easiest way to create a template is to plug it in the scheme above as last child. The basic scheme of our template will look exactly the same except for the com.apple.dt.unit.SingleViewApplication node, which will be replaced by our template.
Of course, if for some reason you have to build more templates and they share some definitions, you may want to extend the provided by Apple hierarchy in order to reduce the identical definitions in different files.
Ok, next we'll go to ~/Library/Developer/Xcode/Templates.
If the Templates directory does not exist, create it.
Then, in the Templates directory, create another folder, called Custom. It will represent the group to which our template belongs. This folder will show up in Xcode on the right side of the new project panel along with the default categories—Application and Framework & Library.
Alright, now I am going to ask you to break some programming rules. If you're okay with it, let’s do some copy/pasting.
Go to the default iOS project templates and copy the Single View Application.xctemplate.
Go back to the ~/Library/Developer/Xcode/Templates/Custom directory and paste it there.
That's it—no more copy/paste, I promise!
Now we have to open the .plist file of our template and change the identifier prefix with, let’s say, com.tkexample.dt.unit.
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<
plist
version
=
"1.0"
>
<
dict
>
<
key
>Kind</
key
>
<
string
>Xcode.Xcode3.ProjectTemplateUnitKind</
string
>
<
key
>Identifier</
key
>
<
string
>com.tkexample.dt.unit.singleViewApplication</
string
>
<
key
>Ancestors</
key
>
<
array
>
<
string
>com.apple.dt.unit.storyboardApplication</
string
>
<
string
>com.apple.dt.unit.coreDataCocoaTouchApplication</
string
>
</
array
>
Let’s test what we have just done. Open Xcode and in the new project panel you should be able to see your custom category with the project template in it.
Piece of cake, right?
Now let’s see how we can modify our template. The answer lays in the rest of the .plist file.
In my experience the most common modifications in a Project Template could be code modifications, build settings and build phases setup.
Let’s start from the build settings and what we can do here.
All the modifications over the build settings should be contained under the key SharedSettings. All build settings are described in the Apple documentation— you can find a link to the article here, and it's quite useful. If you go through the stock .plist files, you will see different definitions in the SharedSettings dictionary. Here you can include all of the build settings from the Apple article depending on your needs, including FRAMEWORK_SEARCH_PATHS.
<
key
>Project</
key
>
<
dict
>
<
key
>SharedSettings</
key
>
<
dict
>
<
key
>FRAMEWORK_SEARCH_PATHS</
key
>
<
array
>
<
string
>"your path"</
string
>
</
array
>
</
dict
>
</
dict
>
In <key>Targets</key> key we can specify different project targets with their properties like build settings or linked frameworks. The <key>Frameworks</key> key allows us to add links to frameworks, the same way we do from Build Phases >> Link Binary With Libraries.
<
key
>Targets</
key
>
<
array
>
<
dict
>
<
key
>Frameworks</
key
>
<
array
>
<
string
>CoreData</
string
>
</
array
>
</
dict
>
</
array
>
In the implementation of the Single View Application there are definitions of the ViewController class using notation with code implementations.
<
dict
>
<
key
>Objective-C</
key
>
<
dict
>
<
key
>Nodes</
key
>
<
array
>
<
string
>ViewController.h:comments</
string
>
<
string
>ViewController.h:imports:importCocoa</
string
>
<
string
>ViewController.h:interface(___FILEBASENAME___ : UIViewController)</
string
>
<
string
>ViewController.m:comments</
string
>
<
string
>ViewController.m:imports:importHeader:ViewController.h</
string
>
<
string
>ViewController.m:extension</
string
>
<
string
>ViewController.m:implementation:methods:viewDidLoad(- (void\)viewDidLoad)</
string
>
<
string
>ViewController.m:implementation:methods:viewDidLoad:super</
string
>
<
string
>ViewController.m:implementation:methods:didReceiveMemoryWarning(- (void\)didReceiveMemoryWarning)</
string
>
<
string
>ViewController.m:implementation:methods:didReceiveMemoryWarning:super</
string
>
<
string
>TestClass.h</
string
>
<
string
>TestClass.m</
string
>
</
array
>
</
dict
>
<
key
>Swift</
key
>
<
dict
>
<
key
>Nodes</
key
>
<
array
>
<
string
>ViewController.swift:comments</
string
>
<
string
>ViewController.swift:imports:importCocoa</
string
>
<
string
>ViewController.swift:implementation(___FILEBASENAME___: UIViewController)</
string
>
<
string
>ViewController.swift:implementation:methods:viewDidLoad(override func viewDidLoad(\))</
string
>
<
string
>ViewController.swift:implementation:methods:viewDidLoad:super</
string
>
<
string
>ViewController.swift:implementation:methods:didReceiveMemoryWarning(override func didReceiveMemoryWarning(\))</
string
>
<
string
>ViewController.swift:implementation:methods:didReceiveMemoryWarning:super</
string
>
<
string
>TestClass.swift</
string
>
</
array
>
</
dict
>
</
dict
>
We are going to use another notation which does not require actual code definitions. Instead of defining every class with its content in the Nodes array, we have to just declare that such files exist. Then in the Definitions dictionary we have to set the path to the files.
Let’s add another class called TestClass into our template.
Add the following lines at the bottom of the Nodes array in the Objective-C section:
<
string
>TestClass.h</
string
>
<
string
>TestClass.m</
string
>
respectively add in the Swift section.
<
string
>TestClass.swift</
string
>
Now we have to specify where our class files are located. Add the following lines in the Definitions dictionary.
<
key
>TestClass.m</
key
>
<
dict
>
<
key
>Path</
key
>
<
string
>TestClass.m</
string
>
</
dict
>
<
key
>TestClass.h</
key
>
<
dict
>
<
key
>Path</
key
>
<
string
>TestClass.h</
string
>
</
dict
>
<
key
>TestClass.swift</
key
>
<
dict
>
<
key
>Path</
key
>
<
string
>TestClass.swift</
string
>
</
dict
>
We can also organize our files into groups. Let’s put our new class into a group called CustomClasses. Modify the Definitions dictionary as show below,
and keep in mind that adding more than one value in the Group array leads to nesting of groups.
<
key
>TestClass.m</
key
>
<
dict
>
<
key
>Group</
key
>
<
array
>
<
string
>CustomClasses</
string
>
</
array
>
<
key
>Path</
key
>
<
string
>TestClass.m</
string
>
</
dict
>
<
key
>TestClass.h</
key
>
<
dict
>
<
key
>Group</
key
>
<
array
>
<
string
>CustomClasses</
string
>
</
array
>
<
key
>Path</
key
>
<
string
>TestClass.h</
string
>
</
dict
>
<
key
>TestClass.swift</
key
>
<
dict
>
<
key
>Group</
key
>
<
array
>
<
string
>CustomClasses</
string
>
</
array
>
<
key
>Path</
key
>
<
string
>TestClass.swift</
string
>
</
dict
>
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<
plist
version
=
"1.0"
>
<
dict
>
<
key
>Kind</
key
>
<
string
>Xcode.Xcode3.ProjectTemplateUnitKind</
string
>
<
key
>Identifier</
key
>
<
string
>com.tkexample.dt.unit.singleViewApplication</
string
>
<
key
>Ancestors</
key
>
<
array
>
<
string
>com.apple.dt.unit.storyboardApplication</
string
>
<
string
>com.apple.dt.unit.coreDataCocoaTouchApplication</
string
>
</
array
>
<
key
>Targets</
key
>
<
array
>
<
dict
>
<
key
>Frameworks</
key
>
<
array
>
<
string
>CoreData</
string
>
</
array
>
</
dict
>
</
array
>
<
key
>Concrete</
key
>
<
true
/>
<
key
>Description</
key
>
<
string
>This template provides a starting point for an application that uses a single view.</
string
>
<
key
>SortOrder</
key
>
<
integer
>1</
integer
>
<
key
>Options</
key
>
<
array
>
<
dict
>
<
key
>Identifier</
key
>
<
string
>languageChoice</
string
>
<
key
>Units</
key
>
<
dict
>
<
key
>Objective-C</
key
>
<
dict
>
<
key
>Nodes</
key
>
<
array
>
<
string
>ViewController.h:comments</
string
>
<
string
>ViewController.h:imports:importCocoa</
string
>
<
string
>ViewController.h:interface(___FILEBASENAME___ : UIViewController)</
string
>
<
string
>ViewController.m:comments</
string
>
<
string
>ViewController.m:imports:importHeader:ViewController.h</
string
>
<
string
>ViewController.m:extension</
string
>
<
string
>ViewController.m:implementation:methods:viewDidLoad(- (void\)viewDidLoad)</
string
>
<
string
>ViewController.m:implementation:methods:viewDidLoad:super</
string
>
<
string
>ViewController.m:implementation:methods:didReceiveMemoryWarning(- (void\)didReceiveMemoryWarning)</
string
>
<
string
>ViewController.m:implementation:methods:didReceiveMemoryWarning:super</
string
>
<
string
>TestClass.h</
string
>
<
string
>TestClass.m</
string
>
</
array
>
</
dict
>
<
key
>Swift</
key
>
<
dict
>
<
key
>Nodes</
key
>
<
array
>
<
string
>ViewController.swift:comments</
string
>
<
string
>ViewController.swift:imports:importCocoa</
string
>
<
string
>ViewController.swift:implementation(___FILEBASENAME___: UIViewController)</
string
>
<
string
>ViewController.swift:implementation:methods:viewDidLoad(override func viewDidLoad(\))</
string
>
<
string
>ViewController.swift:implementation:methods:viewDidLoad:super</
string
>
<
string
>ViewController.swift:implementation:methods:didReceiveMemoryWarning(override func didReceiveMemoryWarning(\))</
string
>
<
string
>ViewController.swift:implementation:methods:didReceiveMemoryWarning:super</
string
>
<
string
>TestClass.swift</
string
>
</
array
>
</
dict
>
</
dict
>
</
dict
>
</
array
>
<
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
>TestClass.m</
key
>
<
dict
>
<
key
>Group</
key
>
<
array
>
<
string
>CustomClasses</
string
>
</
array
>
<
key
>Path</
key
>
<
string
>TestClass.m</
string
>
</
dict
>
<
key
>TestClass.h</
key
>
<
dict
>
<
key
>Group</
key
>
<
array
>
<
string
>CustomClasses</
string
>
</
array
>
<
key
>Path</
key
>
<
string
>TestClass.h</
string
>
</
dict
>
<
key
>TestClass.swift</
key
>
<
dict
>
<
key
>Group</
key
>
<
array
>
<
string
>CustomClasses</
string
>
</
array
>
<
key
>Path</
key
>
<
string
>TestClass.swift</
string
>
</
dict
>
</
dict
>
</
dict
>
</
plist
>
Basically, this how you create custom project templates in Xcode 7.
Keep in mind that the best way to gain knowledge of something new is to play around with it. Speaking of which, here is link to the GitHub repo with the template we just created. Plug it into XCode and check out the result.
We're always adding new improvements to UI for iOS, and this and many others will be a part of the next UI for iOS release coming in the beginning of November. Feel free to get a Trial of the current official version from here.
May the force be with you, ninjas!
Sophia Lazarova is a Junior Developer in the Telerik iOS Team. She recently graduated from the Telerik Academy with flying colors to join the team and start adding value to the suite of the best iOS controls. She is passionate about software development in various technologies.