iOS

Xcode project templates 2: How to auto-configure a project

Every time I set up a new project, it took a full working day to get it ready for development. Until I discovered templates. Xcode templates have been around since the days of Xcode 4, but I only recently learned about them and they have completely changed the way I look at project creation. Try them with me and new projects will suddenly be that much more consistent and maintainable.

Andrej Jaššo20 Sep 2021

In the previous article about Xcode projet templates, I walked you through running this tool step by step and pointed out its benefits. This time I will introduce you to the additional capabilities of Xcode project templates, where when generating the project we will have a podfile ready with a pre-filled configuration. What that means in practice is that, after we generate new project, we enter the project via the console, we call
pod install

and the project will have pods configured. This will significantly reduce the configuration of the project and the style of the podfiles will be uniform when it comes to more complex podfiles.

The first thing to do is to create a new template, similar to last time, in the project templates folder with the .xctemplate extension

Here we will first create a Podfile file and insert the pods we want to import into each new project.

platform :ios, '___VARIABLE_VERSION___'

target '___PACKAGENAME___' do
  use_frameworks!

  pod 'SwiftGen'
  pod 'SwiftLint'
  
end

Note the variables used __VARIABLE_VERSION__ which sets the minimum project version below and __PACKAGENAME__ which sets the project name. When creating a project, __VARIABLE_VERSION__ will be the input parameter and __PACKAGENAME__ will be taken from the project name.

Then it is necessary to configure the TemplateInfo.plist file which represents the configuration of the template

<?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> <!--States the template type-->
	<string>Xcode.Xcode3.ProjectTemplateUnitKind</string>
	<!--States if the template is a standalone template or a part
	of template used in a diffrent template. In this case it will
	be hidden from the template wizzard-->
	<key>Concrete</key>
 	<true/>
	<key>Identifier</key>  <!--Template ID in the "template space"-->
	<string>com.goodrequest.CocoaPods</string>
	<key>Definitions</key>  <!--Template variables-->
	<dict>
		<key>../Podfile</key>
		<dict>
			<key>Path</key>
			<string>Podfile</string>
			<key>Group</key>
			<string>Resources</string>
		</dict>
	</dict>
	<key>Options</key>  <!--"USER INPUT" options-->
	<array>
		<dict>
			<key>Identifier</key> <!--Option ID-->
			<string>cocoapods</string>
			<key>Name</key> <!--Field title-->
			<string>CocoaPods</string>
			<key>Description</key> <!--Field tooltip-->
			<string>Integrate CocoaPods template</string>
			<key>SortOrder</key> <!--Sorting priority-->
			<integer>250</integer>
			<key>Type</key> <!--Field type-->
			<string>checkbox</string>
			<key>Default</key> <!--Default value-->
			<string>false</string>
			<key>Units</key> <!--Field outcome-->
			<dict>
				<key>true</key> <!--True branch outcome-->
				<array>
					<dict>
						<key>Nodes</key> <!--Files to include if True-->
						<array>
							<string>../Podfile</string>
						</array>
					</dict>
				</array>
			</dict>
		</dict>
		<dict>
			<key>Identifier</key>  <!--Option ID-->
			<string>VERSION</string>
			<key>Required</key> <!--Required to fill in the field-->
			<true/>
			<key>Name</key> <!--Field title-->
			<string>iOS Min. Version</string>
			<key>Description</key> <!--Field tooltip-->
			<string>Minimum supported iOS version</string>
			<key>Type</key> <!--Field type-->
			<string>text</string>
			<key>Default</key> <!--Default value-->
			<string>13.0</string>
			<key>NotPersisted</key> <!--Remember last filled in choice-->
			<true/>
		</dict>
	</array>
</dict>
</plist>

Here it is essential to note the identifier variable

  • Kind: The template type is project
  • Concrete: Determines whether the template is visible when selecting a template or whether it is only part of another template and therefore not visible when selected. We will use this template as part of the template from the previous article and thus set false
  • Identifier: Specifies the template ID between templates. This is so that we can refer to this template from another template
  • Definitions: Used to define the variable, which in our case is the Podfile file and the place where it will be created in the project.
  • Definitions Path: Services as the path to the file in the templates folder
  • Definitions Group: Services as a file path after creating a new project. If there is more nesting, it must be defined as an array of strings where each string represents one nesting of the path
  • Options: Here we have defined all inputs of the template user when creating. There is a checkbox if we want to include podfile and iOS version as text input.
  • Options Identifier: Option ID. This can be accessed from files via  ___ VARIABLE_VERSION ___ where you enter the name of your input instead of VERSION.
  • Options Name: Field name
  • Options Desciption: Tooltip array
  • Options SortOrder: Specifies the usage of the field in the template form
  • Options Type: Specifies the type of user input
  • Options Default: Specifies the default value for the option
  • Options Units: Specifies which file the template will select within the logical branch, in this case within the checkbox it is either true or false and if the option is false it does not select any files and therefore in our case it is not necessary to define the option.
  • Options Required: Specifies whether it is possible to create a project even with an empty option
  • Options NotPersisted: Specifies whether the template will remember the last option filled

However, we now want to insert this template into our project template from the previous instructions.

The result will be as follows.

