Xcode ToolsMidOpen-ended
How do you create and install an Xcode File Template for a UIKit MVP architecture feature?
Explanation & Code
Answer:
An Xcode MVP Feature template generates four pre-wired files for UIKit screens (Contract, Presenter, View, and ViewController) using protocol abstractions and @MainActor isolation to maintain strict separation of concerns and full testability without simulator dependencies.
File ▸ New ▸ File from Template… ▸ UIKit ▸ MVP Feature ▸ "Profile"
Generates:
ProfileContract.swift Protocols defining bidirectional view/presenter communication
ProfilePresenter.swift Presentation logic (independent of UIKit)
ProfileView.swift UIKit layout and subviews (no business logic)
ProfileViewController.swift Coordinates the lifecycle, owns customView, and connects presenter
1. Installation Script
Run this shell script in Terminal to install the template into Xcode's user templates directory:
set -e
TPL=~/Library/Developer/Xcode/Templates/"File Templates"/UIKit/MVPFeature.xctemplate
mkdir -p "$TPL"; cd "$TPL"
V='___VARIABLE_productName:identifier___'
H='//
// ___FILENAME___
// ___PACKAGENAME___
//
// Created by ___FULLUSERNAME___ on ___DATE___.
// ___COPYRIGHT___
//'
cat > TemplateInfo.plist <<'EOF'
<?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.IDEFoundation.TextSubstitutionFileTemplateKind</string>
<key>Identifier</key>
<string>com.template.UIKitMVPFeature</string>
<key>Name</key>
<string>MVP Feature</string>
<key>Description</key>
<string>Creates a UIKit MVP feature: Contract, Presenter, View and ViewController.</string>
<key>Summary</key>
<string>Create UIKit MVP Feature</string>
<key>SortOrder</key>
<string>1</string>
<key>Platforms</key>
<array>
<string>com.apple.platform.iphoneos</string>
</array>
<key>Options</key>
<array>
<dict>
<key>Identifier</key><string>productName</string>
<key>Name</key><string>Feature Name:</string>
<key>Description</key><string>Base name of the feature, e.g. Profile (no suffix).</string>
<key>Type</key><string>text</string>
<key>Required</key><true/>
<key>NotPersisted</key><true/>
</dict>
</array>
</dict>
</plist>
EOF
cat > ___FILEBASENAME___Contract.swift <<EOF
$H
import Foundation
/// What the presenter is allowed to ask the view to do.
@MainActor
protocol ${V}ViewProtocol: AnyObject {
func display(title: String)
}
/// What the view is allowed to ask the presenter to do.
@MainActor
protocol ${V}PresenterProtocol: AnyObject {
func viewDidLoad()
}
EOF
cat > ___FILEBASENAME___Presenter.swift <<EOF
$H
import Foundation
final class ${V}Presenter: ${V}PresenterProtocol {
/// Weak reference to avoid retain cycles with ViewController.
private weak var view: ${V}ViewProtocol?
init(view: ${V}ViewProtocol) {
self.view = view
}
func viewDidLoad() {
view?.display(title: "${V}")
}
}
EOF
cat > ___FILEBASENAME___View.swift <<EOF
$H
import UIKit
/// All the layout for the screen. Holds no business logic.
final class ${V}View: UIView {
let titleLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .largeTitle)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
backgroundColor = .systemBackground
addSubview(titleLabel)
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 16),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16)
])
}
}
EOF
cat > ___FILEBASENAME___ViewController.swift <<EOF
$H
import UIKit
final class ${V}ViewController: UIViewController {
private let customView = ${V}View()
private lazy var presenter: ${V}PresenterProtocol = ${V}Presenter(view: self)
override func loadView() {
view = customView
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.viewDidLoad()
}
}
// MARK: - ${V}ViewProtocol
extension ${V}ViewController: ${V}ViewProtocol {
func display(title: String) {
customView.titleLabel.text = title
}
}
EOF
plutil -lint TemplateInfo.plist
2. Architecture Details
- Weak View Reference:
Presenterholds aweak var view: ViewProtocol?becauseViewControllerowns the presenter. Strong back-references would create retain cycles and memory leaks. - Pure Swift Presenter: The Presenter avoids importing
UIKit, keeping business and presentation decisions decoupled from UI components. - Swift 6 Strict Concurrency: All protocols and UI-facing types are annotated
@MainActor. - Fast Unit Tests: Presenter unit tests execute in milliseconds using a
ViewSpywithout launching a UIKit hierarchy or simulator.
3. Unit Testing the Presenter
import XCTest
@MainActor
final class ProfilePresenterTests: XCTestCase {
final class ViewSpy: ProfileViewProtocol {
var displayedTitle: String?
func display(title: String) {
displayedTitle = title
}
}
func test_viewDidLoad_setsTitle() {
let view = ViewSpy()
let presenter = ProfilePresenter(view: view)
presenter.viewDidLoad()
XCTAssertEqual(view.displayedTitle, "Profile")
}
}
Rate your understanding: