All Articles
CategoryArchitecture
Reading Time
10 min read
Published
2026-10-15
Word Count
1,787words

Grab a coffee — this one is a deep dive!

iOSAppTemplates 3.1: 21 tested SwiftUI templates and a renamer that refuses bad names

Summary

How iOSAppTemplates keeps 21 SwiftUI app templates buildable and tested, and why create-ios-app checks for name clashes before it writes a file.

  • iOSAppTemplates is a collection of 21 SwiftUI app templates for iOS 18, from e-commerce and finance to messaging and travel, plus a command-line tool, create-ios-app, that copies a template under your own project name.
  • Before 3.0.0, the root package also shipped fourteen older library products that duplicated the apps under Templates/, and eight template packages pulled in Firebase, Kingfisher, Alamofire or Charts.
  • In CI, the templates add 310 more tests: 202 for the twenty single-store templates and 108 for CleanArchitectureApp.
  • Source code: [github.com/muhittincamdali/iOSAppTemplates](https://github.com/muhittincamdali/iOSAppTemplates)
iOSAppTemplates 3.1: 21 tested SwiftUI templates and a renamer that refuses bad names

iOSAppTemplates is a collection of 21 SwiftUI app templates for iOS 18, from e-commerce and

finance to messaging and travel, plus a command-line tool, create-ios-app, that copies a

template under your own project name. Version 3.1.0 was released on 14 October 2026.

Table of Contents


The problem

A template is only useful if it builds on the first try. Many starter projects need an

account, an API key or a third-party SDK before they run, and many are copied by hand and

renamed with search and replace, which breaks in ways that show up only at compile time.

This repository had the same problems itself. It was opened on 2 August 2025 with three

template packages: EcommerceApp, FitnessApp and SocialMediaApp. In April 2026 seventeen

more were added under Templates/, bringing the count to twenty, and v2.1.0 was tagged on

26 April. In June 2026 the proof scripts were removed, the old create-ios-app gained App Store

metadata generation and an interactive mode, and CI's template generator check stayed red.

The 3.0.0 CHANGELOG records what the October rebuild found: with Swift 6.4 the root package

did not build, because three of its fourteen library products depended on The Composable

Architecture, which does not compile with that version. The template tests were

placeholders. In three templates the only test was XCTAssertTrue(true), and in the other seventeen it checked that a static list had three

entries (commit 087eb7d). The old create-ios-app generated projects that depended on

package versions that do not exist.

The rebuild in October 2026 narrowed the repository to what builds and is tested. PR #22

(+13,881 / −81,774 lines across 765 files) became 3.0.0. PR #23 added a 21st template and

became 3.1.0, both on 14 October.

Design decisions and alternatives

Templates are independent packages; the root is only the tool

Before 3.0.0, the root package also shipped fourteen older library products that duplicated

the apps under Templates/, and eight template packages pulled in Firebase, Kingfisher,

Alamofire or Charts. Now each template has its own Package.swift with no dependencies: one

library target and one test target in the twenty single-store templates, four of each in

CleanArchitectureApp. The root package contains only create-ios-app and the

TemplateScaffold library behind it, and targets macOS 14, where the tool runs.

The trade-off is that the templates are not published as remote packages. The README is

explicit: "each one is meant to be copied and changed."

Behaviour lives in a store, and the store is tested

Twenty of the templates keep their state and actions in a @MainActor ObservableObject

store, and the screens stay thin. Each store has a Swift Testing suite that covers derived

totals, state transitions, guard clauses for invalid input and unknown identifiers, and

limits that must not be exceeded. One example of what "covers" means: the E-commerce shipping

test used to check only a subtotal above 150, so changing >= to > would have passed. The

threshold is now behind CommerceStore.shippingFee(forSubtotal:), and the test checks 149.99,

exactly 150 and 150.01 (commit bdf93a0).

No third-party dependencies

The templates use SwiftUI and Foundation only. Where a real app would talk to an external

service, the template puts a protocol in front of it. Sign-in in SocialMediaApp goes through

an AuthService protocol, and the bundled LocalAuthService keeps accounts in memory with a

demo account. Before 3.0.0 that template called FirebaseApp.configure() and could not start

without a Firebase configuration file.

`@main` lives in a host, not in the template

A library target that declares @main cannot be linked into an app. Every template now

exposes a public <Name>Shell scene, and a small host file calls <Name>Shell.main(). The

host project is an XcodeGen spec (App/project.yml), not a committed .xcodeproj, because

the spec is readable, diffs well and can be renamed as text. The cost is that opening a host

project needs XcodeGen. Examples/QuickStart carries a committed project, so it builds

without XcodeGen.

Renaming is a prefix convention, not placeholders or a syntax tree

Every name in a template that has to change starts with the template's directory name:

EcommerceApp, EcommerceAppShell, EcommerceAppTests, com.example.EcommerceApp. That

turns renaming into one text substitution over file contents and path components.

IdentifierRenamer replaces a match only when the character before it is not a letter, digit

or underscore, so EcommerceAppTests becomes MyShopTests while MyEcommerceApp stays as it

is.

Two alternatives were possible. Placeholders such as {{PROJECT_NAME}} would mean the

template no longer compiles on its own, so it could not be tested. A rename based on a Swift

syntax tree would be more precise for Swift code, but it does not cover project.yml, strings

in Package.swift or the bundle identifier. The trade-off of a text substitution is that names

that do not start with the prefix, such as CommerceStore, keep their names, and that a

substitution can produce a name that already exists. That last point became a real bug.

Layers as targets in the Clean Architecture template

CleanArchitectureApp, added in 3.1.0, splits a team directory app into

CleanArchitectureAppDomain, CleanArchitectureAppData, CleanArchitectureAppPresentation

and a composition root. Data and Presentation depend only on Domain, so "the UI does not touch

networking code" is a compile error rather than a code review rule. Business rules sit in use

cases. For example, the last active admin cannot be demoted or deactivated. The repository tries

the server first and falls back to a saved copy only when the server cannot be reached or

answers with a 5xx status. Other errors such as 401 are passed on.

This template replaces the separate ios-clean-architecture-template repository, which was

archived. Only the idea moved: the code was written again under this repository's rules, with

108 tests across four test targets and three UI tests, including an accessibility audit.

A real bug: "Created", but the project did not compile

After create-ios-app was rewritten, its tests passed and the README example with MyShop

worked. Running the tool with other names showed a problem. This command printed

"Created … (13 files)":

swift
1new CommerceStore --template ecommerce

The project it created did not compile. Renaming EcommerceApp.swift produced

CommerceStore.swift, which overwrote the file that held the real CommerceStore class, and

the module name collided with that class too. SocialMediaApp had the same problem with the

names Models, Components, FeedViewModel and DataManager.

The fix, in commit b7356bb (part of PR #22), changed the order of work. Scaffolder now

reads the whole template into memory and, before it writes anything, compares the names the

rename would produce with the names the template already has: file and folder names, and

names declared in its Swift files (class, struct, enum, protocol, actor,

typealias, func, var, let, case). Words in strings and comments do not count,

otherwise harmless names such as Acme would be refused. Target paths are also compared

ignoring case, because the default macOS file system treats Demo.swift and demo.swift as

the same file. On a clash the tool throws ScaffoldError.nameConflict and creates nothing.

At the 3.1.0 tag, the same command prints:

swift
1Error: CommerceStore cannot be used with this template: renaming would produce names it already has (CommerceStore, CommerceStoreTests, Sources/CommerceStore/CommerceStore.swift). Choose another name.

The tests cover a declared-type clash, a path that differs only in case, and the

CommerceStore, Models and FeedViewModel cases in the real templates. A related fix,

509b060, made the tool reject every Swift keyword that cannot follow import, such as

guard or public, because those names produced packages that did not compile.

The lesson: tests that pass for the documented example say little about inputs nobody tried.

Usage

Clone the repository, add a template folder such as Templates/EcommerceApp to an iOS 18 app

in Xcode as a local package, and replace the file that contains @main with this code from the

README:

swift
1import EcommerceApp
2 
3@main
4enum QuickStartApp {
5 static func main() {
6 EcommerceAppShell.main()
7 }
8}

A test compares this block with Examples/QuickStart/QuickStart/QuickStartApp.swift, so the

README and the example cannot drift apart.

To create a renamed copy, run the tool from the clone:

bash
1cd iOSAppTemplates
2swift run create-ios-app list
3swift run create-ios-app new MyShop --template ecommerce --output ~/Developer

The same logic is available as the TemplateScaffold library. This README example is

compiled and run by the test suite:

swift
1import Foundation
2import TemplateScaffold
3 
4func makeShop(templates: URL, output: URL) throws -> ScaffoldResult {
5 guard let template = TemplateCatalog.template(named: "ecommerce") else {
6 throw CocoaError(.fileNoSuchFile)
7 }
8 let name = try ProjectName("MyShop")
9 return try Scaffolder(templatesDirectory: templates).scaffold(template, as: name, in: output)
10}

At the 3.1.0 tag, swift test runs 40 tests in 6 suites for the library and the tool. In CI,

the templates add 310 more tests: 202 for the twenty single-store templates and 108 for

CleanArchitectureApp. Each template runs as its own job in the iOS Simulator.

Limits

  • Templates are starting points. Twenty of them run on in-memory sample data. They give

you a working tab structure, navigation and a tested store, not a backend.

  • Not remote packages. Templates are added as local packages or copied, and

create-ios-app needs a checkout of the repository.

  • UI tests in one template only. CleanArchitectureApp has UI tests. In the other twenty,

the tests cover the store, and the screens are checked through the screenshots in

docs/images. There are no snapshot tests.

  • Open items in the README roadmap: some states are still compared as strings (the order

headline in EcommerceApp, the containment status in MessagingApp, the preset mode in

AIAssistantApp), and most templates still keep their code in a single <Name>.swift file.

The package is not listed on the Swift Package Index yet.

  • Renaming scope. Types that do not start with the template name keep their names, and

declarations are found with a regular expression, not a parser.

  • Sample sign-in only. LocalAuthService keeps accounts in memory for the demo; it is not

an authentication system.

  • Version strings. At the 3.1.0 tag, create-ios-app --version still reports 3.0.0, and

the README version badge showed v3.0.0. The badge was corrected after the release in PR #25,

which also added a CI check that fails when the badge lags the CHANGELOG. The CLI string is

still set by hand.

Source code: github.com/muhittincamdali/iOSAppTemplates

Tags

#swiftui#ios#app-templates#scaffolding#swift-testing
Muhittin Çamdalı

Muhittin Çamdalı

Senior Mobile Engineer

Senior Mobile Engineer with 12+ years of experience. I build mobile products with iOS, Flutter and React Native, working full-stack and AI-native.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

Your subscription starts when you open the link in the confirmation email and press “Confirm my subscription”. The newsletter keeps open/click statistics; you can unsubscribe anytime with one click. Privacy

Share