Meet Watch Face Sharing
watchOS 入门 16m

认识表盘分享功能

Meet Watch Face Sharing

2020年6月25日

在 Apple 官方观看视频

一句话判断

watchOS 7 终于允许用户分享自定义表盘了,而且你的 App 可以通过 URL 或 NFC 标签引导用户直接添加包含你 Complication 的表盘,这是手表端获客的新通道。

这场 Session 讲了什么

watchOS 7 引入了 Watch Face Sharing 功能。用户可以创建自定义表盘,然后通过信息、邮件、AirDrop 或链接的方式分享给其他人。接收者点击后就能直接将这个表盘添加到自己的 Apple Watch 上。

对开发者来说,这意味着你可以创建一个包含你 App Complication 的表盘,然后通过各种渠道分发给用户。比如一个天气 App 可以分享一个包含天气 Complication 的”户外运动”表盘。用户添加这个表盘后,天气 Complication 就自然地出现在了用户的表盘上。Session 详细介绍了如何生成 Watch Face 的分享 URL、如何在网页中嵌入表盘预览、以及如何通过 NFC 标签触发表盘添加。

值得深挖的点

表盘文件的格式

Watch Face 使用一种特殊的 JSON 格式(.watchface 文件)来描述表盘的配置,包括表盘类型、颜色、包含的 Complication 以及它们的位置。虽然你可以手动构造这个 JSON,但 Apple 推荐通过 Watch App 来创建表盘,然后用系统分享功能导出。这样生成的文件格式更可靠,而且会自动处理设备兼容性问题。

Complication 的多实例支持

watchOS 7 的另一个重要变化是:同一个 App 可以在一个表盘上提供多个 Complication。比如一个健身 App 可以同时显示”今日步数”和”心率”两个 Complication。通过 CLKComplicationDescriptor 你可以声明你的 App 支持哪些 Complication 类型,用户在编辑表盘时会看到所有可选项。

代码片段

声明多个 Complication

场景:为天气 App 声明多个 Complication 类型。

import ClockKit

class ComplicationController: NSObject, CLKComplicationDataSource {
    
    // 声明支持的 Complication 描述符
    func complicationDescriptors() -> [CLKComplicationDescriptor] {
        return [
            // 当前天气
            CLKComplicationDescriptor(
                identifier: "current_weather",
                displayName: "当前天气",
                supportedFamilies: CLKComplicationFamily.allCases
            ),
            // 今日温度
            CLKComplicationDescriptor(
                identifier: "today_temperature",
                displayName: "今日温度",
                supportedFamilies: [.modularSmall, .utilitarianSmall, .circularSmall]
            ),
            // 降雨概率
            CLKComplicationDescriptor(
                identifier: "rain_chance",
                displayName: "降雨概率",
                supportedFamilies: [.modularSmall, .circularSmall]
            )
        ]
    }
    
    // 提供特定 Complication 的时间线数据
    func getCurrentTimelineEntry(
        for complication: CLKComplication,
        withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
        
        // 根据描述符标识符返回不同的数据
        switch complication.identifier {
        case "current_weather":
            let template = createCurrentWeatherTemplate(for: complication.family)
            let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
            handler(entry)
            
        case "today_temperature":
            let template = createTemperatureTemplate(for: complication.family)
            let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
            handler(entry)
            
        default:
            handler(nil)
        }
    }
    
    private func createCurrentWeatherTemplate(
        for family: CLKComplicationFamily) -> CLKComplicationTemplate {
        // 根据不同的表盘位置创建不同的模板
        switch family {
        case .modularLarge:
            return CLKComplicationTemplateModularLargeStandard(
                headerTextProvider: CLKSimpleTextProvider(text: "北京"),
                body1TextProvider: CLKSimpleTextProvider(text: "晴"),
                body2TextProvider: CLKSimpleTextProvider(text: "26°C")
            )
        case .graphicCorner:
            return CLKComplicationTemplateGraphicCornerStackText(
                outerTextProvider: CLKSimpleTextProvider(text: "☀️ 晴"),
                innerTextProvider: CLKSimpleTextProvider(text: "26°C")
            )
        default:
            return CLKComplicationTemplateModularSmallSimpleText(
                textProvider: CLKSimpleTextProvider(text: "26°")
            )
        }
    }
}

通过 URL 分享表盘

场景:在 App 或网页中创建表盘分享链接。

import UIKit

class WatchFaceSharingViewController: UIViewController {
    
    @IBAction func shareWatchFaceTapped(_ sender: UIButton) {
        // 方法一:从文件分享
        // 1. 在 Watch App 中创建并导出 .watchface 文件
        // 2. 在 iPhone App 中使用 UIActivityViewController 分享
        
        guard let watchFaceURL = Bundle.main.url(forResource: "outdoor_sport",
                                                    withExtension: "watchface") else {
            print("未找到表盘文件")
            return
        }
        
        let activityVC = UIActivityViewController(
            activityItems: [watchFaceURL],
            applicationActivities: nil
        )
        present(activityVC, animated: true)
    }
    
    // 方法二:生成深度链接
    func generateWatchFaceLink() -> URL? {
        // Apple 的表盘分享链接格式
        // https://clockface.apple.com/share?v=...
        // 实际使用时通过 Watch App 的分享功能获取完整 URL
        return nil
    }
}

使用 SwiftUI 创建 watchOS 7 的 Complication

场景:利用 SwiftUI 创建图形化的 Complication。

import SwiftUI
import ClockKit

// watchOS 7 支持 SwiftUI 模板
// CLKComplicationTemplateGraphicRectangularFullView

struct WeatherComplicationView: View {
    let temperature: String
    let condition: String
    
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Image(systemName: "sun.max.fill")
                    .foregroundColor(.yellow)
                Text(temperature)
                    .font(.title2)
                    .fontWeight(.bold)
            }
            Text(condition)
                .font(.caption2)
                .foregroundColor(.secondary)
        }
    }
}

// 在 ComplicationController 中使用
func getGraphicRectangularTemplate() -> CLKComplicationTemplate {
    let view = WeatherComplicationView(
        temperature: "26°C",
        condition: "晴朗"
    )
    return CLKComplicationTemplateGraphicRectangularFullView(
        contentView: view
    )
}

最佳实践

已有项目:如果你有 watchOS App,在 watchOS 7 中声明 complicationDescriptors() 方法来暴露你支持的所有 Complication。创建几个精心设计的”预设表盘”,包含你的 Complication 和系统 Complication 的组合,通过 App 或网站分享给用户。

新项目:从设计阶段就考虑 Complication 的展示策略。不同表盘位置的 Complication 大小和形状不同,需要为每种位置设计合适的信息密度。利用 SwiftUI 的 CLKComplicationTemplateGraphic* 系列模板来创建更丰富的图形化 Complication。

还有什么值得关注

  • 表盘分享链接可以在网页、邮件、社交媒体等任何地方分发。
  • 用户收到表盘后,如果还没有安装对应的 App,系统会提示安装。
  • NFC 标签也可以触发表盘添加,适合零售和实体场景。
WWDC 2020