Skip to content

πŸ“± Complete App Clips development toolkit for iOS with instant experiences

License

Notifications You must be signed in to change notification settings

muhittincamdali/AppClipsStudio

AppClipsStudio

πŸ“± Complete App Clips development toolkit for iOS with instant experiences

CI Status Swift 6.0 iOS 17.0+ App Clips SPM License

Features β€’ Installation β€’ Quick Start β€’ Components β€’ Documentation


πŸ“‹ Table of Contents


Why AppClipsStudio?

Building App Clips requires managing invocation URLs, handling deep links, creating lightweight experiences, and ensuring <10MB size. AppClipsStudio handles all the complexity.

// Define your App Clip in seconds
@main
struct MyAppClip: AppClip {
    var body: some Scene {
        AppClipScene { invocation in
            switch invocation.experience {
            case .orderFood(let restaurantId):
                OrderView(restaurant: restaurantId)
            case .rentBike(let stationId):
                BikeRentalView(station: stationId)
            }
        }
    }
}

Features

Feature Description
πŸš€ Quick Setup App Clip target in minutes
πŸ”— Smart Invocations NFC, QR, Safari, Maps, Messages
πŸ“ Location Verification Confirm user is at location
πŸ’³ Apple Pay Ready One-tap payments
πŸ” Sign in with Apple Seamless authentication
πŸ“¦ Size Optimizer Keep under 10MB limit
πŸ§ͺ Testing Tools Local & TestFlight testing
πŸ“Š Analytics Clip performance metrics

Installation

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/muhittincamdali/AppClipsStudio.git", from: "1.0.0")
]

Quick Start

1. Create App Clip Target

# Using CLI
appclipstudio init --name "MyAppClip" --bundleId "com.myapp.clip"

Or in Xcode: File β†’ New β†’ Target β†’ App Clip

2. Define Experiences

import AppClipsStudio

enum ClipExperience: AppClipExperience {
    case orderFood(restaurantId: String)
    case viewMenu(restaurantId: String)
    case payBill(tableId: String)
    
    static func parse(from url: URL) -> ClipExperience? {
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
            return nil
        }
        
        switch components.path {
        case "/order":
            return .orderFood(restaurantId: components.queryValue("id") ?? "")
        case "/menu":
            return .viewMenu(restaurantId: components.queryValue("id") ?? "")
        case "/pay":
            return .payBill(tableId: components.queryValue("table") ?? "")
        default:
            return nil
        }
    }
}

3. Build Your UI

struct OrderView: View {
    let restaurantId: String
    @StateObject private var viewModel = OrderViewModel()
    
    var body: some View {
        VStack {
            RestaurantHeader(id: restaurantId)
            MenuList(items: viewModel.menuItems)
            
            AppClipPayButton(amount: viewModel.total) {
                await viewModel.checkout()
            }
        }
        .appClipOverlay() // Shows "Get the Full App" banner
    }
}

Invocation Methods

NFC Tags

// Generate NFC payload
let nfcData = AppClipNFC.generate(
    url: "https://myapp.com/order?id=123",
    experience: .orderFood
)

// Write to NFC tag
try await NFCWriter.write(nfcData, to: tag)

QR Codes

// Generate App Clip QR code
let qrCode = AppClipQR.generate(
    url: "https://myapp.com/menu?id=456",
    size: CGSize(width: 200, height: 200),
    style: .appClipCode // Special Apple design
)

Image(uiImage: qrCode)

App Clip Codes

// Generate official App Clip Code
let clipCode = try await AppClipCode.request(
    url: "https://myapp.com/experience",
    style: .camera, // or .nfc
    color: .blue
)

Safari Smart Banner

<meta name="apple-itunes-app" 
      content="app-id=123456789, 
               app-clip-bundle-id=com.myapp.clip,
               app-clip-display=card">

Maps Integration

// Register location-based experience
AppClipLocation.register(
    experience: .orderFood,
    coordinate: CLLocationCoordinate2D(latitude: 37.33, longitude: -122.03),
    radius: 100 // meters
)

Components

AppClipPayButton

One-tap Apple Pay:

AppClipPayButton(
    amount: 29.99,
    currency: .usd,
    label: "Order Coffee"
) { result in
    switch result {
    case .success(let payment):
        await processPayment(payment)
    case .failure(let error):
        showError(error)
    }
}

AppClipSignInButton

Seamless authentication:

AppClipSignInButton { result in
    switch result {
    case .success(let credential):
        await createAccount(credential)
    case .failure(let error):
        showError(error)
    }
}

LocationVerification

Confirm user location:

LocationVerification(
    coordinate: restaurantLocation,
    radius: 50
) { verified in
    if verified {
        showOrderOptions()
    } else {
        showLocationError()
    }
}

AppClipOverlay

Promote full app:

ContentView()
    .appClipOverlay(
        title: "Get the Full Experience",
        subtitle: "Download MyApp for rewards & history",
        action: .openAppStore
    )

Size Optimization

App Clips must be under 10MB. AppClipsStudio helps:

// Analyze bundle size
let analysis = try await BundleAnalyzer.analyze()
print("Current size: \(analysis.totalSize)")
print("Limit: 10MB")

for item in analysis.largestAssets {
    print("\(item.name): \(item.size)")
}

// Recommendations
for suggestion in analysis.optimizations {
    print(suggestion)
}

Automatic Optimizations

AppClipOptimizer.configure {
    $0.compressImages = true
    $0.stripUnusedCode = true
    $0.minimizeAssets = true
    $0.useThinBinary = true
}

Testing

Local Testing

// Test invocation locally
AppClipTester.simulate(
    url: "https://myapp.com/order?id=test123",
    location: .mock(latitude: 37.33, longitude: -122.03)
)

TestFlight

// Configure for TestFlight
AppClipConfig.testFlight {
    $0.invocationURL = "https://myapp.com/test"
    $0.mockLocation = true
}

Xcode

# Run with invocation URL
appclipstudio run --url "https://myapp.com/order?id=123"

Analytics

// Track clip performance
AppClipAnalytics.track(.clipLaunched(experience: .orderFood))
AppClipAnalytics.track(.paymentCompleted(amount: 29.99))
AppClipAnalytics.track(.fullAppPromoted)

// Get insights
let metrics = await AppClipAnalytics.getMetrics()
print("Launches: \(metrics.launches)")
print("Conversions: \(metrics.fullAppInstalls)")
print("Revenue: \(metrics.totalRevenue)")

Data Transfer

Transfer data to full app:

// In App Clip
AppClipDataTransfer.save(
    key: "orderHistory",
    value: orderData
)

// In Full App
if let data = AppClipDataTransfer.retrieve(key: "orderHistory") {
    importOrderHistory(data)
}

Best Practices

Keep It Focused

// βœ… Good: Single focused task
struct BikeRentalClip: AppClip {
    var body: some Scene {
        AppClipScene { _ in
            RentBikeFlow() // Just rental, nothing else
        }
    }
}

// ❌ Avoid: Too many features

Fast Launch

// βœ… Good: Immediate content
struct QuickOrderClip: View {
    var body: some View {
        MenuView() // Shows immediately
            .task {
                await loadDetails() // Background
            }
    }
}

Clear Value

// Show value proposition immediately
SplashView(
    title: "Order in 30 Seconds",
    subtitle: "No app download required",
    action: "Start Order"
)

CLI Tool

# Initialize App Clip target
appclipstudio init

# Analyze bundle size
appclipstudio analyze

# Generate QR codes
appclipstudio qr --url "https://myapp.com/exp" --output qr.png

# Test invocation
appclipstudio test --url "https://myapp.com/order"

# Validate configuration
appclipstudio validate

Examples

See Examples:

  • CoffeeShop - Order & pay
  • BikeRental - Rent bikes
  • Restaurant - View menu & pay bill
  • Parking - Pay for parking

Requirements

Platform Version
iOS 17.0+
Xcode 16.0+

Contributing

See CONTRIBUTING.md.

License

MIT License - see LICENSE.


Build instant experiences ⚑


πŸ“ˆ Star History

Star History Chart

About

πŸ“± Complete App Clips development toolkit for iOS with instant experiences

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published

Languages