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
- Design decisions and alternatives
- Templates are independent packages; the root is only the tool
- Behaviour lives in a store, and the store is tested
- No third-party dependencies
- `@main` lives in a host, not in the template
- Renaming is a prefix convention, not placeholders or a syntax tree
- Layers as targets in the Clean Architecture template
- A real bug: "Created", but the project did not compile
- Usage
- Limits
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)":
1new CommerceStore --template ecommerceThe 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:
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:
1import EcommerceApp2 3@main4enum 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:
1cd iOSAppTemplates2swift run create-ios-app list3swift run create-ios-app new MyShop --template ecommerce --output ~/DeveloperThe same logic is available as the TemplateScaffold library. This README example is
compiled and run by the test suite:
1import Foundation2import TemplateScaffold3 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.
CleanArchitectureApphas 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.
LocalAuthServicekeeps accounts in memory for the demo; it is not
an authentication system.
- Version strings. At the 3.1.0 tag,
create-ios-app --versionstill reports3.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
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

