iOS 14 WidgetKit开发

Created By Ningyuan — 2020/09/27

首先,吐槽一下苹果,发布会上、官方文档上,就仅仅以Swift项目作基础,演示了常规流程,而我们这些OC项目或混编的,就得自己去踩坑研究。

iOS14带来了新的WidgetKit框架,其实就是iOS10那会出来的Extension加强版,最低支持版本为iOS14版本,开发语言为SwiftUI(OC党们都快开始学起来吧)。

  • Widget只有三种尺寸:systemSmall、systemMedium、systemLarge,下面是各尺寸

    Screen size (portrait)Small widgetMedium widgetLarge widget
    414x896 pt169x169 pt360x169 pt360x379 pt
    375x812 pt155x155 pt329x155 pt329x345 pt
    414x736 pt159x159 pt348x159 pt348x357 pt
    375x667 pt148x148 pt321x148 pt321x324 pt
    320x568 pt141x141 pt292x141 pt292x311 pt
  • 一个App可创建多个不同功能或展示样式Widget,每个Widget也可选择性提供上述的三种尺寸

  • Widget跟先前版本的Extension一样,是一个独立的程序,有自己的生命周期(称为Timeline),理论上可以说跟主项目“没有关系”

  • Widget仅能通过设定好的Timeline刷新数据,并不能实时更新

下面会结合Demo,来讲讲Widget开发过程中一些要点

1. 创建不可配置的Widget

这里就跳过创建主工程的步骤,直接开始创建Widget项目

新建一个Target,在Application Extension中找到Widget Extension

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这里有一个iOS交流群:[891488181]不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

image.png

点击下一步之后,这里填上Widget的ProductName,下面比较需要注意的就是Include Configuration Intent,这个是涉及Widget是使用静态的StaticConfiguration还是用户可配置的IntentConfiguration,区别之后再写,先不打勾,从常规的Widget不可配置模式开始

image.png

一般第一次创建的话,会出现这个弹窗,点击Activate即可

image.png

之后Xcode默认会创建一个时间显示的Widget,运行如下图所示

image.png

也可以手动选择其他尺寸的Widget,添加到桌面

image.png

2. 基础代码

下面来看看Xcode创建的基础代码,与Beta版稍有不同

Provider

提供刷新数据、控制数据的刷新相关方法,里面默认实现了三个方法

  • placeholder方法,给widget提供占位数据,异步返回一个TimelineEtry
func placeholder(in context: Context) -> SimpleEntry {
	SimpleEntry(date: Date())
}

展示上大致是这样的

image.png

  • getSnapshot方法提供Widget初次启动渲染时,或首次出现时所需的数据,一般可看作初始化数据
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
	let entry = SimpleEntry(date: Date())
	completion(entry)
}
  • getTimeline方法,方法回调接收Entry数组,可认为是数据源跟刷新间隔
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
	var entries: [SimpleEntry] = []

	// 添加5个entry,每个entry设定刷新时间为1小时
	let currentDate = Date()

	for hourOffset in 0 ..< 5 {
		let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
		let entry = SimpleEntry(date: entryDate)
		entries.append(entry)
	}

    // 这里是重点,传入Entry与你希望设置的刷新机制,.atEnd表示 等entries都显示完毕之后,再进行Timeline 的刷新
	let timeline = Timeline(entries: entries, policy: .atEnd)
	completion(timeline)
}
SimpleEntry

最简单的TimelineEntry,内部只声明了一个date变量,用来记录该entry的时间,官方解释是WidgetKit进行渲染widget的时间

struct SimpleEntry: TimelineEntry {
    let date: Date
}
EntryView

Widget的View部分,实现数据与View的绑定

struct WeatherWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}
Widget

Widget加载的入口,我们创建的Widget是不可配置的,所以这边会自动生成StaticConfiguration,专门对静态类型的Widget提供配置