<?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.goodrequest.cocoaTouchApplicationBase</string>
		<string>com.goodrequest.CocoaPods</string> <!--ID of the Pod template-->
	</array>
	<key>Concrete</key>
	<true/>
	<key>Definitions</key>
	<dict>
		<key>Application/AppDelegate.swift</key>
		<dict>
			<key>Path</key>
			<string>Application/AppDelegate.swift</string>
			<key>Group</key>
			<array>
				<string>Application</string>
			</array>
		</dict>
		<key>Extensions/Foundation/String+Extensions.swift</key>
		<dict>
			<key>Path</key>
			<string>Extensions/Foundation/String+Extensions.swift</string>
			<key>Group</key>
			<array>
				<string>Extensions</string>
				<string>Foundation</string>
			</array>
		</dict>
		<key>Extensions/UIKit/UIView+Extensions.swift</key>
		<dict>
			<key>Path</key>
			<string>Extensions/UIKit/UIView+Extensions.swift</string>
			<key>Group</key>
			<array>
				<string>Extensions</string>
				<string>UIKit</string>
			</array>
		</dict>
		<key>Helpers/TypeAliases.swift</key>
		<dict>
			<key>Path</key>
			<string>Helpers/TypeAliases.swift</string>
			<key>Group</key>
			<string>Helpers</string>
		</dict>
		<key>Models/Error/AppError.swift</key>
		<dict>
			<key>Path</key>
			<string>Models/Error/AppError.swift</string>
			<key>Group</key>
			<array>
				<string>Models</string>
				<string>Error</string>
			</array>
		</dict>
		<key>Models/Response/SampleResponse.swift</key>
		<dict>
			<key>Path</key>
			<string>Models/Response/SampleResponse.swift</string>
			<key>Group</key>
			<array>
				<string>Models</string>
				<string>Response</string>
			</array>
		</dict>
		<key>Models/Request/SampleRequest.swift</key>
		<dict>
			<key>Path</key>
			<string>Models/Request/SampleRequest.swift</string>
			<key>Group</key>
			<array>
				<string>Models</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Managers/Dependency/DependencyContainer.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Dependency/DependencyContainer.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Dependency</string>
			</array>
		</dict>
		<key>Managers/Request/RequestManager.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Request/RequestManager.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Managers/Request/RequestManagerType.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Request/RequestManagerType.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Managers/Request/Endpoint.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Request/Endpoint.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Coordinators/Coordinator.swift</key>
		<dict>
			<key>Path</key>
			<string>Coordinators/Coordinator.swift</string>
			<key>Group</key>
			<string>Coordinators</string>
		</dict>
		<key>Coordinators/AppCoordinator.swift</key>
		<dict>
			<key>Path</key>
			<string>Coordinators/AppCoordinator.swift</string>
			<key>Group</key>
			<string>Coordinators</string>
		</dict>
		<key>Screens/SampleController/SampleViewController.swift</key>
		<dict>
			<key>Path</key>
			<string>Screens/SampleController/SampleViewController.swift</string>
			<key>Group</key>
			<array>
				<string>Screens</string>
				<string>SampleController</string>
			</array>
		</dict>
		<key>Screens/BaseViewController.swift</key>
		<dict>
			<key>Path</key>
			<string>Screens/BaseViewController.swift</string>
			<key>Group</key>
			<array>
				<string>Screens</string>
			</array>
		</dict>
		<key>Views/SampleView/SampleView.swift</key>
		<dict>
			<key>Path</key>
			<string>Views/SampleView/SampleView.swift</string>
			<key>Group</key>
			<array>
				<string>Views</string>
				<string>SampleView</string>
			</array>
		</dict>
		<key>Resources/Assets.xcassets</key>
		<dict>
			<key>Path</key>
			<string>Resources/Assets.xcassets</string>
			<key>Group</key>
			<array>
				<string>Resources</string>
			</array>
		</dict>
		<key>Resources/Colors.xcassets</key>
		<dict>
			<key>Path</key>
			<string>Resources/Colors.xcassets</string>
			<key>Group</key>
			<array>
				<string>Resources</string>
			</array>
		</dict>
		<key>Resources/SwiftGen/swiftgen.yml</key>
		<dict>
			<key>Path</key>
			<string>Resources/SwiftGen/swiftgen.yml</string>
			<key>Group</key>
			<array>
				<string>Resources</string>
				<string>SwiftGen</string>
			</array>
		</dict>
	</dict>
	<key>Nodes</key>
	<array>
		<string>Application/AppDelegate.swift</string>
		<string>Extensions/UIKit/UIView+Extensions.swift</string>
		<string>Extensions/Foundation/String+Extensions.swift</string>
		<string>Helpers/TypeAliases.swift</string>
		<string>Models/Error/AppError.swift</string>
		<string>Models/Response/SampleResponse.swift</string>
		<string>Models/Request/SampleRequest.swift</string>
		<string>Managers/Dependency/DependencyContainer.swift</string>
		<string>Managers/Request/RequestManager.swift</string>
		<string>Managers/Request/RequestManagerType.swift</string>
		<string>Managers/Request/Endpoint.swift</string>
		<string>Coordinators/Coordinator.swift</string>
		<string>Coordinators/AppCoordinator.swift</string>
		<string>Screens/SampleController/SampleViewController.swift</string>
		<string>Screens/BaseViewController.swift</string>
		<string>Views/SampleView/SampleView.swift</string>
		<string>Resources/SwiftGen/swiftgen.yml</string>
		<string>Resources/Colors.xcassets</string>
		<string>Resources/Assets.xcassets</string>
		<string>Info.plist:UIMainStoryboardFile</string>
		<string>Info.plist:UIApplicationSceneManifest:UISceneStoryboardFile</string>
	</array>
</dict>
</plist>

The only way these files differ is the ancestors key where we add the Identifier from the template we just created.

<key>Ancestors</key>
	<array>
		<string>com.goodrequest.cocoaTouchApplicationBase</string>
		<string>com.goodrequest.CocoaPods</string> <!--ID of the Pod template-->
	</array>

The only way these files differ is the ancestors key where we add the Identifier from the template we just created.

Andrej Jaššo20 Sep 2021