WWDC22: 复杂组件

发布
更新
字数 868
阅读 5 分钟
阅读量 1613

复杂组件是 watchOS 关键的组成部分,它提供快速地一览重要信息的途径,通过点击,可以定位到应用。从 watchOS 5 开始,富组件功能又提供了使用图像内容即更多组件样式的能力。在 watchOS 7 加入 SwiftUI 后,复杂组件到达了一个新阶段。今天借助于 WidgetKit,复杂组件功能再次更新,使其以小组件的形式,把一览信息的体验带到 iOS 中。 截屏2022-06-15 09.52.32.png 基于最新的 WidgetKit,只要一次开发,就可以同时发布到 iOS 16 和 watchOS 9 ,实现一致的一览信息体验。这是因为现在有了新的 WidgetFamily 类型,以 accessory 开头的。

// WidgetFamily additions

public enum WidgetFamily {
    // ...
    
    // 适合摘要、刻度盘或者进度条
    case accessoryCircular
    
    // 适合多行文本、小图片或图表,与 `ClockKit` 的 `.graphicRectangular` 类似
    case accessoryRectangular
    
    // 显示在时间上的简短文本
    case accessoryInline
    
    // watchOS only
    case accessoryCorner
}

颜色

为了保持一致性,新的 accessory widget 主要由系统控制样式和布局,开发者可以根据系统当前渲染模式进行适配:full-color, accented, vibrant。

public struct WidgetRenderingMode {
    static var fullColor: WidgetRenderingMode
    static var accented: WidgetRenderingMode
    static var vibrant: WidgetRenderingMode
}

struct MyWidgetView: View {
    // 从环境变量获取渲染模式
    @Environment(\.widgetRenderingMode) var renderingMode
    
    var body: some View {
        switch renderingMode {
            ...
        }
    }
}

full-color mode

在 watchOS 上会按照设计的样式,如渐变色等,完全展示出来。

accented mode

视图会被分为两组,强调内容和默认内容。要设置为强调内容,可以使用 .widgetAccentable() 修改器。

vibrant mode

在 iOS 中,widget 的内容首先会被去色,然后调整亮度以适应锁屏界面界面的样式,同时会增加毛玻璃背景,让内容显示跟清晰。

ZStack {
    // 背景
    AccessoryWidgetBackground()
    // 内容
    VStack {
        Text("Mon")
        Text("6")
         .font(.title)
    }
}

实践

完整代码参照 https://developer.apple.com/documentation/widgetkit/adding_widgets_to_the_lock_screen_and_watch_faces

添加新的 accessory families

WidgetKit 中原有的 system family 在手表上不可用,所以需要用平台宏命令进行区分

struct EmojiRangerWidget: Widget {
    public var body: some WidgetConfiguration {
        // ...
        #if os(watchOS)
        .supportedFamilies([.accessoryCircular,
                            .accessoryRectangular, .accessoryInline])
        #else
        .supportedFamilies([.accessoryCircular,
                            .accessoryRectangular, .accessoryInline,
                            .systemSmall, .systemMedium])

为 watchOS 适配 IntentRecommendation API

因为在 iOS 中动态 widget 可以直接在界面中进行设置编辑,而在 watchOS 中,需要提供一个预配置好的列表,供用户选择,为此可以在 IntentTimelineProvider 中重写 recommendations 方法,接口参考:

public protocol IntentTimlineProvider {
    // ...
    associatedtype Intent : INIntent
    
    /// 提供一个可供用户在表单中选择的 intent configuration 建议集合
    func recommendations() -> [IntentRecommendation<INIntent>]
    
    // ...   
}

public struct IntentRecommendation<Intent> where Intent : INIntent {
    public init(intent: Intent, description: Text)
    public init(intent: Intent, description: LocalizedStringKey)
    public init<S>(intent: Intent, description: S) where S : StringProtocol
}

新的 SwiftUI ProgressView

新的.accessoryCircular 很适合使用新的环形进度条视图展示内容,基于 SwiftUI 的进度条是图可以自动更新。

case .accessoryCircular:
    ProgressView(interval: entry.character.injuryDate...entry.character.fullHealthDate,
                         countdown: false,
                         label: { Text(entry.character.name) },
                         currentValueLabel: {
                Avatar(character: entry.character, includeBackground: false)
            })
            .progressViewStyle(.circular)

区分主次

case .accessoryRectangular:
        HStack(alignment: .center, spacing: 0) {
            VStack(alignment: .leading) {
                // 主要内容
                Text(entry.character.name)
                    // 突出字体
                    .font(.headline)
                    // 突出颜色
                    .widgetAccentable()
                    
                Text("Level \(entry.character.level)")
                Text(entry.character.fullHealthDate, style: .timer)
            }.frame(maxWidth: .infinity, alignment: .leading)
            Avatar(character: entry.character, includeBackground: false)
        }

其他

在 watchOS 中使用 @Environment(\.isLuminanceReduced) var isLuminanceReduced 判断屏幕状态,可以动态的调整内容。

@Environment(\.isLuminanceReduced)
var isLuminanceReduced

var body: some View {
    if isLuminanceReduced {
        Text("🙈").font(.title)
    } else {
        Text("🐵").font(.title)
    }
}