ios-development

Comprehensive iOS app development skill with Liquid Glass design, modern Swift patterns (@Observable, SwiftData), and App Store readiness. Self-updates by checking Apple Developer News for new iOS versions. Use for any iOS/SwiftUI project.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ios-development" with this command: npx skills add simonstrumse/vibelabs-skills/simonstrumse-vibelabs-skills-ios-development

iOS Development Skill

Complete iOS development toolkit: design system, architecture patterns, and App Store verification. Self-updating - checks Apple Developer News for iOS changes.

Team ID: YOUR_TEAM_ID Current Target: iOS 26 (Liquid Glass) Minimum: iOS 17.0 (for @Observable, SwiftData) Last Updated: December 2025


Self-Update Protocol

When starting a new iOS project or major feature, ALWAYS run this check:

BEFORE implementing iOS features, search the web for:
1. "Apple Developer News iOS [current year]"
2. "iOS [latest version] SwiftUI new APIs"
3. "WWDC [current year] SwiftUI changes"

Check for:
- New iOS version announcements
- Deprecated APIs
- New required APIs (privacy, permissions)
- Design system changes
- App Store requirement changes

If significant changes found, update this skill file before proceeding.

Why self-update? Apple deprecates APIs and mandates new patterns yearly. iOS 26 made Liquid Glass mandatory. iOS 27 will remove the option to retain old designs.


Quick Reference

Modern Swift Stack (iOS 17+)

LayerTechnologyNOT This
State@ObservableObservableObject
Bindings@State, @Bindable@StateObject, @ObservedObject
PersistenceSwiftData + VersionedSchemaCore Data, UserDefaults
UISwiftUI + Liquid GlassUIKit wrappers
ArchitectureMVVM with @QueryMVC

Glass Effect Quick Reference

// Basic glass
.glassEffect()

// Tinted + interactive
.glassEffect(.regular.tint(.blue).interactive())

// Group related elements
GlassEffectContainer { ... }

// Morphing sheet
.matchedTransitionSource(id: "x", in: namespace)
.navigationTransition(.zoom(sourceID: "x", in: namespace))

PART 1: LIQUID GLASS DESIGN SYSTEM

Design Philosophy

Liquid Glass is Apple's translucent material that reflects and refracts surroundings while dynamically transforming to bring focus to content.

Core Principles

  1. Dynamic Transparency - Interfaces feel like overlapping sheets of glass
  2. Glass is Overlay - Apply ONLY to elements on TOP of UI (toolbars, FABs, modals)
  3. Fluid Interaction - Morphing transitions connect related elements
  4. Depth and Context - Elements create sense of spatial hierarchy
  5. Colorful Backgrounds - Vibrant content behind glass makes effects shine

Layout Rules

  • Keep essential UI in central 70% of canvas
  • Let tab bars auto-collapse on scroll
  • Think in layers: content at bottom, glass controls on top
  • Apply glass sparingly (key surfaces only)
  • Use native Apple components (optimized for Liquid Glass)

What NOT to Do

  • Don't create custom components (poor Liquid Glass compatibility)
  • Don't align thin borders exactly with mask edges
  • Don't overuse glass effects
  • Don't apply glass to main content areas
  • Don't stack multiple glass layers

MANDATORY by iOS 27 - Apple will remove option to retain old designs.


Glass Effect APIs

Basic Glass Effect

// Simple glass background
Button("Action") { }
    .glassEffect()

// Tinted glass with color
Button("Save") { }
    .glassEffect(.regular.tint(.blue))

// Interactive glass (grows + shimmers on touch)
Button("Tap Me") { }
    .glassEffect(.regular.tint(.purple.opacity(0.8)).interactive())

// Button style alternative
Button("Glass Button") { }
    .buttonStyle(.glass)

Glass Effect Container

Group elements to blend together:

GlassEffectContainer {
    HStack(spacing: 12) {
        Button(action: {}) {
            Image(systemName: "camera")
        }
        .glassEffect()

        Button(action: {}) {
            Image(systemName: "photo")
        }
        .glassEffect()
    }
}

Glass Effect Identity

Link related elements across view hierarchy:

@Namespace private var glassNamespace

VStack {
    Button("Expand") { }
        .glassEffectID("action", in: glassNamespace)

    if expanded {
        DetailPanel()
            .glassEffectID("action", in: glassNamespace)
    }
}

Custom Glass Shapes

// Circular glass
Circle()
    .glassEffect()
    .frame(width: 60, height: 60)