@main
struct WeatherWidget: Widget {
    let kind: String = "WeatherWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            WeatherWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}
Preview

画布上的预览,经常出问题,感觉可以忽略~

struct WeatherWidget_Previews: PreviewProvider {
    static var previews: some View {
        WeatherWidgetEntryView(entry: SimpleEntry(date: Date()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

3. 适配不同尺寸

  • 将EntryView修改成如下方式,使用SwiftUI提供的@Environment,可根据不同的尺寸展示不同的布局
struct MyWidgetEntryView : View {

    @Environment(\.widgetFamily)
    var family: WidgetFamily
    var entry: Provider.Entry

    @ViewBuilder
    var body: some View {

        switch family {
        case .systemSmall:Text(entry.date, style: .time)
        case .systemMedium: Text(entry.date, style: .time)
        case .systemLarge: Text(entry.date, style: .time)
        default:Text(entry.date, style: .time)
        }
    }
}
  • 若想仅适配某个尺寸,可在Configuration之后添加.supportedFamilies,不加则表示默认适配三个尺寸

image.png

4. 为Widget添加可配置模式

接下来说说如何为Widget添加可配置模式,下面分两个方法进行介绍:新建项目添加、在静态配置基础上添加

(1)新建Widget时勾选Include Configuration Intent

新建的项目中会多一个xx.intentdefinition文件

image.png

代码中,IntentConfiguration替代了StaticConfiguration

@main
struct WeatherWidget: Widget {
    let kind: String = "WeatherWidget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            WeatherWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

Provider里面的getSnapshot、getTimeline方法多了一项configuration参数

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

接着在Parameters添加不同的参数,也可以在这里添加枚举、自定义类型等,具体还需各位自行摸索

image.png

再取title进行显示

image.png

配置完毕,运行,长按Widget,弹窗会多出一项配置Edit Widget,点击则可进入Widget编辑界面

image.png

进入配置界面,即可为配置

image.png

完成

image.png

(2)在静态配置的基础上修改为动态配置

这个方式处理起来比新建widget直接勾选intent要麻烦一些,首先我们在Widget项目内新建一个Intent Definition文件

image.png

文件名可以随意建,记住Targets需要勾选中Extension

image.png

创建完是这样子的,里面的Intents需要自己创建

image.png

+,选择New Intent,刚新建Intent后如下图所示

image.png

给Intent起个名字,这里我用Configuration,一会要用到;Custom Intent处,修改Category为View,选中Widgets,至于Configurable in Shortcuts、Suggestion则根据自己项目的需求来确定是否勾选吧,之后在Parameter添加一个content

image.png

接着要到Widget的swift文件去修改,添加import Intents,将Intent替换成刚刚的自定义配置,名称为ConfigurationIntent,上面起的配置名为Configuration,Xcode生成配置文件时,则会在末尾加上Intent

修改Provider
  • 遵循的协议,改成IntentTimelineProvider
  • getSnapshot、getTimeline也相应需要修改,建议先把原本的方法暂时注释,敲几个单词,由Xcode提示并回车,确认改好再删掉原代码

image.png

我猜99%的小伙伴在这步都会报错,请移步至常见问题Q&A

修改SimpleEntry

为其添加一个指定类型为ConfigurationIntent的data变量

image.png

修改Widget

原本的StaticConfiguration改为IntentConfiguration

image.png

需要Preview的,再按照Xcode提示修改即可

常见问题Q&A:

Q1:新建Widget时勾选Include Configuration Intent后,编译报错

有可能是项目前缀导致的,假设项目前缀是AB,那么Intent的类名也需要加上前缀,如:ABConfigurationIntent

image.png

Q2:手动新建Intent配置时报错

一般错误为:Type ‘Provider’ does not confirm to protocol ‘IntentTimelineProvider’Cannot find ‘ConfigurationIntent’ in Scope,这里的ConfigurationIntent对应IntentDefinition文件的自定义配置名称,各位注意对应

首先要说说这个ConfigurationIntent文件,通过查看新建Widget时直接勾选所生成的文件路径得知,这个文件并非直接存在我们的主项目,而是每次编译之后生成的

image.png

那既然编译的时候报错,就说明这个ConfigurationIntent文件没有跟我们的项目关联起来,跟没有import这个项目有点像,所以才会有Cannot find 'XXXIntent' in Scope的错误

在Extension的Target的BuildSettings中,搜索intent,有一个Intent Definition Complier - Code Generation的属性栏,可以看到Intent Class Generation Language这个配置,是Automatic

如果搜索不到,请确认项目中是否已添加了DefinitionIntent文件

image.png

可以先将其改成Swift,然后再编译一下,可以看到刚刚的缓存文件,生成了OC文件。。。难怪编译报错。那我们就找到问题了。

image.png

而这个Automatic,好像是不会根据项目所使用的的语言去自动生成相应的Intent配置文件,不知道是不是只会生成OC文件,目前已知组合:

OC项目,创建的是OC文件

混编项目,创建的是OC文件

纯Swift项目,创建的是OC文件

将这个选项选中,改成Swift,就可以解决这个问题

image.png

Q3:Q2中的报错,修改完毕,使用模拟器运行后点击Edit Widget没反应

这个好像是bug,需要先将模拟器内的应用删除,再重新运行

参考资料:

  • Widget介绍官方文档:https://developer.apple.com/documentation/widgetkit
  • Widget官方Demo:https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension
  • Widget追加可配置模式:https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget

作者:Linghit_iOS
链接:https://juejin.im/post/6885148185602785293

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值