
Ios Xcode
- 240 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
ios-xcode: A skill for development. This provides functionality for development workflows.
Key points
- ios-xcode
Ios Xcode by the numbers
- 240 all-time installs (skills.sh)
- +7 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,635 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill ios-xcodeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 240 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I use ios-xcode for development tasks?
Use ios-xcode for development tasks
Who is it for?
Best when you're working on backend & apis and need structured help with ios-xcode.
Skip if: Teams with no backend & apis needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to use ios-xcode for development tasks, or when ios-xcode: a skill for development. this provides functionality for development workflows.
What you get
Structured output aligned to ios-xcode: ios-xcode.
Files
iOS Xcode & Tooling Best Practices
Comprehensive guide for Xcode project configuration, SwiftData persistence, testing, debugging, profiling, and app distribution. Contains 19 rules across 6 categories.
Clinic Architecture Contract (iOS 26 / Swift 6.2)
All guidance in this skill assumes the clinic modular MVVM-C architecture:
- Feature modules import
Domain+DesignSystemonly (neverData, never sibling features) - App target is the convergence point and owns
DependencyContainer, concrete coordinators, and Route Shell wiring Domainstays pure Swift and defines models plus repository,*Coordinating,ErrorRouting, andAppErrorcontractsDataowns SwiftData/network/sync/retry/background I/O and implements Domain protocols- Read/write flow defaults to stale-while-revalidate reads and optimistic queued writes
- ViewModels call repository protocols directly (no default use-case/interactor layer)
When to Apply
Reference these guidelines when:
- Setting up Xcode projects with AppStorage, ScenePhase, or widgets
- Implementing SwiftData models, queries, and CRUD operations
- Writing tests with Swift Testing framework
- Debugging with breakpoints and console output
- Profiling performance with Instruments
- Distributing apps via TestFlight
- Building for visionOS or integrating ML features
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | SwiftData & Persistence | CRITICAL | data- |
| 2 | Project & Platform | HIGH | platform- |
| 3 | Testing | HIGH | test- |
| 4 | Debugging & Profiling | MEDIUM-HIGH | debug-, perf- |
| 5 | Distribution | MEDIUM | dist- |
| 6 | Specialty Platforms | MEDIUM | ml-, spatial- |
Quick Reference
1. Project & Platform (HIGH)
- `platform-app-storage` - Use AppStorage for user preferences
- `platform-scene-phase` - Respond to app lifecycle with ScenePhase
- `platform-widget-integration` - Design for widget and Live Activity integration
- `platform-system-features` - Integrate system features natively
2. SwiftData & Persistence (CRITICAL)
- `data-model-macro` - Define models with @Model macro
- `data-query-for-fetching` - Use @Query for fetching data
- `data-model-container` - Configure model containers
- `data-relationships` - Define model relationships
- `data-crud-operations` - Implement CRUD operations
3. Testing (HIGH)
- `test-swift-testing` - Use Swift Testing framework
- `test-preview-sample-data` - Create preview sample data
- `test-preview-macro` - Use #Preview macro for rapid iteration
4. Debugging & Profiling (MEDIUM-HIGH)
- `debug-breakpoints` - Use breakpoints for debugging
- `debug-console-output` - Use console output for debugging
- `perf-instruments-profiling` - Profile SwiftUI with Instruments
5. Distribution (MEDIUM)
- `dist-testflight` - Distribute via TestFlight
- `dist-app-icons` - Design app icons for distribution
6. Specialty Platforms (MEDIUM)
- `ml-natural-language` - Integrate Natural Language ML
- `spatial-visionos-windows` - Build for visionOS spatial computing
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
Rule Title Here
Brief explanation of WHY this matters and the performance/quality implications. Keep to 1-3 sentences.
Incorrect (description of the problem/cost):
// Production-realistic bad code example
// Include comment explaining the consequence
struct ExampleView: View {
var body: some View {
Text("Example")
}
}Correct (description of the benefit/solution):
// Production-realistic good code example
// Minimal diff from incorrect version
struct ExampleView: View {
var body: some View {
Text("Example")
}
}Alternative (when to use this approach):
// Optional: alternative approach for different contextsWhen NOT to use this pattern:
- Exception case 1
- Exception case 2
Reference: Documentation Title
{
"version": "1.0.1",
"organization": "dot-skills",
"technology": "Xcode",
"date": "May 2026",
"abstract": "Xcode setup and tooling guidance for iOS 26 / Swift 6.2 modular MVVM-C projects covering project configuration, SwiftData container wiring, testing, debugging, profiling, and distribution."
}
Sections
This file defines all sections, their ordering, impact levels, and descriptions. The section ID (in parentheses) is the filename prefix used to group rules.
---
1. Project & Platform (project)
Impact: HIGH Description: Xcode project configuration, app storage, scene lifecycle, and widget integration define the foundation of your app's capabilities and platform integration.
2. SwiftData & Persistence (data)
Impact: CRITICAL Description: SwiftData model definitions, queries, containers, and CRUD operations are essential for apps with persistent data. Wrong patterns cause data corruption and performance issues.
3. Testing (test)
Impact: HIGH Description: Swift Testing framework, preview macros, and sample data enable rapid iteration and catch bugs before they ship. Previews are your fastest feedback loop.
4. Debugging & Profiling (debug)
Impact: MEDIUM-HIGH Description: Breakpoints, console output, and Instruments profiling are essential for diagnosing issues and optimizing performance in Xcode.
5. Distribution (dist)
Impact: MEDIUM Description: TestFlight distribution and app icon design are the final steps before users experience your app. Getting these right ensures a professional launch.
6. Specialty Platforms (platform)
Impact: MEDIUM Description: Natural Language ML and visionOS spatial computing extend your app to Apple's broader ecosystem. These patterns enable platform-specific features.
Perform CRUD with modelContext
Clinic architecture alignment (iOS 26 / Swift 6.2): Keep Feature modules on Domain + DesignSystem only; keep App-target DependencyContainer, route shells, and concrete coordinators as the integration point; keep Data as the only owner of SwiftData/network/sync I/O.
Use the model context from @Environment(\.modelContext) to insert and delete SwiftData models. Updates happen automatically when you modify model properties. SwiftData saves changes automatically.
Incorrect (manual save calls or wrong patterns):
// Don't create models without inserting
let friend = Friend(name: "Sophie")
// friend exists but isn't persisted!
// Don't try to manually save
modelContext.save() // Usually unnecessaryCorrect (proper CRUD operations):
import SwiftData
import SwiftUI
struct FriendListView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \Friend.name) private var friends: [Friend]
var body: some View {
List {
ForEach(friends) { friend in
Text(friend.name)
}
.onDelete(perform: deleteFriends)
}
.toolbar {
Button("Add", systemImage: "plus") {
addFriend()
}
}
}
// CREATE
private func addFriend() {
let friend = Friend(name: "New Friend")
modelContext.insert(friend)
// SwiftData auto-saves
}
// DELETE
private func deleteFriends(at offsets: IndexSet) {
for index in offsets {
modelContext.delete(friends[index])
}
}
}
// UPDATE - just modify properties
struct FriendDetailView: View {
@Bindable var friend: Friend // @Bindable for binding to model properties
var body: some View {
Form {
TextField("Name", text: $friend.name)
// Changes auto-save when you type
}
}
}CRUD operations:
- Create:
modelContext.insert(model) - Read:
@Queryfetches automatically - Update: Modify model properties directly
- Delete:
modelContext.delete(model)
Reference: Develop in Swift Tutorials - Create, update, and delete data
Configure modelContainer in App Entry Point
Clinic architecture alignment (iOS 26 / Swift 6.2): Keep Feature modules on Domain + DesignSystem only; keep App-target DependencyContainer, route shells, and concrete coordinators as the integration point; keep Data as the only owner of SwiftData/network/sync I/O.
Add the .modelContainer(for:) modifier to your app's main scene to enable SwiftData. This creates the database and makes the model context available throughout your view hierarchy.
Incorrect (no container setup):
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
// @Query will fail without modelContainer!
}
}
}Correct (modelContainer configured):
import SwiftUI
import SwiftData
@main
struct FriendsFavoritesApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [Friend.self, Movie.self])
}
}
// For previews, create a sample data container
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.modelContainer(SampleData.shared.modelContainer)
}
}
// Sample data helper for previews
@MainActor
class SampleData {
static let shared = SampleData()
let modelContainer: ModelContainer
init() {
let schema = Schema([Friend.self, Movie.self])
let config = ModelConfiguration(isStoredInMemoryOnly: true)
modelContainer = try! ModelContainer(for: schema, configurations: config)
// Insert sample data
let context = modelContainer.mainContext
context.insert(Friend(name: "Sophie"))
context.insert(Friend(name: "Alex"))
}
}Container options:
isStoredInMemoryOnly: true- For previews and testing- Multiple model types in single container
- CloudKit sync configuration
- Custom storage location
Reference: Develop in Swift Tutorials - Navigate sample data
Use @Model for SwiftData Persistence
The @Model macro marks a class for SwiftData persistence. SwiftData automatically saves changes, tracks relationships, and integrates with SwiftUI. Use classes (not structs) for SwiftData models.
Incorrect (struct or missing @Model):
// Structs can't be SwiftData models
struct Friend {
var name: String
var birthday: Date
}
// Class without @Model won't persist
class Friend {
var name: String
var birthday: Date
}Correct (@Model class):
import SwiftData
@Model
class Friend {
var name: String
var birthday: Date
init(name: String, birthday: Date = .now) {
self.name = name
self.birthday = birthday
}
}
@Model
class Movie {
var title: String
var releaseDate: Date
var isFavorite: Bool
init(title: String, releaseDate: Date = .now, isFavorite: Bool = false) {
self.title = title
self.releaseDate = releaseDate
self.isFavorite = isFavorite
}
}@Model requirements:
- Must be a class (reference type)
- Properties are automatically persisted
- Provide initializer with required properties
- Import SwiftData framework
SwiftData features:
- Automatic saving on changes
- Undo/redo support
- CloudKit sync (with configuration)
- Type-safe queries
Reference: Develop in Swift Tutorials - Navigate sample data
Use @Query to Fetch SwiftData Models
Clinic architecture alignment (iOS 26 / Swift 6.2): Keep Feature modules on Domain + DesignSystem only; keep App-target DependencyContainer, route shells, and concrete coordinators as the integration point; keep Data as the only owner of SwiftData/network/sync I/O.
@Query fetches SwiftData models and automatically updates your view when data changes. It's the primary way to display persisted data in SwiftUI. Add sorting and filtering with predicates.
Incorrect (manual fetching):
// Don't manually fetch in onAppear
struct FriendListView: View {
@State private var friends: [Friend] = []
var body: some View {
List(friends) { friend in
Text(friend.name)
}
.onAppear {
// Manual fetching won't update when data changes
friends = fetchFriends()
}
}
}Correct (@Query for reactive fetching):
import SwiftData
import SwiftUI
struct FriendListView: View {
@Query private var friends: [Friend] // Automatically fetches all Friends
var body: some View {
List(friends) { friend in
Text(friend.name)
}
}
}
// With sorting
struct FriendListView: View {
@Query(sort: \Friend.name) private var friends: [Friend]
var body: some View {
List(friends) { friend in
Text(friend.name)
}
}
}
// With filtering and sorting
struct FavoritesView: View {
@Query(
filter: #Predicate<Movie> { $0.isFavorite },
sort: \Movie.title
) private var favorites: [Movie]
var body: some View {
List(favorites) { movie in
Text(movie.title)
}
}
}@Query features:
- Automatically re-fetches when data changes
- Sort with key paths:
sort: \Model.property - Filter with
#Predicatemacro - Combine multiple sort descriptors
- Results are always up-to-date
Reference: Develop in Swift Tutorials - Navigate sample data
Define Model Relationships with Properties
Clinic architecture alignment (iOS 26 / Swift 6.2): Keep Feature modules on Domain + DesignSystem only; keep App-target DependencyContainer, route shells, and concrete coordinators as the integration point; keep Data as the only owner of SwiftData/network/sync I/O.
SwiftData creates relationships automatically when model properties reference other model types. Use array properties for one-to-many relationships and single properties for one-to-one.
Incorrect (manual ID references):
// Don't use IDs to reference other models
@Model
class Friend {
var name: String
var favoriteMovieIds: [UUID] = [] // Manual tracking is error-prone
}
@Model
class Movie {
var id: UUID = UUID()
var title: String
}Correct (direct model references):
import SwiftData
@Model
class Friend {
var name: String
// One-to-many: Friend has many favorite movies
var favoriteMovies: [Movie] = []
init(name: String) {
self.name = name
}
}
@Model
class Movie {
var title: String
// Inverse relationship (optional but recommended)
var fans: [Friend] = []
init(title: String) {
self.title = title
}
}
// Usage
let friend = Friend(name: "Sophie")
let movie = Movie(title: "Dune")
// Add to relationship
friend.favoriteMovies.append(movie)
// SwiftData automatically updates movie.fans
// Query with relationships
@Query private var friends: [Friend]
ForEach(friend.favoriteMovies) { movie in
Text(movie.title)
}Relationship patterns:
- Array property -> one-to-many relationship
- Single optional property -> one-to-one relationship
- Define inverse for bidirectional relationships
- SwiftData handles cascading deletes
Reference: Develop in Swift Tutorials - Work with relationships
Use Breakpoints to Debug Code
Scattering print() statements through your code to trace values is slow, clutters output, and risks being shipped to production. Breakpoints pause execution at a specific line so you can inspect every variable in scope, step through logic, and evaluate expressions in the debugger console without modifying source code.
Incorrect (print debugging only):
// Print statements are verbose and slow
func processData() {
print("Starting processData")
print("items count: \(items.count)")
for item in items {
print("Processing: \(item)")
// ...
print("Done processing: \(item)")
}
print("Finished processData")
}Another incorrect example (scattered print statements to trace a bug):
struct TipCalculator {
func calculateTip(billAmount: Double, tipPercentage: Double, splitCount: Int) -> Double {
print("billAmount: \(billAmount)")
print("tipPercentage: \(tipPercentage)")
let tipAmount = billAmount * tipPercentage
print("tipAmount: \(tipAmount)")
let totalWithTip = billAmount + tipAmount
print("totalWithTip: \(totalWithTip)")
let perPerson = totalWithTip / Double(splitCount)
print("perPerson: \(perPerson)")
return perPerson
}
}Correct (breakpoint debugging):
func processData() {
// Set breakpoint on this line (click line number gutter)
for item in items {
let result = transform(item) // <- Breakpoint here
// Inspect 'item' and 'result' in Variables view
// Step over (F6) to see next iteration
}
}
// Clean code debugged with breakpoints
struct TipCalculator {
func calculateTip(billAmount: Double, tipPercentage: Double, splitCount: Int) -> Double {
let tipAmount = billAmount * tipPercentage
let totalWithTip = billAmount + tipAmount
let perPerson = totalWithTip / Double(splitCount) // set breakpoint here to inspect all values
return perPerson
}
}
// In Xcode: click the line gutter to add a breakpoint, then use
// the debug console to evaluate expressions like `po tipAmount`
// Use print sparingly for production logging
func fetchData() async {
#if DEBUG
print("Fetching data from \(url)")
#endif
// ...
}Debugging workflow: 1. Set breakpoint: Click line number gutter 2. Run app: Execution pauses at breakpoint 3. Inspect variables: View values in Debug area 4. Step controls:
- Step Over (F6): Execute current line
- Step Into (F7): Enter function
- Step Out (F8): Exit function
- Continue (Control+Command+Y): Resume execution
Conditional breakpoints:
- Right-click breakpoint > Edit Breakpoint
- Add condition:
items.count > 10 - Add action: Log message without stopping
Reference: Develop in Swift Tutorials - Investigate and fix a bug
Use Debug Console for Runtime Inspection
When paused at a breakpoint, use the debug console to evaluate expressions, inspect objects, and test fixes. Type po variableName to print object descriptions.
Incorrect (print statements instead of debugger):
// Don't litter code with print statements
func processData(_ data: [Item]) {
print("data count: \(data.count)") // Clutters console
print("first item: \(data.first)") // Hard to find
for item in data {
print("processing: \(item)") // Too much noise
}
}Debug console commands:
// Print object description
(lldb) po friend
▿ Friend
- name: "Sophie"
- birthday: 2024-01-15
// Print primitive value
(lldb) p count
(Int) $R0 = 42
// Evaluate expressions
(lldb) po friends.count
3
(lldb) po friends.filter { $0.name.contains("S") }
▿ 1 element
- 0 : Friend(name: "Sophie")
// Call methods
(lldb) po friend.name.uppercased()
"SOPHIE"
// Modify values (use with caution)
(lldb) expression friend.name = "Alex"Console in SwiftUI debugging:
struct ContentView: View {
@State private var items: [Item] = []
var body: some View {
List(items) { item in
Text(item.name)
}
.onAppear {
// Set breakpoint here
loadItems()
// In console: po items
}
}
}Useful lldb commands:
po expression- Print object (calls debugDescription)p expression- Print value with typeexpression- Evaluate/modify valuesbt- Show call stack (backtrace)frame variable- Show all local variables
Reference: Develop in Swift Tutorials - Investigate and fix a bug
Design App Icons for Distribution
Design a simple, recognizable app icon without text or unnecessary detail. Icons should work at all sizes from 1024px down to 29px.
Incorrect (problematic app icons):
- Icon with text - unreadable at small sizes
- Photos or screenshots - too complex
- iOS interface elements - confusing context
- Apple product images - trademark violation
- Transparent backgrounds - looks brokenCorrect (effective app icons):
- Simple, distinct silhouette
- Limited color palette (2-3 colors)
- Recognizable at 29px
- Unique within your category
- Consistent with app brandingIcon specifications:
App Store: 1024x1024px
iPhone: 180x180px (@3x)
iPad: 167x167px (@2x)
Notification: 60x60px
Settings: 87x87px
Spotlight: 120x120pxDesign principles:
- Single focal point
- No text (won't be readable)
- Avoid photos (too detailed)
- Fill the entire space (no margins)
- Works on light and dark wallpapers
- Consider the rounded rect mask
Providing icons in Xcode:
// Asset catalog handles all sizes
// Just provide 1024x1024 in AppIcon asset
// Xcode generates other sizes automaticallyReference: App icons - Human Interface Guidelines
Test with TestFlight Before Release
TestFlight lets you distribute beta builds to testers before App Store release. Upload builds from Xcode, invite testers, and collect crash reports and feedback.
Incorrect (skipping beta testing):
// Don't ship directly to App Store without testing
// Problems you'll miss:
// - Device-specific crashes
// - Network edge cases
// - User flow confusion
// - Accessibility issues
// - Battery/performance problems on older devicesTestFlight workflow:
1. Archive your app:
- Product > Archive in Xcode
- Validate the archive
2. Upload to App Store Connect:
- Distribute App > App Store Connect
- Upload completes in Organizer
3. Configure TestFlight:
- Set What to Test notes
- Add internal testers (up to 100)
- Add external testers (up to 10,000)
4. Testers receive:
- Email invitation
- Install via TestFlight app
- Submit feedback and crash reports
Code considerations:
// Detect TestFlight vs App Store
#if DEBUG
let isTestFlight = false
#else
let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
#endif
// Show beta features only in TestFlight
if isTestFlight {
Text("Beta Feature")
}
// Provide feedback mechanism
Button("Send Feedback") {
// Open feedback form or email
}TestFlight benefits:
- Test on real devices and networks
- Automatic crash reporting
- Built-in feedback screenshots
- 90-day build expiration
- No App Review for internal testers
Use Natural Language Framework for Text Analysis
The Natural Language framework provides on-device text analysis including sentiment analysis, language detection, and tokenization. Results are private - text never leaves the device.
Incorrect (manual string analysis):
// Don't manually analyze text
func isPositive(_ text: String) -> Bool {
let positiveWords = ["good", "great", "love", "happy"]
for word in positiveWords {
if text.lowercased().contains(word) {
return true // Misses context, sarcasm, negation
}
}
return false
}Basic sentiment analysis:
import NaturalLanguage
struct SentimentAnalyzer {
func analyzeSentiment(of text: String) -> Double {
let tagger = NLTagger(tagSchemes: [.sentimentScore])
tagger.string = text
let (sentiment, _) = tagger.tag(
at: text.startIndex,
unit: .paragraph,
scheme: .sentimentScore
)
// Returns value from -1.0 (negative) to 1.0 (positive)
return Double(sentiment?.rawValue ?? "0") ?? 0
}
}
// Usage
let analyzer = SentimentAnalyzer()
let score = analyzer.analyzeSentiment(of: "I love this app!")
// score ~ 0.8 (positive)
let negativeScore = analyzer.analyzeSentiment(of: "This is terrible")
// negativeScore ~ -0.6 (negative)SwiftUI integration:
struct SentimentView: View {
@State private var text = ""
@State private var sentiment: Double = 0
var body: some View {
VStack {
TextField("Enter text", text: $text)
.onChange(of: text) { _, newValue in
sentiment = SentimentAnalyzer().analyzeSentiment(of: newValue)
}
Text(sentimentEmoji)
.font(.largeTitle)
}
}
var sentimentEmoji: String {
switch sentiment {
case 0.3...: return "positive"
case -0.3..<0.3: return "neutral"
default: return "negative"
}
}
}Natural Language capabilities:
- Sentiment analysis
- Language identification
- Tokenization (words, sentences)
- Part-of-speech tagging
- Named entity recognition
Reference: Develop in Swift Tutorials - Analyze sentiment in text
Profile SwiftUI with Instruments
Don't guess at performance issues. Use Instruments to identify actual bottlenecks in view body execution, re-renders, and layout.
Incorrect (guessing at performance issues):
struct ProductList: View {
let products: [Product]
var body: some View {
// "This feels slow, let me add memoization everywhere"
List(products) { product in
ProductRow(product: product)
}
// Random optimizations without measuring
// May not address the actual problem
}
}Correct (profile then optimize):
struct ProductList: View {
let products: [Product]
var body: some View {
let _ = Self._printChanges() // Debug: see why body runs
List(products) { product in
ProductRow(product: product)
}
// 1. Profile with Instruments (Cmd+I)
// 2. Find actual bottleneck
// 3. Fix specific issue
// 4. Verify improvement
}
}Instruments setup:
1. Profile in Release mode (Cmd+I or Product > Profile) 2. Select "SwiftUI" template (Instruments 26+) 3. Or use "Time Profiler" + "SwiftUI View Body" instruments
Common issues and solutions:
| Symptom | Likely Cause | Solution |
|---|---|---|
| Body runs on every frame | Animation on parent | Extract animated view |
| 100+ body calls per second | State at wrong level | Move state down |
| Slow single body | Expensive computation | Cache or move to model |
| Memory growth | Unbounded list | Use LazyVStack |
Best practice: Profile before and after optimization to verify improvements.
Reference: WWDC25: Optimize SwiftUI Performance
Use AppStorage for User Preferences
@AppStorage wraps UserDefaults with SwiftUI reactivity. Changes persist automatically and trigger view updates.
Incorrect (manual UserDefaults):
struct SettingsView: View {
@State private var darkModeEnabled: Bool
init() {
_darkModeEnabled = State(initialValue: UserDefaults.standard.bool(forKey: "darkMode"))
}
var body: some View {
Toggle("Dark Mode", isOn: $darkModeEnabled)
.onChange(of: darkModeEnabled) { _, newValue in
UserDefaults.standard.set(newValue, forKey: "darkMode") // Manual sync
}
}
}Correct (AppStorage):
struct SettingsView: View {
@AppStorage("darkModeEnabled") private var darkModeEnabled = false
var body: some View {
Toggle("Dark Mode", isOn: $darkModeEnabled)
// Automatically persisted to UserDefaults
}
}Supported types:
@AppStorage("username") var username = "" // String
@AppStorage("itemCount") var itemCount = 0 // Int
@AppStorage("price") var price = 0.0 // Double
@AppStorage("isEnabled") var isEnabled = false // Bool
@AppStorage("selectedTab") var selectedTab: Tab = .home // RawRepresentable
@AppStorage("lastOpened") var lastOpened: Date? // Optional with nil defaultCustom app group for sharing:
// Share between app and widget
@AppStorage("streak", store: UserDefaults(suiteName: "group.com.app.shared"))
var streak = 0Enum storage with RawRepresentable:
enum Theme: String, CaseIterable {
case system, light, dark
}
struct AppearanceSettings: View {
@AppStorage("theme") private var theme: Theme = .system
var body: some View {
Picker("Theme", selection: $theme) {
ForEach(Theme.allCases, id: \.self) { theme in
Text(theme.rawValue.capitalized)
}
}
}
}When NOT to use AppStorage:
- Large data (use FileManager or Core Data)
- Sensitive data (use Keychain)
- Complex objects (use Codable + file storage)
Reference: AppStorage Documentation
Respond to App Lifecycle with ScenePhase
ScenePhase tells you when your app moves between active, inactive, and background states. Use it to save state and manage resources.
Incorrect (not responding to lifecycle):
struct GameView: View {
@State private var gameState: GameState
var body: some View {
GameBoard(state: gameState)
// Game continues running when app is backgrounded
// State lost if terminated
}
}Correct (handling lifecycle):
struct GameView: View {
@State private var gameState: GameState
@Environment(\.scenePhase) private var scenePhase
var body: some View {
GameBoard(state: gameState)
.onChange(of: scenePhase) { _, newPhase in
switch newPhase {
case .active:
resumeGame()
case .inactive:
pauseGame()
case .background:
saveGameState()
@unknown default:
break
}
}
}
private func saveGameState() {
// Persist to disk before termination
try? JSONEncoder().encode(gameState).write(to: saveURL)
}
}Scene phases:
.active // App is in foreground and interactive
.inactive // App is visible but not interactive (e.g., during app switcher)
.background // App is not visibleCommon use cases:
.onChange(of: scenePhase) { _, phase in
switch phase {
case .active:
// Refresh data that might be stale
refreshContent()
// Resume timers
timer.resume()
case .inactive:
// Pause video/audio
player.pause()
// Stop animations
isAnimating = false
case .background:
// Save unsaved changes
saveDocument()
// Cancel non-essential network requests
networkManager.cancelPendingRequests()
// Clear sensitive data from memory
clearCachedCredentials()
@unknown default:
break
}
}App-level vs View-level:
@main
struct MyApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { _, phase in
// App-wide lifecycle handling
}
}
}Reference: ScenePhase Documentation
Integrate System Features Natively
Use system-provided UI for sharing, photo picking, and contacts. Users trust familiar interfaces, and you get automatic updates.
Incorrect (custom share implementation):
struct ArticleView: View {
let article: Article
@State private var showingCustomShare = false
var body: some View {
Button("Share") { showingCustomShare = true }
.sheet(isPresented: $showingCustomShare) {
// Custom share UI
VStack {
Button("Copy Link") { /* ... */ }
Button("Twitter") { /* ... */ }
Button("Facebook") { /* ... */ }
// Missing: AirDrop, Messages, Mail, Notes...
// Must maintain every share destination
}
}
}
}Correct (system ShareLink):
struct ArticleView: View {
let article: Article
var body: some View {
ShareLink(item: article.url, subject: Text(article.title)) {
Label("Share", systemImage: "square.and.arrow.up")
}
// All share destinations included
// Updated automatically by iOS
}
}Photo picker (iOS 16+):
struct ProfileEditor: View {
@State private var selectedPhoto: PhotosPickerItem?
@State private var avatarImage: Image?
var body: some View {
PhotosPicker(selection: $selectedPhoto, matching: .images) {
if let avatarImage {
avatarImage.resizable().frame(width: 100, height: 100)
} else {
Image(systemName: "person.circle.fill")
.font(.system(size: 100))
}
}
.onChange(of: selectedPhoto) { _, newValue in
Task {
if let data = try? await newValue?.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
avatarImage = Image(uiImage: uiImage)
}
}
}
}
}Benefits of system UI:
- Familiar to users, builds trust
- Automatically updated with iOS
- Handles permissions and privacy
- Consistent accessibility support
Reference: Human Interface Guidelines - System Features
Design for Widget and Live Activity Integration
Widgets and Live Activities extend your app beyond the main interface. Design shared components and data models that work across both.
Incorrect (duplicate code for widget):
// Main app
struct WorkoutProgressView: View {
let workout: Workout
var body: some View {
VStack {
Text("\(workout.elapsedTime.formatted())")
Text("\(workout.caloriesBurned) cal")
}
}
}
// Widget - completely separate implementation
struct WorkoutWidgetView: View {
let entry: WorkoutEntry
var body: some View {
// Duplicated layout logic
// Different data model
// Inconsistent appearance
VStack {
Text("\(entry.time)")
Text("\(entry.calories)")
}
}
}Correct (shared components via App Group):
// Shared framework
struct WorkoutProgress: Codable {
let elapsedTime: TimeInterval
let caloriesBurned: Int
}
struct WorkoutProgressView: View {
let progress: WorkoutProgress
@Environment(\.widgetFamily) var family // nil in main app
var body: some View {
Group {
switch family {
case .systemSmall:
CompactView(progress: progress)
default:
FullView(progress: progress)
}
}
}
}
// Works in both app and widget
// Single source of truth for layoutShare data via App Group:
extension UserDefaults {
static let shared = UserDefaults(suiteName: "group.com.app.fitness")!
}
// Main app writes
UserDefaults.shared.set(encoded, forKey: "workout")
// Widget reads
let workout = UserDefaults.shared.data(forKey: "workout")Updating widgets:
// Refresh widget timeline
WidgetCenter.shared.reloadAllTimelines()Design considerations:
- Widgets are read-only snapshots
- Use
.containerBackground()for backgrounds - Keep text concise - widgets are glanceable
- Deep link to relevant app sections
Reference: Human Interface Guidelines - Widgets
Build visionOS Apps with Windows
visionOS windows use familiar SwiftUI patterns but exist in 3D space. Add depth with .offset(z:) and use glass material backgrounds. Windows are the foundation before volumes and immersive spaces.
Incorrect (ignoring spatial design):
// Don't use flat, opaque backgrounds in visionOS
struct BadVisionView: View {
var body: some View {
VStack {
Text("Hello")
}
.background(Color.white) // Opaque backgrounds look wrong
.frame(width: 200, height: 100) // Fixed sizes don't adapt
}
}Basic visionOS window:
import SwiftUI
@main
struct MyVisionApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, visionOS!")
.font(.extraLargeTitle)
Image(systemName: "vision.pro")
.font(.system(size: 100))
}
.padding(50)
}
}Adding depth to elements:
struct DepthView: View {
@State private var isPressed = false
var body: some View {
VStack(spacing: 20) {
// Elements at different depths
Text("Background")
.padding()
.background(.regularMaterial)
Text("Foreground")
.padding()
.background(.regularMaterial)
.offset(z: 50) // Comes toward viewer
// Interactive depth
Button("Press Me") {
isPressed.toggle()
}
.offset(z: isPressed ? 100 : 0)
.animation(.spring, value: isPressed)
}
}
}visionOS considerations:
- Use glass materials (
.regularMaterial,.thickMaterial) - Add depth with
.offset(z:)in points - Larger touch targets (44pt minimum, 60pt+ recommended)
- Test in Simulator and on device
Reference: Develop in Swift Tutorials - Add depth to your app
Use #Preview Macro for Live Development
Without previews, every visual change requires a full build-and-run cycle on a simulator. The #Preview macro renders the view directly in Xcode's canvas, giving sub-second feedback on layout and styling changes. Supplying realistic sample data in previews catches edge cases like long text and missing images before they reach QA.
Incorrect (no preview, must build and run to see changes):
struct ContentView: View {
var body: some View {
Text("Hello")
}
}
// No preview - must run app to see changesCorrect (preview for rapid iteration):
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
// Multiple previews for different states
#Preview("Light Mode") {
ContentView()
}
#Preview("Dark Mode") {
ContentView()
.preferredColorScheme(.dark)
}
// Preview with sample data
#Preview("With User") {
ProfileView(user: User(name: "Sophie", email: "sophie@example.com"))
}Previews with varied sample data for edge-case coverage:
struct OrderSummaryCard: View {
let itemName: String
let quantity: Int
let priceInCents: Int
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(itemName)
.font(.headline)
Text("Qty: \(quantity)")
.font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer()
Text("$\(priceInCents / 100).\(String(format: "%02d", priceInCents % 100))")
.font(.title3).bold()
}
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 10))
.shadow(radius: 2)
}
}
#Preview("Standard Item") {
OrderSummaryCard(itemName: "Wireless Charger", quantity: 1, priceInCents: 2999)
.padding()
}
#Preview("Long Name & High Qty") { // catches truncation and layout overflow
OrderSummaryCard(itemName: "Ultra-Premium Noise-Cancelling Headphones Pro Max", quantity: 150, priceInCents: 34999)
.padding()
}Preview modes:
- Live Mode: Interactive - tap buttons, scroll lists
- Selectable Mode: Click elements to highlight corresponding code
- Variants: Test different dynamic type sizes, color schemes
Tips:
- Press Option + Command + P to refresh preview
- Pin previews to keep them visible while editing other files
- Use
.previewLayout(.sizeThatFits)for component-sized previews
Reference: Develop in Swift Tutorials - Hello, SwiftUI
Use Preview with Sample Data for Visual Testing
Clinic architecture alignment (iOS 26 / Swift 6.2): Keep Feature modules on Domain + DesignSystem only; keep App-target DependencyContainer, route shells, and concrete coordinators as the integration point; keep Data as the only owner of SwiftData/network/sync I/O.
Previews with empty or minimal data hide real-world layout problems like text truncation, long names overflowing, and empty states never being tested. Using realistic sample data in your previews catches these visual issues during development without launching the full app on a simulator.
Incorrect (preview with minimal data hides layout issues):
#Preview {
ContactList(contacts: [
Contact(name: "Alice", phone: "555-0100", email: "a@b.com")
])
}Correct (preview with realistic sample data reveals edge cases):
extension Contact {
static let sampleContacts: [Contact] = [
Contact(name: "Alice Johnson", phone: "555-0100", email: "alice.johnson@example.com"),
Contact(name: "Dr. Roberto Garcia-Martinez", phone: "+44 20 7946 0958", email: "roberto.garcia-martinez@longcompany.co.uk"),
Contact(name: "김민준", phone: "010-1234-5678", email: "minjun.kim@example.kr"),
Contact(name: "Sam", phone: "", email: ""), // tests missing data
]
}
#Preview("Contact list with varied data") {
NavigationStack {
ContactList(contacts: Contact.sampleContacts) // reveals truncation and empty states
}
}Reference: Develop in Swift Tutorials
Write Unit Tests with Swift Testing
Swift Testing (new framework) uses @Test attribute and #expect macro for assertions. Write tests to verify your model logic works correctly before building UI. The #expect macro provides clear, readable failure messages that pinpoint exactly what went wrong.
Incorrect (no tests or old XCTest):
// Code without tests - bugs discovered in production
class Scoreboard {
func calculateScore() -> Int { ... }
}
// Old XCTest style (still works but verbose)
class ScoreboardTests: XCTestCase {
func testCalculateScore() {
XCTAssertEqual(scoreboard.calculateScore(), 100)
}
}Correct (Swift Testing):
import Testing
// Test struct with @Test methods
struct ScoreboardTests {
@Test func initialScoreIsZero() {
let scoreboard = Scoreboard()
#expect(scoreboard.score == 0)
}
@Test func scoreIncreasesOnCorrectAnswer() {
var scoreboard = Scoreboard()
scoreboard.recordCorrectAnswer()
#expect(scoreboard.score == 10)
}
@Test func scoreDecreasesOnWrongAnswer() {
var scoreboard = Scoreboard()
scoreboard.score = 20
scoreboard.recordWrongAnswer()
#expect(scoreboard.score == 15)
}
@Test func scoreNeverGoesNegative() {
var scoreboard = Scoreboard()
scoreboard.recordWrongAnswer()
#expect(scoreboard.score >= 0)
}
}
// Parameterized tests
@Test(arguments: [1, 2, 3, 5, 8])
func fibonacciNumbers(input: Int) {
#expect(fibonacci(input) > 0)
}Testing real model logic with #expect:
import Testing
@Test func addingProductToCartIncreasesTotal() {
var cart = ShoppingCart()
let coffee = Product(id: "coffee-01", name: "Coffee Beans", price: 12.99)
cart.add(coffee, quantity: 2)
#expect(cart.items.count == 1)
#expect(cart.totalPrice == 25.98) // 12.99 * 2
}
@Test func addingSameProductMergesQuantity() {
var cart = ShoppingCart()
let coffee = Product(id: "coffee-01", name: "Coffee Beans", price: 12.99)
cart.add(coffee, quantity: 1)
cart.add(coffee, quantity: 3)
#expect(cart.items.count == 1) // merged, not duplicated
#expect(cart.items[0].quantity == 4)
}Swift Testing features:
@Testattribute marks test functions#expect(condition)for assertions- Parameterized tests with
arguments: - Better error messages than XCTest
- Works alongside XCTest
Reference: Develop in Swift Tutorials - Add functionality with Swift Testing
Related skills
FAQ
What does ios-xcode do?
ios-xcode: A skill for development. This provides functionality for development workflows.
When should I use ios-xcode?
When you need to use ios-xcode for development tasks, or when ios-xcode: a skill for development. this provides functionality for development workflows.
What are the main capabilities?
ios-xcode.