// Custom shape
CustomPath()
    .glassEffect(.regular, in: CustomShape())

Liquid Glass Sheets

Partial Height (Floating)

.sheet(isPresented: $showSheet) {
    ContentView()
        .presentationDetents([.medium, .large])
        // Auto-gets Liquid Glass background
}

Morphing Transitions

struct ContentView: View {
    @Namespace private var transition
    @State private var showInfo = false

    var body: some View {
        NavigationStack {
            MainContent()
                .toolbar {
                    ToolbarItem(placement: .bottomBar) {
                        Button("Info", systemImage: "info") {
                            showInfo = true
                        }
                    }
                    .matchedTransitionSource(id: "info", in: transition)
                }
                .sheet(isPresented: $showInfo) {
                    InfoView()
                        .presentationDetents([.medium, .large])
                        .navigationTransition(.zoom(sourceID: "info", in: transition))
                }
        }
    }
}

Component Patterns

Floating Action Bar

struct FloatingActionBar: View {
    let actions: [ActionItem]

    var body: some View {
        GlassEffectContainer {
            HStack(spacing: 16) {
                ForEach(actions) { action in
                    Button(action: action.handler) {
                        Image(systemName: action.icon)
                            .font(.title2)
                    }
                    .glassEffect(.regular.interactive())
                }
            }
            .padding(.horizontal, 20)
            .padding(.vertical, 12)
        }
        .glassEffect()
    }
}

Glass Card

struct GlassCard<Content: View>: View {
    @ViewBuilder let content: Content

    var body: some View {
        content
            .padding(20)
            .background {
                RoundedRectangle(cornerRadius: 24)
                    .glassEffect(.regular.tint(.white.opacity(0.1)))
            }
    }
}

Animated Counter

struct GlassCounter: View {
    let value: Int

    var body: some View {
        Text("\(value)")
            .font(.system(size: 48, weight: .bold, design: .rounded))
            .contentTransition(.numericText())
            .padding(24)
            .glassEffect(.regular.tint(.blue.opacity(0.3)).interactive())
            .animation(.spring(duration: 0.3), value: value)
    }
}

PART 2: MODERN SWIFT ARCHITECTURE

State Management: @Observable

CRITICAL: Always use @Observable (iOS 17+). ObservableObject is legacy.

import Observation

@Observable
final class MyViewModel {
    var title: String = ""
    var items: [Item] = []
    var isLoading: Bool = false

    // NO @Published needed!
    // Views auto-update when properties they READ change

    func loadItems() async {
        isLoading = true
        items = await fetchItems()
        isLoading = false
    }
}

In Views:

struct MyView: View {
    @State private var viewModel = MyViewModel()  // NOT @StateObject

    var body: some View {
        List(viewModel.items) { item in
            Text(item.name)
        }
        .task {
            await viewModel.loadItems()
        }
    }
}

Why @Observable?

  • Performance: Views only re-render when properties they actually read change
  • Simplicity: No @Published, @ObservedObject, @StateObject boilerplate
  • Apple recommended: "For new development, using Observable is the easiest way"

SwiftData: ALWAYS Use VersionedSchema

Never ship unversioned SwiftData. Start with VersionedSchema from day one to avoid migration crashes.

import SwiftData

// ALWAYS start versioned
enum AppSchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [Item.self, Category.self]
    }

    @Model
    final class Item {
        var id: UUID
        var name: String
        var createdAt: Date

        init(name: String) {
            self.id = UUID()
            self.name = name
            self.createdAt = Date()
        }
    }

    @Model
    final class Category {
        var id: UUID
        var name: String

        @Relationship(deleteRule: .cascade)
        var items: [Item] = []

        init(name: String) {
            self.id = UUID()
            self.name = name
        }
    }
}

// Migration Plan (even for V1)
enum AppMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [AppSchemaV1.self]
    }

    static var stages: [MigrationStage] { [] }
}

Adding V2 Later

enum AppSchemaV2: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)

    @Model
    final class Item {
        var id: UUID

        // RENAMING: Use @Attribute(originalName:)
        @Attribute(originalName: "name")
        var title: String

        var createdAt: Date

        // NEW PROPERTY: Provide default
        var priority: Int = 0
    }
}

enum AppMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [AppSchemaV1.self, AppSchemaV2.self]
    }

    static var stages: [MigrationStage] {
        [migrateV1toV2]
    }

    static let migrateV1toV2 = MigrationStage.lightweight(
        fromVersion: AppSchemaV1.self,
        toVersion: AppSchemaV2.self
    )
}

