iOS进阶+实战

iOS开发之WidgetKit补充

2020-10-22  本文已影响0人  YungFan

在 iOS 14 正式版发布之前我写了一篇博文《iOS开发之WidgetKit》,iOS 14 正式版发布以后,经测试,Apple 改变了 Widget 的 API,所以本文进行一个补充说明(在前文的基础上做了修改,尤其是代码部分)。

介绍

App实现

Widget 寄宿于 App,所以首先必须将 App 功能实现。

添加Widget

  1. 点击项目,选择File > New > Target
  2. Application Extension中,选择Widget Extension,然后点击Next
  3. 输入扩展名的名称。
  4. 单击Finish
  5. 此时会生成一个新文件夹,包含以下内容
    • 扩展名.swift
    • 扩展名.intentdefinition
    • Assets.xcassets
    • Info.plist

数据共享

App 与 Widget 可以通过网络数据和本地数据两种方式进行数据的共享。

配置

  1. App 在Signing&Capabilities中打开App Groups,内容一般为group.Bundle Identifier
  2. Widget 必须在Signing&Capabilities中打开App Groups,内容与 App 保持一致。
配置App Groups.jpg

如果文件需要共享,可以选中 App 中需要共享给 Widget 的文件,然后勾选 Widget 的 Target。

实现

配置完成以后,可以通过UserDefaultsFileManager来实现 App 与 Widget 的数据共享,这里以UserDefaults为例,因为 SwiftUI 提供了@AppStorage来简化操作。

// 包含App Groups的UserDefaults
@AppStorage("contact", store: UserDefaults(suiteName: "group.cn.abc.yf.SwiftUI-Widget"))

// 然后在后面保存数据
@AppStorage("contact", store: UserDefaults(suiteName: "group.cn.abc.yf.SwiftUI-Widget"))

// 然后在后面取出数据

编写Widget

  1. 原理:开发者通过 SwiftUI 构建 Views,定义Timelines为 Views 提供对应时间所需的数据,当数据变化时,通过reload更新数据。TimelineProvider提供一组TimelineEntryReloadPolicy,用来后续刷新页面。
  2. 实现 Widget 的代码相对比较模版,可以从 Widget 的入口开始,缺什么补什么。

入口

@main
struct UserWidget: Widget {
    private let kind: String = "UserWidget"
    
    public var body: some WidgetConfiguration {
    
    }
}
不同尺寸.jpg

内容

不论是哪种配置,都需要提供以下内容。

Entry

渲染 Widget 所需的数据模型,需要遵守TimelineEntry协议。

struct Model: TimelineEntry {
    let date: Date
    // 显示的内容Model
}

Provider

遵守TimelineProvider协议,告诉 WidgetKit 何时渲染与刷新 Widget。需要实现以下 3 个方法:

struct Provider: TimelineProvider {
    // 占位视图,是一个标准的 SwiftUI View,当第一次展示或者发生错误时都会展示该 View。
    func placeholder(in context: Context) -> TimelineEntry {
    }
    
    // 编辑屏幕在左上角选择添加Widget、第一次展示时会调用该方法
    func getSnapshot(in context: Context, completion: @escaping (TimelineEntry) -> Void) {
    }

    // 进行数据的预处理,转化成Entry
    // 最后一定要调用 completion,进而刷新Widget
    func getTimeline(in context: Context, completion: @escaping (Timeline<TimelineEntry>) -> Void) {
    }
}
  1. getTimeline 是最重要的方法,后面的数据刷新都会在其中完成,所以可能会在其中完成最新的网络数据和本地数据的获取,然后转成 Model 以供使用。
  2. getTimeline 的方法里有一个 policy 参数,表示刷新的时机,可以选择.never(不刷新),.atEnd(Entry 显示完毕之后自动刷新) 或 .after(date)(到达某个特定时间后自动刷新)
  3. Widget 刷新的时间由系统统一决定(有时候设置了也不会自己刷新),如果需要强制刷新 Widget,可以在 App 中使用 WidgetCenter 来重新加载所有时间线:WidgetCenter.shared.reloadAllTimelines()

EntryView

屏幕上 Widget 显示的内容,可以针对不同尺寸的 Widget 设置不同的 View。

struct EntryView: View {
    
    var entry: Provider.Entry // 数据模型
    
    @Environment(\.widgetFamily) var family // 尺寸环境变量

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall:
            // 小尺寸
        case .systemMedium:
            // 中尺寸
        default:
            // 大尺寸
        }
    }
}
  1. Widget 能且只能使用 SwiftUI 构建界面。
  2. Widget 本质:一个随着时间线而更新的 SwiftUI View。

运行

交互

只能点击,点击会打开 App。也可以通过.widgetURL(myDeeplink)方法配置当 Widget 被点击时触发哪个 Deep Linking,也可以通过使用链接使 Widget 的不同部分触发不同的 Deep Linking(可以直接理解为 Widget 只是一个按钮,点按这个按钮会跳转到指定 URL 对应的页面)。

// 某个Widget内容
var body: some View {
    VStack {
        Link(destination: homeDeepLink) {
            Text("主页")
        }
        Link(destination: settingsDeepLink) {
            Text("设置")
        }
    }.widgetURL(myDeeplink)
}
@main
struct SwiftUIApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL(perform: { url in
                    print(url)
                })
        }
    }
}

源代码

Widget案例

上一篇下一篇

猜你喜欢

热点阅读