developing-ios-apps

Develops iOS/macOS applications with XcodeGen, SwiftUI, and SPM. Handles Apple Developer signing, notarization, and CI/CD pipelines. Triggers on XcodeGen…

INSTALLATION
npx skills add https://github.com/daymade/claude-code-skills --skill developing-ios-apps
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2b

Task

Command

Generate project

xcodegen generate

Build simulator

xcodebuild -destination 'platform=iOS Simulator,name=iPhone 17' build

Build device (paid account)

xcodebuild -destination 'platform=iOS,name=DEVICE' -allowProvisioningUpdates build

Clean DerivedData

rm -rf ~/Library/Developer/Xcode/DerivedData/PROJECT-*

Find device name

xcrun xctrace list devices

XcodeGen Configuration

Minimal project.yml

name: AppName

options:

  bundleIdPrefix: com.company

  deploymentTarget:

    iOS: "16.0"

settings:

  base:

    SWIFT_VERSION: "6.0"

packages:

  SomePackage:

    url: https://github.com/org/repo

    from: "1.0.0"

targets:

  AppName:

    type: application

    platform: iOS

    sources:

      - path: AppName

    settings:

      base:

        INFOPLIST_FILE: AppName/Info.plist

        PRODUCT_BUNDLE_IDENTIFIER: com.company.appname

        CODE_SIGN_STYLE: Automatic

        DEVELOPMENT_TEAM: TEAM_ID_HERE

    dependencies:

      - package: SomePackage

Code Signing Configuration

Personal (free) account: Works in Xcode GUI only. Command-line builds require paid account.

# In target settings

settings:

  base:

    CODE_SIGN_STYLE: Automatic

    DEVELOPMENT_TEAM: TEAM_ID  # Get from Xcode → Settings → Accounts

Get Team ID:

security find-identity -v -p codesigning | head -3

iOS Version Compatibility

API Changes by Version

iOS 17+ Only

iOS 16 Compatible

.onChange { old, new in }

.onChange { new in }

ContentUnavailableView

Custom VStack

AVAudioApplication

AVAudioSession

@Observable macro

@ObservableObject

SwiftData

CoreData/Realm

Lowering Deployment Target

  • Update project.yml:
deploymentTarget:

  iOS: "16.0"
  • Fix incompatible APIs:
// iOS 17

.onChange(of: value) { oldValue, newValue in }

// iOS 16

.onChange(of: value) { newValue in }

// iOS 17

ContentUnavailableView("Title", systemImage: "icon")

// iOS 16

VStack {

    Image(systemName: "icon").font(.system(size: 48))

    Text("Title").font(.title2.bold())

}

// iOS 17

AVAudioApplication.shared.recordPermission

// iOS 16

AVAudioSession.sharedInstance().recordPermission
  • Regenerate: xcodegen generate

Device Deployment

First-time Setup

  • Connect device via USB
  • Trust computer on device
  • In Xcode: Settings → Accounts → Add Apple ID
  • Select device in scheme dropdown
  • Run (Cmd + R)
  • On device: Settings → General → VPN & Device Management → Trust

Command-line Build (requires paid account)

xcodebuild \

  -project App.xcodeproj \

  -scheme App \

  -destination 'platform=iOS,name=DeviceName' \

  -allowProvisioningUpdates \

  build

Common Issues

Error

Solution

"Library not loaded: @rpath/Framework"

SPM dynamic framework not embedded. Build in Xcode GUI first, then CLI works

"No Account for Team"

Add Apple ID in Xcode Settings → Accounts

"Provisioning profile not found"

Free account limitation. Use Xcode GUI or get paid account

Device not listed

Reconnect USB, trust computer on device, restart Xcode

DerivedData won't delete

Close Xcode first: pkill -9 Xcode && rm -rf ~/Library/Developer/Xcode/DerivedData/PROJECT-*

Free vs Paid Developer Account

Feature

Free Apple ID

Paid ($99/year)

Xcode GUI builds

Command-line builds

App validity

7 days

1 year

App Store

CI/CD

SPM Dependencies

SPM Dynamic Framework Not Embedded

Root Cause: XcodeGen doesn't generate the "Embed Frameworks" build phase for SPM dynamic frameworks (like RealmSwift, Realm). The app builds successfully but crashes on launch with:

dyld: Library not loaded: @rpath/RealmSwift.framework/RealmSwift

  Referenced from: /var/containers/Bundle/Application/.../App.app/App

  Reason: image not found

Why This Happens:

  • Static frameworks (most SPM packages) are linked into the binary - no embedding needed
  • Dynamic frameworks (RealmSwift, etc.) must be copied into the app bundle
  • XcodeGen generates link phase but NOT embed phase for SPM packages
  • embed: true in project.yml causes build errors (XcodeGen limitation)

The Fix (Manual, one-time per project):

  • Open project in Xcode GUI
  • Select target → General → Frameworks, Libraries
  • Find the dynamic framework (RealmSwift)
  • Change "Do Not Embed" → "Embed & Sign"
  • Build and run from Xcode GUI first

After Manual Fix: Command-line builds (xcodebuild) will work because Xcode persists the embed setting in project.pbxproj.

Identifying Dynamic Frameworks:

# Check if a framework is dynamic

file ~/Library/Developer/Xcode/DerivedData/PROJECT-*/Build/Products/Debug-iphoneos/FRAMEWORK.framework/FRAMEWORK