Migration Rules

Lightweight (automatic):

  • Adding new models
  • Adding properties with default values
  • Deleting properties
  • Renaming with @Attribute(originalName:)

Crashes if you:

  • Ship unversioned, then add VersionedSchema
  • Add .unique when duplicates exist
  • Rename without @Attribute(originalName:)

MVVM Pattern with SwiftData

// VIEW: @Query for reactive data
struct ItemListView: View {
    @Query(sort: \Item.createdAt, order: .reverse)
    private var items: [Item]

    @State private var viewModel = ItemListViewModel()
    @Environment(\.modelContext) private var modelContext

    var body: some View {
        List(items) { item in
            ItemRow(item: item)
        }
        .toolbar {
            Button("Add") {
                viewModel.addItem(context: modelContext)
            }
            .glassEffect(.regular.interactive())
        }
    }
}

// VIEWMODEL: Operations only
@Observable
final class ItemListViewModel {
    var newItemName = ""

    func addItem(context: ModelContext) {
        let item = Item(name: newItemName)
        context.insert(item)
        try? context.save()
        newItemName = ""
    }

    func deleteItem(_ item: Item, context: ModelContext) {
        context.delete(item)
        try? context.save()
    }
}

Pattern:

  • @Query in Views for reactive display
  • ViewModels for business logic
  • Pass ModelContext to methods (don't store as property)

App Entry Point

import SwiftUI
import SwiftData

@main
struct MyApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema(versionedSchema: AppSchemaV1.self)
        let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(
                for: schema,
                migrationPlan: AppMigrationPlan.self,
                configurations: [config]
            )
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
    }
}

PART 3: PROJECT STRUCTURE

ProjectName/
├── App/
│   ├── ProjectNameApp.swift           # @main, ModelContainer
│   └── AppConfig.swift                # Configuration constants
│
├── Models/
│   ├── Schemas/
│   │   ├── AppSchemaV1.swift          # VersionedSchema V1
│   │   └── AppMigrationPlan.swift     # SchemaMigrationPlan
│   └── Domain/
│       └── DomainModels.swift         # Non-persisted models
│
├── Views/
│   ├── ContentView.swift
│   ├── [Feature]/
│   │   ├── [Feature]View.swift
│   │   └── [Feature]Components.swift
│   └── Components/
│       ├── GlassCard.swift
│       └── FloatingActionBar.swift
│
├── ViewModels/
│   └── [Feature]ViewModel.swift       # @Observable ViewModels
│
├── Services/
│   ├── NetworkService.swift
│   └── [Domain]Service.swift
│
├── Utilities/
│   ├── Extensions/
│   └── Constants.swift
│
└── Resources/
    ├── Assets.xcassets
    ├── Localizable.xcstrings
    └── PrivacyInfo.xcprivacy          # Required since May 2024

PART 4: APP STORE VERIFICATION CHECKLIST

Run before EVERY App Store submission.

TIER 1: Critical (Prevents Rejections)

Build Quality

  • Clean Release build succeeds
  • ZERO warnings in Release configuration
  • BUILD_ACTIVE_ARCHITECTURE_ONLY = NO for Release

Code Signing

  • Team ID: YOUR_TEAM_ID
  • Bundle identifier: reverse-DNS format
  • Provisioning profile valid and not expired
  • Deployment target iOS 17.0+

Privacy & Security

  • PrivacyInfo.xcprivacy exists (required since May 2024)
  • Required reason APIs declared:
    • UserDefaults API
    • File timestamp APIs
    • System boot time APIs
    • Disk space APIs
  • Privacy descriptions in Info.plist
  • All URLs use HTTPS
  • No hardcoded credentials or API keys

Deprecated APIs

  • No UIWebView (use WKWebView)
  • No ObservableObject (use @Observable)
  • No Core Data (use SwiftData)
  • Third-party libraries iOS 26 compatible

Assets

  • App icon 1024x1024px present
  • App icon has NO transparency
  • App icon has NO applied effects
  • Launch screen configured

TIER 2: Quality

Code Quality

  • No placeholder text ("lorem ipsum", "TODO: Replace")
  • No TODO/FIXME comments in production
  • No debug print statements
  • No forced unwraps in production paths

Info.plist

  • CFBundleVersion set
  • CFBundleShortVersionString matches release
  • CFBundleDisplayName set
  • UISupportedInterfaceOrientations set

