makgunay

macos-distribution

macOS app distribution covering code signing (Developer ID, App Store certificates), notarization (notarytool), DMG/pkg creation, App Store submission workflow, sandboxing entitlements, StoreKit for in-app purchases and subscriptions (SubscriptionOfferView, appTransactionID, Transaction.currentEntitlements, subscriptionStatusTask), StoreKit testing with local configuration files, PrivacyInfo.xcprivacy manifest, and dual distribution strategies (App Store + direct). Use when preparing an app for distribution, implementing purchases/subscriptions, configuring signing, or troubleshooting App Store rejection.

makgunay 1 Updated 7mo ago
GitHub

Install

npx skillscat add makgunay/claude-swift-skills/macos-distribution

Install via the SkillsCat registry.

About this skill

We need to produce a 2-3 sentence plain-text summary, objective, factual, no marketing language, no superlatives, no calls to action, natural prose, no bullet points, no headings, no markdown. At most 60 words. We need to summarize the skill: macOS app distribution covering code signing, notarization, DMG/pkg creation, App Store submission workflow, sandboxing entitlements, StoreKit for in-app purchases/subscriptions, PrivacyInfo.xcprivacy, dual distribution strategies.

SKILL.md

macOS Distribution & StoreKit

Critical Constraints

  • ❌ DO NOT distribute without code signing → ✅ Always sign with Developer ID (direct) or Apple Distribution (App Store)
  • ❌ DO NOT skip notarization for direct distribution → ✅ Required since macOS 10.15 for Gatekeeper
  • ❌ DO NOT use Transaction.currentEntitlement(for:) → ✅ Use Transaction.currentEntitlements(for:) (plural, returns sequence)
  • ❌ DO NOT forget PrivacyInfo.xcprivacy → ✅ Required for App Store submission

Distribution Decision Tree

App Store distribution?
├── YES → Apple Distribution certificate + sandbox + review
│   ├── Basic version (sandbox-safe features)
│   └── Pro features via IAP/subscription
└── Direct distribution?
    ├── Developer ID certificate + notarization
    ├── DMG or pkg installer
    └── Full system access (no sandbox required)

Dual strategy?
├── App Store: Basic/free version, sandbox-safe
└── Direct: Pro version, full Accessibility/CGEvent access

Code Signing & Notarization (Direct)

# Sign with Developer ID
codesign --deep --force --verify --verbose \
    --sign "Developer ID Application: Your Name (TEAMID)" \
    --options runtime \
    MyApp.app

# Create DMG
hdiutil create -volname "MyApp" -srcfolder MyApp.app \
    -ov -format UDZO MyApp.dmg

# Notarize
xcrun notarytool submit MyApp.dmg \
    --apple-id "you@email.com" \
    --team-id "TEAMID" \
    --password "app-specific-password" \
    --wait

# Staple (attach notarization ticket)
xcrun stapler staple MyApp.dmg

Sandboxing Entitlements (App Store)

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key><true/>
    <key>com.apple.security.network.client</key><true/>
    <key>com.apple.security.files.user-selected.read-write</key><true/>
    <key>com.apple.developer.icloud-container-identifiers</key>
    <array><string>iCloud.com.company.app</string></array>
    <key>com.apple.security.application-groups</key>
    <array><string>group.com.company.app</string></array>
</dict>
</plist>

StoreKit — SubscriptionOfferView

import StoreKit

// Basic subscription offer
SubscriptionOfferView(productID: "com.app.pro.monthly")
    .prefersPromotionalIcon(true)

// With custom icon
SubscriptionOfferView(productID: "com.app.pro.monthly") {
    Image("pro_icon").resizable().frame(width: 40, height: 40)
}

// Show upgrade/downgrade based on customer status
SubscriptionOfferView(groupID: "com.app.subscriptions", visibleRelationship: .upgrade)

// Detail action
SubscriptionOfferView(productID: "com.app.pro.monthly")
    .subscriptionOfferViewDetailAction { isShowingStore = true }

StoreKit — Transaction Handling

// Check entitlements (new plural API)
for await result in Transaction.currentEntitlements(for: "com.app.pro") {
    if case .verified(let transaction) = result {
        let appTxID = transaction.appTransactionID
        if let offerPeriod = transaction.offerPeriod {
            // Handle offer period
        }
    }
}

// Track subscription status
.subscriptionStatusTask(for: "com.app.subscriptions") { statuses in
    if statuses.contains(where: { $0.state == .subscribed }) {
        customerStatus = .subscribed
    } else {
        customerStatus = .notSubscribed
    }
}

// AppTransaction — unique download identifier
let appTransaction = try await AppTransaction.shared
let transactionID = appTransaction.appTransactionID
let platform = appTransaction.originalPlatform  // .iOS, .macOS, .tvOS, .visionOS

StoreKit Testing in Xcode

  1. File → New → File From Template → search "StoreKit Configuration File"
  2. Define products in the .storekit file
  3. Edit scheme → Options → StoreKit Configuration → select your file
  4. Use Transaction Manager window to test purchase scenarios

Launch at Login

import ServiceManagement

func setLaunchAtLogin(_ enabled: Bool) {
    do {
        if enabled { try SMAppService.mainApp.register() }
        else { try SMAppService.mainApp.unregister() }
    } catch { print("Launch at login error: \(error)") }
}

func isLaunchAtLoginEnabled() -> Bool {
    SMAppService.mainApp.status == .enabled
}

Common Mistakes & Fixes

Mistake Fix
"App is damaged" error Notarize and staple before distribution
App Store rejection for entitlements Justify each entitlement in review notes
currentEntitlement(for:) deprecated Use currentEntitlements(for:) (plural)
Missing privacy manifest Add PrivacyInfo.xcprivacy to app target
StoreKit products not loading in dev Set StoreKit Configuration in scheme options

References