# Dynamic: "Mach-O 64-bit dynamically linked shared library"

# Static: "current ar archive"

Adding Packages

packages:

  AudioKit:

    url: https://github.com/AudioKit/AudioKit

    from: "5.6.5"

  RealmSwift:

    url: https://github.com/realm/realm-swift

    from: "10.54.6"

targets:

  App:

    dependencies:

      - package: AudioKit

      - package: RealmSwift

        product: RealmSwift  # Explicit product name when package has multiple

Resolving Dependencies (China proxy)

git config --global http.proxy http://127.0.0.1:1082

git config --global https.proxy http://127.0.0.1:1082

xcodebuild -scmProvider system -resolvePackageDependencies

Never clear global SPM cache (~/Library/Caches/org.swift.swiftpm). Re-downloading is slow.

Camera / AVFoundation

Camera preview requires real device (simulator has no camera).

Quick Debugging Checklist

  • Permission: Added NSCameraUsageDescription to Info.plist?
  • Device: Running on real device, not simulator?
  • Session running: session.startRunning() called on background thread?
  • View size: UIViewRepresentable has non-zero bounds?
  • Video mirroring: Disabled automaticallyAdjustsVideoMirroring before setting isVideoMirrored?

Video Mirroring (Front Camera)

CRITICAL: Must disable automatic adjustment before setting manual mirroring:

// WRONG - crashes with "Cannot be set when automaticallyAdjustsVideoMirroring is YES"

connection.isVideoMirrored = true

// CORRECT - disable automatic first

connection.automaticallyAdjustsVideoMirroring = false

connection.isVideoMirrored = true

UIViewRepresentable Sizing Issue

UIViewRepresentable in ZStack may have zero bounds. Fix with explicit frame:

// BAD: UIViewRepresentable may get zero size in ZStack

ZStack {

    CameraPreviewView(session: session)  // May be invisible!

    OtherContent()

}

// GOOD: Explicit sizing

ZStack {

    GeometryReader { geo in

        CameraPreviewView(session: session)

            .frame(width: geo.size.width, height: geo.size.height)

    }

    .ignoresSafeArea()

    OtherContent()

}

Debug Logging Pattern

Add logging to trace camera flow:

import os

private let logger = Logger(subsystem: "com.app", category: "Camera")

func start() async {

    logger.info("start() called, isRunning=\(self.isRunning)")

    // ... setup code ...

    logger.info("session.startRunning() completed")

}

// For CGRect (doesn't conform to CustomStringConvertible)

logger.info("bounds=\(NSCoder.string(for: self.bounds))")

Filter in Console.app by subsystem.

For detailed camera implementation: See references/camera-avfoundation.md

macOS Code Signing & Notarization

For distributing macOS apps (Electron or native) outside the App Store, signing + notarization is required. Without it users see "Apple cannot check this app for malicious software."

5-step checklist:

Step

What

Critical detail

1

Create CSR in Keychain Access

Common Name doesn't matter; choose "Saved to disk"

2

Request Developer ID Application cert at developer.apple.com

Choose G2 Sub-CA (not Previous Sub-CA)

3

Install .cer → must choose **login keychain**

iCloud/System → Error -25294 (private key mismatch)

4

Export P12 from login keychain with password

Base64: base64 -i cert.p12 | pbcopy

5

Create App Store Connect API Key (Developer role)

Download .p8 once only; record Key ID + Issuer ID

GitHub Secrets required (5 secrets):

Secret

Source

MACOS_CERT_P12

Step 4 base64

MACOS_CERT_PASSWORD

Step 4 password

APPLE_API_KEY

Step 5 .p8 base64

APPLE_API_KEY_ID

Step 5 Key ID

APPLE_API_ISSUER

Step 5 Issuer ID

**APPLE_TEAM_ID is NOT needed.** notarytool infers team from the API key. Passing teamId to @electron/notarize v2.5.0 causes a credential conflict error.

Electron Forge osxSign critical settings:

osxSign: {

  identity: 'Developer ID Application',

  hardenedRuntime: true,

  entitlements: 'entitlements.mac.plist',

  entitlementsInherit: 'entitlements.mac.plist',

  continueOnError: false,  // CRITICAL: default is true, silently falls back to adhoc

  // Skip non-binary files in large embedded runtimes (prevents EMFILE)

  ignore: (filePath: string) => {

    if (!filePath.includes('python-runtime')) return false;

    if (/\.(so|dylib|node)$/.test(filePath)) return false;

    return true;

  },

  // CI: explicitly specify keychain (apple-actions/import-codesign-certs uses signing_temp.keychain)

  ...(process.env.MACOS_SIGNING_KEYCHAIN

    ? { keychain: process.env.MACOS_SIGNING_KEYCHAIN }

    : {}),

},

Fail-fast three-layer defense:

  • @electron/osx-sign: continueOnError: false — signing error throws immediately
  • postPackage hook: codesign --verify --deep --strict + adhoc detection
  • Release trigger script: verify local HEAD matches remote before dispatch

Verify signing:

security find-identity -v -p codesigning | grep "Developer ID Application"

For complete step-by-step guide, entitlements, workflow examples, and full troubleshooting (7 real-world errors with root causes): references/apple-codesign-notarize.md

Resources

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card