Design great App Clips
Design 入门 22m

设计出色的 App Clips

Design great App Clips

2020年6月23日

在 Apple 官方观看视频

一句话判断

App Clips 的设计哲学只有八个字:“秒开、秒用、不废话”——如果你设计 App Clip 时还在想”怎么让用户看到更多功能”,你就搞反了。

这场 Session 讲了什么

这场 Session 从设计和用户体验角度讲解了如何打造优秀的 App Clip。App Clip 是主应用的轻量级版本,用户通过 NFC、QR 码或 Safari 横幅触发后,无需安装完整应用即可使用核心功能。Session 强调了 App Clip 设计的三个核心原则:即时性(instant)、轻量化(lightweight)和聚焦性(focused)。

即时性意味着 App Clip 必须在几秒内完成加载并展示核心功能。用户扫码点咖啡的期望是”看到菜单 -> 下单 -> 付款”,而不是”看到欢迎页 -> 注册账号 -> 填写个人信息 -> 然后才能点餐”。Session 反复强调了”零摩擦”的设计目标——利用 Sign in with Apple 的自动静默登录和 Apple Pay 的一键支付,消除所有不必要的交互步骤。

轻量化要求 App Clip 只包含完成当前任务所需的最少功能。Session 展示了几个反面教材:把完整应用的导航栏、设置页、个人中心都塞进 App Clip,导致体积超限、加载缓慢。正确的做法是只保留一条线性任务流,完成任务后通过 SKOverlay 引导用户安装完整应用。

值得深挖的点

任务流设计的”单线原则”

App Clip 应该只有一条线性的任务流:进入 -> 执行核心操作 -> 完成/安装完整应用。不要提供分支选项、不要让用户做选择、不要展示与当前场景无关的功能。Session 用停车场缴费的例子说明——用户扫了停车位的二维码,App Clip 应该直接展示”确认缴费 ¥15,车牌号 xxx”,而不是展示停车场的楼层导航、会员注册和积分商城。

Sign in with Apple 与 Apple Pay 的”免登录”体验

App Clip 可以利用 Sign in with Apple 的静默模式——用户第一次使用时自动创建临时账号,无需任何登录操作。配合 Apple Pay,用户在 App Clip 中的整个流程可以是:扫码 -> 看到订单 -> 指纹确认支付 -> 完成。全程零输入、零注册、零等待。Session 将这种体验称为”像用自助售货机一样简单”。

代码片段

// App Clip 的精简任务流设计
import SwiftUI

struct CoffeeOrderClipView: View {
    let storeName: String
    let storeID: String
    @State private var selectedDrink: Drink?
    @State private var isProcessing = false
    
    var body: some View {
        // 单一任务流:选饮品 -> 确认 -> 支付
        VStack(spacing: 16) {
            // 顶部:店铺信息,来自扫码 URL 参数
            Text(storeName)
                .font(.headline)
                .padding(.top)
            
            // 饮品选择(简洁网格)
            LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())],
                       spacing: 12) {
                ForEach(drinks) { drink in
                    DrinkCard(drink: drink)
                        .onTapGesture {
                            selectedDrink = drink
                        }
                }
            }
            
            Spacer()
            
            // 底部:一键支付按钮
            if let drink = selectedDrink {
                PayButton(drink: drink, storeID: storeID) {
                    isProcessing = true
                }
                .padding(.bottom)
            }
        }
        // 不显示导航栏,不提供返回上一级
        .navigationBarHidden(true)
    }
}
// 利用 Apple Pay 实现一键支付
import SwiftUI
import PassKit

struct PayButton: View {
    let drink: Drink
    let storeID: String
    let onPaymentStart: () -> Void
    
    // 检查设备是否支持 Apple Pay
    var applePayAvailable: Bool {
        PKPaymentAuthorizationController.canMakePayments(
            usingNetworks: [.visa, .masterCard, .chinaUnionPay]
        )
    }
    
    var body: some View {
        VStack(spacing: 8) {
            Text("\(drink.name) - ¥\(drink.price)")
                .font(.subheadline)
            
            if applePayAvailable {
                // 使用 Apple Pay 按钮
                Button {
                    onPaymentStart()
                    initiateApplePay()
                } label: {
                    Text("用 Apple Pay 支付")
                        .bold()
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.black)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
            }
        }
    }
    
    func initiateApplePay() {
        let request = PKPaymentRequest()
        request.merchantIdentifier = "merchant.your.id"
        request.paymentSummaryItems = [
            PKPaymentSummaryItem(label: drink.name, amount: NSDecimalNumber(value: drink.price))
        ]
        request.supportedNetworks = [.visa, .masterCard, .chinaUnionPay]
        request.merchantCapabilities = .capability3DS
        
        // 直接弹出 Apple Pay,不需要任何用户信息填写
        let controller = PKPaymentAuthorizationController(paymentRequest: request)
        // controller.delegate = self
        controller.present()
    }
}
// 任务完成后引导安装完整应用
import StoreKit

struct OrderCompleteView: View {
    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: "checkmark.circle.fill")
                .font(.system(size: 60))
                .foregroundColor(.green)
            
            Text("下单成功!")
                .font(.title2)
                .fontWeight(.semibold)
            
            Text("正在制作中,预计 5 分钟")
                .font(.subheadline)
                .foregroundColor(.secondary)
            
            Button("完成") {
                // 展示安装完整应用的引导
                showAppStoreOverlay()
            }
            .buttonStyle(.borderedProminent)
        }
    }
    
    func showAppStoreOverlay() {
        let config = SKOverlay.AppClipConfiguration(position: .bottom)
        let overlay = SKOverlay(
            appIdentifier: "你的AppStoreID",
            configuration: config
        )
        if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
            overlay.present(in: scene)
        }
    }
}

最佳实践

  • App Clip 的首屏加载时间控制在 3 秒以内,超过这个时间用户会直接离开
  • 利用 URL 参数直接跳转到具体功能,不要展示通用的”首页”
  • 使用 Sign in with Apple 的静默模式,避免任何注册/登录步骤
  • 在 App Clip 完成任务后,通过 SKOverlay 非侵入式地引导安装完整应用
  • 为每个物理触点(NFC 标签、QR 码)设计独立的 App Clip 体验 URL

还有什么值得关注

  • App Clip 的 10MB 体积限制意味着你不能使用 On-Demand Resources,但可以引用系统框架的功能
  • Session 提到了 App Clip Code——一种 Apple 设计的新二维码,同时支持 NFC 和视觉扫描
  • 用户完成 App Clip 体验后,完整应用会在后台自动下载(如果用户同意安装),确保打开完整应用时数据无缝衔接
WWDC 2020