原文地址:https://www.raywenderlich.com/26582967-xcode-project-and-file-templates
If you’re tired of filling out boilerplate code whenever you begin a new Xcode project, you can save yourself a ton of time by creating customized project templates.
Using Xcode’s project and file templates
, you have the power to customize how new projects and files start out their life from the moment you use File ▸ New
. These templates allow you to pre-fill the contents of new files and project so that you can get to work on the core of your next excellent app idea more quickly.
In this tutorial, you’ll learn:
- What Xcode templates are and where to find the default templates that ship with Xcode.
- The anatomy of Xcode’s templates.
- How to create project templates.
- How to create file templates.
- Getting input from users when they use a template.
Get ready because you’re about to replace some unnecessary boilerplate with custom Xcode templates!
Getting Started
First, click the Download Materials
button at the top or bottom of this tutorial to download the project materials.
In this tutorial, you’ll build an app called Stellar Space
which uses NASA’s Astronomy Picture of the Day API to show today’s latest space-related and wallpaper-worthy photo. Along the way you’ll how to create your own templates.
Note: The starter materials don’t contain a starter Xcode project. That’s because you’ll be creating the project by yourself, using custom Xcode project templates!
Imagine for a moment that you are a big fan of the Model-View-ViewModel (MVVM) design pattern. You want to use it in all your apps, and this app is no different. Usually, a new SwiftUI app comes with a ContentView.swift for you to use for the first screen of your app. But instead of using the default template, you’ll create your own template that pairs ContentView
with a view model to separate the UI and business logic. Using that template will make your life easier when creating your projects.
Since Xcode’s templating system is robust but poorly documented, you’ll need to explore the default templates Xcode offers so you can know what you can customize.
Understanding Default Templates
Every time you’ve created a new Xcode project in the past, you did so using Xcode’s default templates. These default templates make things easier than starting with a blank project.
Templates decide what your new project will contain. The default templates include many different options including, but not limited to:
- A blank view with a “Hello, World!” text.
- Setup code for your Core Data implementation
- A fully functioning ARKit scene
Xcode’s rich and complex templating system also allows user input, conditional logic and many combinations of files. Each template also offers inheritance, meaning the templates you create can adopt logic from one another or the default templates.
Inheritance also means that all the fantastic behavior of the default templates is up for grabs. Consequently, looking at the default templates is a great way to start creating templates of your own.
Exploring Base Templates
Xcode stores the default templates within the Xcode app bundle itself. To take a look at all the base templates, open a Finder window. You can do this by opening Finder and pressing Shift-Command-G or going to Go ▸ Go to Folder…. Then, copy and paste the location of the default project templates into the resulting window:
Finder's folder navigation pop-up containing project templates' folder path/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates
Next, click Go.
Folder content of the Project TemplatesThe Base folder contains templates that you won’t see directly in Xcode’s New Project dialog. These base templates are abstract, and they serve as good starting points that other templates can inherit from.
Folder structure of the Project Template's Base folderThe Base folder contains most of Xcode’s default templates. iOS templates are in a different folder. You’ll look at those next.
Exploring iOS Templates
In Finder, go to Go ▸ Go to Folder… again. Then, enter the location of the default iOS templates as follows:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS
Click Go.
Folder content of the iOS Project TemplatesYou’ll see the iOS folder. Here, you’ll find templates for the iOS category in Xcode, as seen in this screenshot:
Xcode pop-up with the iOS categories available for choosing a template for creating a new projectOpen the Application subfolder in Finder, and then open App.xctemplate.
Folder content of App.xctemplateTake a look. When you create a new iOS app, Xcode uses this template. Next, you’ll copy this template to a different location so you can customize it and make it your own.
Creating Your First Project Template
Now it’s time to create your custom project template. There are three basic steps:
- Copy an existing template. In this case, App.xctemplate.
- Create a folder to store all your custom templates.
- Modify the copied template to match your needs.
First, while in the iOS default templates folder, right-click App.xctemplate and click Copy.
Next, open a new Finder window. Press Command-Shift-G or go to Go ▸ Go to Folder… and type in the path to the Xcode folder in your user library:
~/Library/Developer/Xcode
Then, click Go.
Contents of the Xcode folderThe Xcode folder stores device logs, application archives and other user data. Most importantly, for now, it also stores your custom templates. It is in your home directory and therefore is specific to your user account on the machine.
To create new custom template subfolder, right-click in this folder and then click New Folder. Name the subfolder Templates. Then, open the new Templates subfolder.
Create a new subfolder named Templates and open it in finderThis folder will store your custom templates. Now, right-click in the folder and click Paste Item to paste the copied App.xctemplate.
Next, rename the item you just pasted from App.xctemplate to View Model App.xctemplate.
Your new template looks like this:
Content of custom template, View Model App.xctemplateTo make your template work, you’ll need to give it an unique identifier. Inside View Model App.xctemplate, open TemplateInfo.plist with Xcode.
Contents of TemplateInfo.plistChange the value for Identifier to com.raywenderlich.mvvm.
Voilà! :] You just created a new custom template!
However, you’ll need to add code to make it more meaningful. Xcode knows to read templates in the folder you just created. However if Xcode is already open, you need to quit and restart to enable your changes. Then, go to File ▸ New ▸ Project… to open the New Project dialog.
Scroll to the bottom of the iOS category. Here, you’ll find a new Templates section that holds your custom templates.
Xcode pop-up to choose custom templatesSelect View Model App and click Next.
Template options for first custom templateYour template comes with the same rich list of options as the default app template. How does a simple template exhibit all that behavior?
Contents of a Template
You’re now going to look at the template you just created to learn what makes up a template.
Go back to Finder and open View Model App.xctemplate.
Inside the template, you’ll find:
- TemplateInfo.plist: The file which is the driving force behind the template. This file contains the logic that determines what you’ll see in the New Project dialog and how to interpret any user input.
- Any other files that will be used to form the template project. Currently, you’ll only see Main.storyboard as that’s the only file necessary to kick off the project.
Usually, a SwiftUI app starts with a ContentView.swift file, but it’s missing from this template folder. That’s because this template inherits ContentView.swift indirectly from another template called iOS SwiftUI App.xctemplate, located in the same folder as the original App.xctemplate you copied.
App.xctemplate inherits from Core Data Cocoa Touch App.xctemplate, which in turn inherits from iOS SwiftUI App.xctemplate. It’s templates all the way down! :]
Now that you’ve reviewed the templates, you’ll create a template ViewModel.swift file and connect it with your custom template’s ContentView.swift. This will be the first customization of the template you’ll do to start seeing the power of custom templates!
Customizing the Project Template
The first step to customizing your project template is creating a view model class.
Open Terminal.app and enter the following:
touch ~/Library/Developer/Xcode/Templates/View\ Model\ App.xctemplate/ViewModel.swift
Note: Make sure you’ve created View Model App.xctemplate with the exact name and location as instructed, or the previous command won’t work.
Terminal to run the touch command to create ViewModel.swiftThis command creates an empty ViewModel.swift file within your template.
ViewModel.swift added in the custom project custom templateNext go back to Finder and open ViewModel.swift in Xcode. At the moment, it’s blank, so add the following code to its contents:
import Foundation
import Combine
class ViewModel: NSObject, ObservableObject {
@Published var title = "Hello, world! :]"
}
This creates a template ViewModel
class that will drive your app’s view behavior.
With the code above, you’ve done a few things:
At the top is the ___FILEHEADER___
text macro. This macro is defined by Xcode and populates the file with copyright information, including your name, the current date, and the file name. ___FILEHEADER___
looks like this:
// ___FILENAME___
// ___PACKAGENAME___
//
// Created by ___FULLUSERNAME___ on ___DATE___.
// ___COPYRIGHT___
//
After ___FILEHEADER___
, the code added the necessary imports and defined the ViewModel
class containing a title
string for now.
Save and close ViewModel.swift.
Your template will now create ContentView.swift from the inheritance described earlier and also ViewModel.swift from the file you just added. But you haven’t done anything to tell your template to add these files to a new Xcode project. To do this you’ll dive into TemplateInfo.plist. First, take a look at what the TemplateInfo.plist contains.
Working With Project Template Info
TemplateInfo.plist is the brains of your template. It instructs the template how to connect with ViewModel.swift. Next, you’ll explore TemplateInfo.plist‘s contents and create those instructions.
Open TemplateInfo.plist with Xcode.
Custom Project TemplateInfo.plist in XcodeBelow are some essential keys you’ll find inside the property list:
-
Kind: The type of template. In this case it is
Xcode.Xcode3.ProjectTemplateUnitKind
, which means it’s a project template. - Identifier: A unique identifier for your template.
-
Ancestors: A list of identifiers of other templates that this template inherits from. You’ll see two items in this array:
com.apple.dt.unit.coreDataCocoaTouchApplication
andcom.apple.dt.unit.sceneLifecycleApplication
. -
Concrete: Indicates if the template is available directly while creating a new project in Xcode. Since you want to use this template to create a custom project, set it to
true
. If set tofalse
, you can’t use the template directly as it will be an abstract base template from which your other templates can inherit information. - Description: A short description of the template.
- SortOrder: Usually, Xcode arranges templates alphabetically, but you can set the sort order here to override the alphabetical sorting.
- NameOfInitialFileForEditor: Indicates the file that Xcode will display once the new project is ready.
-
Options: A list of different dialog options in the New Project dialog.
Options
is the most complex as well as the most important of all the keys. UsingOptions
, you can ask the user for input in the form of text fields, checkboxes and drop-down lists. Additionally, theOptions
section determines how to interpret user input.
Next, you’ll learn more about these options so you’ll know what “options” you have when you’re building your custom template.
Exploring Options
Click the arrow to the left of Options to open it. Then, do the same to open Item 1.
Options under Custom Project TemplateInfo.plist in XcodeThis option allows users to choose the interface type for their projects. They can choose either SwiftUI or Storyboard.
Choosing the interface type while creating a new iOS projectYou want to add ViewModel.swift to the project if the user chooses SwiftUI. You’ll do that shortly.
Here’s what’s inside the Item 1
dictionary:
- Identifier: A unique identifier to reference the option.
- Name: The name of the option that the user will see in the New Project dialog.
- Description: A short description of the option’s purpose.
-
Values: A list of possible values for
popup
type options. - Default: A default value in case the user doesn’t choose one.
-
Type: The type of the option, either
popup
,checkbox
ortext
.
Bravo! You just finished learning the anatomy of the brain of a template. It’s time to now feed in your custom code. :]
Adding the View Model
So far, you’ve used Xcode’s Property List Editor to navigate TemplateInfo.plist. Xcode is a great way to view and tweak property lists, but it can be tough to make larger changes. Instead, you’ll edit the raw XML.
Open TemplateInfo.plist in TextEdit.app or your favorite text editor. Then, near the bottom of the file, find Values. It’ll look like this:
Interface Values in TemplateInfo.plistRemove the Values
key as well as the array under it. Replace it with this:
<key>Units</key>
<dict>
<key>SwiftUI</key>
<array>
<dict>
<key>Nodes</key>
<array>
<string>ViewModel.swift</string>
</array>
<key>Definitions</key>
<dict>
<key>ViewModel.swift</key>
<dict>
<key>Path</key>
<string>ViewModel.swift</string>
</dict>
</dict>
</dict>
</array>
<key>Storyboard</key>
<dict></dict>
</dict>
Save the file and reopen it in Xcode. It will look like this:
Interface Units type in TemplateInfo.plist opened in XcodeHere’s what’s in the items above:
-
Units
is a list of values for the option, along with the behavior for each value. - Each item in the dictionary is a
Value
that the user will see in the New Project dialog. Currently, the values are SwiftUI and Storyboard, which are the same as the oldValues
. - Under the SwiftUI value, there’s a dictionary for your added behavior.
-
Nodes
is a list of files that will be included if the user chooses the SwiftUI value as the option foruserInterface
. -
Definitions
contains the actual path for any files inNodes
. - A
Storyboard
value that’s the same as the one in the oldValues
, without any behavior.
You’re finally ready to use your template to actually create your project!
Using the Template
Now, you’ll use the template you just set up. First, close Xcode, then reopen it for your changes to take effect.
Go to File ▸ New ▸ Project… to open the New Project dialog. Scroll to the bottom of the iOS category and select your View Model App template. Click Next.
Select the View Model App template while creating a new project in XcodeOn the next screen, set the following values for the options:
- Product Name: Stellar Space.
- Organization Identifier: com.raywenderlich.
- Interface: SwiftUI.
- Language: Swift.
Click Next, and then Create.
Xcode pop-up to choose options for creating new projectBuild and run. You’ll be presented with the classic “Hello, world!” starter screen:
imageYou’ll see the ViewModel.swift file in your project structure. However, ContentView
isn’t tied to ViewModel
yet. In ContentView.swift, add the following inside ContentView
, above body
:
@ObservedObject var viewModel = ViewModel()
Next, find Text
inside body
. Then, replace it with this:
Text(viewModel.title)
Build and run.
Starter Screen with Label populated using ViewModel dataYou’re now pulling the text from the view model.
You’ve created a project with your custom template and used the new ViewModel
. Next, you’ll flesh out the rest of Stellar Space.
Populating the Project
You’ll find most of the files for Stellar Space inside the project materials that you downloaded earlier.
Open the project materials in Finder and select the API and UI folders.
Copying API and UI folder from the start project to the current projectThen, drag these into Xcode’s Project navigator.
The copied folders are linked in the current workspace folder in XcodeSelect the Copy items if needed checkbox. Click Finish.
Select Copy items if needed to include new files in the current projectYou’ve added a bunch of Swift files to Stellar Space. Later, you’ll use the files in the API folder to communicate to NASA’s API, and you’ll use the files in the UI folder to beautify your ContentView
.
Next, select Assets in Xcode’s Project navigator. Delete the empty AppIcon asset.
Delete the default AppIcon givin in the Assets.xcassets folderFinally, drag AppIcon.appiconset from the project materials into Assets, right next to AccentColor.
Drag AppIcon.appiconset from the project materials into AssetsYou’ve just created Stellar Space’s app icon.
Build and run.
A screen showing errors in the project due to API errorsOh, no! Looks like there’s an APIError
object missing from Stellar Space. APIError
is an enum
that Stellar Space uses to identify a special group of errors. Don’t worry, because next, you’ll learn about Xcode file templates and create the APIError
enumeration in the process.
Creating File Templates
So far, you’ve worked with project templates that Xcode uses to create new projects. But Xcode also uses template files when you add a file to an existing project, so now you’ll work on customizing file templates.
Using file templates reduces the amount of boilerplate you need to write. Therefore, you are best to employee custom file templates when you find yourself often creating new files and writing almost identical boilerplate code to start the file off.
Everything you see in the New File dialog is a template, just like in the New Project dialog.
Xcode pop-up to choose a template for creating a new fileAPIError
is an enum
, so you’ll create an Xcode template that makes a single Swift file with an enum
inside. That can be useful as you might often create enumerations and having a template to make life easier doing so will reduce your time spent writing boilerplate code.
To create a Swift Enum template for APIError
, you’ll copy and modify the default Swift File template.
Copying a Default File Template
Xcode stores its file templates alongside project templates. In Finder, go to Go ▸ Go to Folder… and type in the path to the default file templates:
/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates
Click Go.
Xcode library content with File TemplatesOpen the MultiPlatform subfolder and then the Source folder within. Here, you’ll find the Swift File.xctemplate.
Swift file template under Source folderSwift File.xctemplate is the template that Xcode uses to create a plain Swift file.
It’s a simple template and a great starting point for your enum template as it’s very close to what you need. Copy Swift File.xctemplate to the location where you stored your custom templates:
Copy and Paste the Swift File.xctemplate in the Templates folder~/Library/Developer/Xcode/Templates/
Next, within the custom templates folder, rename Swift File.xctemplate to Swift Enum.xctemplate. When you’re done, the custom templates folder will look like this:
Rename Swift File.xctemplate to Swift Enum.xctemplateGreat! But it’s still the boilerplate Swift File template. So, now, you’ll customize it.
Customizing the File Template Info
You’ll use your new Swift Enum.xctemplate template to create APIError.swift, which contains an enum
with an Error
raw value. So, it’d be great if your template gave users the option to set a raw value for the enum when creating a file from the template. You’ll do that by adding an option to TemplateInfo.plist.
In Swift Enum.xctemplate, open TemplateInfo.plist with Xcode. Change the values for the following keys:
- Description: A Swift enum with a raw value.
- Summary: A Swift enum with a raw value.
- DefaultCompletionName: Enum.
Description
and Summary
are used by Xcode in the same way as project templates. However, DefaultCompletionName
is new. The value of this key will be the name of any file you create if you don’t rename it. In other words, without changing the file name, the template would name a new file Enum.swift.
At this stage, TemplateInfo.plist looks like this:
TemplateInfo.plist for new Swift fileNow you need to include an option to ask the user to enter a raw value for the enum, such as Error
. This will be user input that’s used while generating the new file from the template.
To do so, open TemplateInfo.plist in TextEdit.app or any other text editor. Near the top of the file, after the very first <dict>
, add the following:
<key>Options</key>
<array>
<dict>
<key>Type</key>
<string>text</string>
<key>Description</key>
<string>Raw value for enumeration</string>
<key>Name</key>
<string>Raw Value:</string>
<key>Required</key>
<true/>
<key>Identifier</key>
<string>rawValue</string>
</dict>
</array>
Your TemplateInfo.plist will look like this when you’re done:
TemplateInfo.plist after adding custom options to choose while creating a new swift fileYou’ve added a new Options
array with one dictionary inside it. This will tell Xcode to provide an option when using the template. In this case, the single option added represents a Raw Value option to choose a raw value for the enumeration. Here’s what each item in the dictionary means:
-
Type: The type of user input:
text
,checkbox
orpopup
. - Description: A short description of the option.
- Name: The name of the option that the user will see in the New File dialog.
- Required: A boolean value to decide whether or not the user needs to enter a value for the option before continuing.
- Identifier: A unique identifier to reference the value of the option.
You’re done adding file template info. Now, you need to tell the template how to create the Swift file in the way you need.
Creating Template Files
Inside Swift Enum.xctemplate, open FILEBASENAME.swift. This is the file that your file template creates in your project.
___FILEBASENAME___
is a text macro that all file templates have. It’s the name that you choose for the file in the New File dialog, just without the file extension. For example, if you choose APIError.swift as the file name when using the template, then Xcode sets ___FILEBASENAME___
to APIError
.
Inside FILEBASENAME.swift, replace the contents with this:
//___FILEHEADER___
import Foundation
enum ___FILEBASENAMEASIDENTIFIER___: ___VARIABLE_rawValue___ {
case one
case two
}
You’ve created a simple enum with two default cases. In this file, you use three template macros. Here’s what each of these do.
At the top of the file is ___FILEHEADER___
, which sets the usual copyright, file and author details.
Next, instead of a name for the enum, you have ___FILEBASENAMEASIDENTIFIER___
. This is the same as the ___FILEBASENAME___
variable for the file name, except that you can’t use ___FILEBASENAME___
inside the template file.
Finally, ___VARIABLE_rawValue___
is a reference to the rawValue
option you created earlier. The format looks like ___VARIABLE_<optionidentifiername style="box-sizing: inherit;">___</optionidentifiername>
.
That’s all you need to set up your template. Quit and reopen Xcode. Next, you’ll use your template to create APIError.swift.
Using the Template
In Xcode, with Stellar Space open, go to File ▸ New ▸ File…. Then, choose your Swift Enum template.
For Raw Value, enter Error. Then, click Next.
Xcode pop-up to enter Raw Value in the Swift Enum templateName the file APIError.swift and click Create.
Open APIError.swiftcases within APIError
and replace them with this:
case network(description: String)
case parsing(description: String)
Finally, build and run.
Starter screen with updated Label text driven from ViewModelThe app compiles! But, you still have some finishing touches to add to ContentView
and ViewModel
to display more than just a fancy message on the screen. :]
Finishing the Project
On the final stretch now to finish off the project to display those lovely NASA images.
In Xcode, open ViewModel.swift. First, add the following import at the top of the file:
import UIKit
@Published var image: UIImage?
@Published var errorMessage: String?
@Published var isLoading = true
@Published var date = Date()
var canMoveForward: Bool {
!calendar.isDateInToday(date)
}
var dateString: String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
return dateFormatter.string(from: date)
}
private let spaceImageFetcher = SpaceImageFetcher()
private let calendar = Calendar.current
private var disposables = Set<AnyCancellable>()
func changeDate(days: Int) {
if let newDate = calendar.date(byAdding: .day, value: days, to: date) {
date = newDate
loadImage()
}
}
func loadImage() {
isLoading = true
image = nil
errorMessage = nil
spaceImageFetcher.dailyPicture(date: date)
.receive(on: DispatchQueue.main)
.flatMap { dailyPic -> AnyPublisher<UIImage?, APIError> in
self.title = dailyPic.title
return self.spaceImageFetcher.loadImage(url: dailyPic.url)
}
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { [weak self] value in
switch value {
case .failure(let reason):
print("Failure: \(reason.localizedDescription)")
self?.errorMessage = "Something went wrong."
case .finished:
break
}
self?.isLoading = false
}, receiveValue: { image in
self.image = image
})
.store(in: &disposables)
}
I won’t explain the code here because it’s not relevant to the tutorial. All you need to know is that this code adds the logic for accessing the NASA API and displaying images.
Then, open ContentView.swift. Replace the declaration of body
with this:
var body: some View {
ZStack {
ColorPalette.primary.ignoresSafeArea()
VStack {
Text(viewModel.dateString)
.foregroundColor(ColorPalette.accent)
Spacer()
if !viewModel.isLoading {
if let image = viewModel.image {
Image(uiImage: image)
.resizable()
.scaledToFit()
Text(viewModel.title)
.foregroundColor(ColorPalette.accent)
} else if let error = viewModel.errorMessage {
Text(error)
.foregroundColor(ColorPalette.secondary)
}
Spacer()
ZStack {
HStack {
Button("Back", action: changeDateBack)
.padding(.leading, 20)
Spacer()
if viewModel.canMoveForward {
Button("Forward", action: changeDateForward)
.disabled(!viewModel.canMoveForward)
.padding(.trailing, 20)
}
}
}
} else {
ProgressView()
.progressViewStyle(ProgressViewWithBackgroundStyle())
.frame(width: 100, height: 100)
Spacer()
}
}
}
.onAppear(perform: fetchImage)
.buttonStyle(SpaceButtonStyle())
}
func changeDateBack() {
viewModel.changeDate(days: -1)
}
func changeDateForward() {
viewModel.changeDate(days: 1)
}
func fetchImage() {
viewModel.loadImage()
}
With this, ContentView
has the UI to display everything happening in ViewModel
. Build and run.
[图片上传中...(image-564458-1642558611777-0)]
At last, you’ve completed Stellar Space, and you have two new Xcode templates to show for it: a project template and a file template!
Where to Go From Here?
In this tutorial, you created both a project template and a file template, using Xcode’s default templates as a source of inspiration.
You added your files to your templates, controlled them with the Template Options file and customized them with variables.
Templates are a great example of the power of an IDE like Xcode. If you’d like to learn more about how Xcode can make your life easier, iOS App Distribution & Best Practices dives under the hood of the Xcode project structure.
Your project template separates business logic from UI code by including a view model. If you’d like to learn more about project architecture, have a look at Advanced iOS App Architecture. You might get some ideas for new templates, too!
Please join the discussion below if you have questions or comments or want to share some of your creative templates.
网友评论