TIER 3: iOS 26 Specific

  • Built with Xcode 16+
  • Liquid Glass design implemented
  • Essential UI in central 70% of canvas
  • No dependencies blocking iOS 26

PART 5: DEPRECATED PATTERNS

PatternWhy DeprecatedUse Instead
ObservableObjectLegacy, unnecessary re-renders@Observable macro
@PublishedRequires ObservableObjectProperties on @Observable
@StateObjectFor ObservableObject@State with @Observable
@ObservedObjectFor ObservableObjectDirect access or @Bindable
Core DataLegacy persistenceSwiftData
UserDefaults for modelsNot type-safeSwiftData
Unversioned SwiftDataMigration crashesVersionedSchema from day one
UIKit wrappersWhen SwiftUI native existsNative SwiftUI
Custom tab barsPoor Liquid Glass supportNative TabView

PART 6: UIKit INTEGRATION

For camera apps and other UIKit requirements:

// SwiftUI wrapper for UIKit view
struct CameraPreview: UIViewRepresentable {
    let session: AVCaptureSession

    func makeUIView(context: Context) -> PreviewView {
        let view = PreviewView()
        view.videoPreviewLayer.session = session
        view.videoPreviewLayer.videoGravity = .resizeAspectFill
        return view
    }

    func updateUIView(_ uiView: PreviewView, context: Context) {}
}

class PreviewView: UIView {
    override class var layerClass: AnyClass {
        AVCaptureVideoPreviewLayer.self
    }

    var videoPreviewLayer: AVCaptureVideoPreviewLayer {
        layer as! AVCaptureVideoPreviewLayer
    }
}

UIKit Navigation Bar with SwiftUI Content

// In UIViewController
let hostingController = UIHostingController(rootView: SwiftUIToolbar())
hostingController.sizingOptions = [.intrinsicContentSize]
hostingController.view.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: hostingController.view)

PART 7: PERFORMANCE

Glass Effect Performance

  • Glass uses CABackdropLayer - GPU intensive
  • Test on target devices (iPhone XR, SE for baseline)
  • Limit simultaneous glass surfaces to 3-5
  • Avoid glass on rapidly-animating content
  • Profile with Instruments if frame drops occur

SwiftData Performance

// Batch operations
modelContext.autosaveEnabled = false
for item in items {
    modelContext.insert(item)
}
try modelContext.save()
modelContext.autosaveEnabled = true

// Efficient queries with predicates
@Query(filter: #Predicate<Item> { $0.isActive })
private var activeItems: [Item]

@Observable Performance

Views only re-render when properties they actually read change:

@Observable
class ViewModel {
    var title: String = ""      // Change triggers re-render only if view reads title
    var internalState: Int = 0  // No re-render if view doesn't read this
}

PART 8: COMMON COMMANDS

# Build and run
xcodebuild -scheme ProjectName -destination 'platform=iOS Simulator,name=iPhone 16 Pro'

# Run tests
xcodebuild test -scheme ProjectName -destination 'platform=iOS Simulator,name=iPhone 16 Pro'

# Clean build
xcodebuild clean -scheme ProjectName

# Archive for App Store
xcodebuild archive -scheme ProjectName -archivePath build/ProjectName.xcarchive

# Show build settings
xcodebuild -showBuildSettings | grep -E "TEAM|BUNDLE|TARGET"

Quick Start Checklist

  • Set deployment target iOS 17.0 minimum
  • Configure Team ID: YOUR_TEAM_ID
  • Create folder structure
  • Define VersionedSchema from day one
  • Use @Observable for all ViewModels
  • Use @State for ViewModels in Views
  • Use native Apple components (Liquid Glass optimized)
  • Keep essential UI in central 70% of canvas
  • Apply .glassEffect() only to overlay elements
  • Create PrivacyInfo.xcprivacy
  • Test on iOS 26 simulator
  • Run verification checklist before submission

App Store Text Rules

  • Never use emojis in "What to Test" or App Store texts
  • Follow Liquid Glass design for iOS 26+ screenshots
  • Ensure app icon follows current guidelines
  • Test on multiple device sizes

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

bunny-net

No summary provided by upstream source.

Repository SourceNeeds Review
General

klarsprak

No summary provided by upstream source.

Repository SourceNeeds Review
General

transcribe

No summary provided by upstream source.

Repository SourceNeeds Review
General

fiken

No summary provided by upstream source.

Repository SourceNeeds Review