Listify Widget 开发笔记

2021/01/03

WidgetKit & Today Extension

Apple 在 iOS 8 的年代就上线过小插件,命名为 Today Extension,只能存在于 iOS 的通知中心里,随着 WidgetKit 出现,之前的 Today Extension 将会慢慢被淘汰了。 与之前的 Today Extension 相比,Widget 有这些不同。

  • 设计
    Today Extension 的背景颜色根据官方推荐都是透明的,方便与通知中心融入,尽管背景颜色可以调节成其他的颜色,还是很少看见有开发者将Today Extension 背景设置为其他的颜色
    Widget 的设计显得丰富一些,Apple 没有刻意规定Widget的背景颜色是什么样子,可以随意更换。Listify 使用了 暗色与白色两个背景颜色分别对应 iOS 的白天模式与夜间模式。

  • 尺寸
    Today Extension 拥有两种尺寸, compact 和 expanded,默认展示compact,用户点击右上角的箭头可以使 Today Extension 扩大为 expanded 模式,expanded模式下高度不固定,看开发者是如何规定的,但是有一个最大高度,超过最大高度后的视图会被截短。
    WidgetKit 只有大,中,小三个尺寸,每种尺寸的大小不可改变,但是开发者可以选择只开发其中一种尺寸。

  • 数据交换
    Today Extension 与 WidgetKit 一样,均没有办法直接读取主 App 的内容,比如 Listify 的 Widget 无法直接读取 App 的数据库取得数据,只能使用UserDefault 或者 CloudKit 读取数据,Listify 的 Today Extension 与 Widget 均使用 UserDefault 交换数据。

  • 交互
    WidgetKit 无法实现交互,点击 Widget 后只可以跳转到 App,开发者仅可知道用户点击了Widget 的某个区域并且根据这个信息显示不同的视图仅此而已。目前我发现能够实现Widget交互的App只有官方的Shortcut,应该是官方开了后门。
    Today Extension 只可以接受点击交互,但是会自动屏蔽掉滚动操作,如果你实现了一个 TableView,这个TableView 可以响应点击事件,但是无法响应滚动事件。并且 Today Extension 官方上是无法实现弹出键盘进行输入的。

  • 其他
    Widget 可以同时存在多个实例,开发者可以使用 Intent 对Widget显示内容进行配置,比如用户可以在添加 Listify Widget 时候选择 Widget 显示的是哪个清单的内容。
    Today Extension 只能存在一个,在通知中心里不可能同时存在两个一样的控件。

  • 开发
    视图层面,WidgetKit 只能使用 SwiftUI 开发,而Today Extension 使用 UIKit 开发。
    WidgetKit 只能支持基本视图,无法支持 TableView 这样的视图。

开发

开发这里我更推荐去看WWDC官方的文档和开发视频,这里只对相关的解决方案进行大致解释,不会出现过多可以直接使用的代码。
我在开发 Listify WidgetKit 的时候学习了一下相关的资源:
WidgetKit 的开发文档: Apple Developer Documentation
下面这个视频讲解了 SwiftUI 和 WidgetKit 的基础。
Build SwiftUI views for widgets - WWDC 2020 - Videos - Apple Developer
下面三个视频是实战型的
Widgets Code-along, part 1: The adventure begins - WWDC 2020 - Videos - Apple Developer
Widgets Code-along, part 2: Alternate timelines - WWDC 2020 - Videos - Apple Developer
Widgets Code-along, part 3: Advancing timelines - WWDC 2020 - Videos - Apple Developer

TimelineEntry

WidgetKit 开发依赖于一个 Timeline 的概念,顾名思义就是时间轴的意思,iOS 会定期向你的 Widget 询问一个时间轴,这个时间轴就是你的 Widget 未来一段时间展示的样子,比如天气 App 的 Widget,在操作系统向 Widget 询问时间轴的时候,天气 App 应该要返回未来5个小时,每个小时的天气数据。TimelineEntry就是时间轴中一项的抽象。

以下就是 Listify Widget使用的 TimelineEntry,每个TimelineEntry必须包含一个date 日期,用于确定这个 Entry 的展示时间,ListEntity 是 Listify 中用于抽象一个清单的模型。

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: SelectListIntent
    let listEntity: ListEntity
}

IntentTimelineProvider

在新建一个 WidgetKit 项目之后, Xcode 首先会生成一个 IntentTimelineProvider,IntentTimelineProvider 用于给小插件渲染提供数据,这个struct实现了三个函数,这三个函数也同样非常直观。

  • placeholder
    用于给Widget提供一个假数据用作预览,比如在用户选择小插件的时候,这个函数就会被调用
  • getSnapshot
    这个函数就是小插件内容提供的主要函数,当iOS需要刷新小插件的时候,这个函数会被调用,开发者需要生成一个Entry并且回调。
  • getTimeline
    Widget 不是实时刷新的,开发者需要告诉iOS什么时候需要刷新 Widget 视图,这个函数就是用于告诉系统什么时候需要刷新小插件。开发者需要在这个函数里生成未来会用到到一系列Entry并生成一个 Timeline 对象然后回调,iOS会通过读取这些 Entry 中的 date 来定期刷新小插件。

当然iOS并不会确保在你编写的时间准确刷新,你生成的Entry中的date属性只是用来告诉 iOS 你的小插件需要刷新,至于 iOS 时候会在那个时刻调用你的 getSnapshot,取决于操作系统了。本来Listify 的小号 Widget 上会展示当天的日期,后来由于我无法保证新的一天00:00的时候让 iOS 刷新我的小插件,索性不去展示当天的日期。

WidgetEntryView

建立工程后也会默认实现一个 struct WidgetEntryView,继承自 SwiftUI 中的View,其中包含一个 entry (就是上面提到的 TimelineEntry)以及一个 叫做body的View,开发者需要复写body来返回相应的视图,下面是 Listify 的 Widget 渲染代码,当entry可用时候,根据 Widget 的大小生成了三个不同的控件 SmallWidgetView,MediumWidgetView,LargeWidgetView,这三个视图是我自己实现的,分别对应大中小三个尺寸。

struct Listify_WidgetEntryView : View {
    var entry: Provider.Entry?
    @Environment(\.widgetFamily) var family
    @ViewBuilder
    var body: some View {
        ZStack{
            Color("WidgetBackground")
            if let e = entry {
                switch family {
                case .systemSmall:
                    SmallWidgetView(entry: e)
                case .systemMedium:
                    MediumWidgetView(entry: e)
                case .systemLarge:
                    LargeWidgetView(entry: e)
                @unknown default:
                    FailureStateView()
                }
            }else{
                FailureStateView()
            }
        }       
    }
}

Intent

Listify 小插件有一个功能是列表选择,当用户添加 Listify Widget 的时候,Listify 会询问用户添加的 Widget 要显示哪一个清单,这个时候就需要用到 Intent 了,Intent 相关的知识 SiriKit 中也会用到。

这里我同样推荐 WWDC 的官方视频 Add configuration and intelligence to your widgets - WWDC 2020 - Videos - Apple Developer 我就是根据这个视频开发的。

Copyright © 2015-2021 MikeTech.it. All rights reserved.

Developed By Yigang Zhou