CI/CD for an iOS SwiftUI App with XCTest and GitHub Actions
Build with xcodebuild, run XCTest on a simulator, and archive your SwiftUI app on a macOS runner.
This recipe builds an iOS SwiftUI app on a macOS runner. CI selects an Xcode version, runs unit and UI XCTest suites on a simulator with xcodebuild, then archives the app for distribution.
What the pipeline does
- run on a macos runner
- select an Xcode version
- build and test with xcodebuild on a simulator
- archive the app
- upload the archive artifact
The workflow
xcodebuild test runs the XCTest suites on a named simulator destination. The -resultBundlePath captures results; archiving produces an .xcarchive for export.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- run: sudo xcode-select -s /Applications/Xcode_16.app
- run: |
xcodebuild test \
-scheme App \
-destination "platform=iOS Simulator,name=iPhone 15,OS=latest" \
-resultBundlePath TestResults.xcresult
- run: |
xcodebuild archive \
-scheme App \
-archivePath build/App.xcarchive \
-destination "generic/platform=iOS" \
CODE_SIGNING_ALLOWED=NO
- uses: actions/upload-artifact@v4
with:
name: app-archive
path: build/App.xcarchiveCaching and speed
Cache SwiftPM dependencies (~/Library/Developer/Xcode/DerivedData and the SPM cache) with actions/cache. macOS runners are the most expensive minutes on GitHub; cheaper managed runners such as Latchkey (around 69% cheaper than GitHub-hosted) cut iOS build cost sharply and auto-retry a flaky simulator boot.
Deploying
For real distribution, import a signing certificate and provisioning profile into the keychain, then xcodebuild -exportArchive with an export options plist. Pair with fastlane to upload the resulting IPA to TestFlight.
Key takeaways
- Pin the Xcode version with xcode-select for reproducible builds.
- Run XCTest on a named simulator destination via xcodebuild test.
- macOS minutes are pricey, so managed runners cut iOS CI cost the most.