iOS14小组件只有用swiftUI来实现,可能有不少小伙伴再接到到要实现小组件需求的时候可能还没接触过swift和swiftUI,所以我就贴具体的代码,然后比较详细的解惑一波。
在xcode的顶部菜单栏中选择 File->New->Target 然后如下图所示
在创建完target之后工程目录里面会出现一个TestWidget文件夹 里面有三个文件 info.plist和资源文件夹就不解释了。
1按小组件的运行流程一步步的往下说明 首先是主函数 对于主函数的理解 暂时只要知道 这里告诉了系统 小组件的标题、说明、以及哪个自定义view来显示就好了
@main // 通过这个注解来声明这个结构体是主函数入口
struct TestWidget: Widget {
// 这个小组件的唯一标识
let kind: String = "TestWidget"
// 小组件的具体内容
var body: some WidgetConfiguration {
// kind:小组件标识
// provider:传入一个实现了TimelineProvider协议的结构体 后面具体解释
// entry:集成TimelineEntry的一个实例对象 这个对象是什么时候实例化的怎么来的后面具体解释
StaticConfiguration(kind: kind, provider: Provider()) { entry in
// TestWidgetEntryView自定义的view 需要一个entry参数来初始化这个view 这个view就是小组件显示的view
TestWidgetEntryView(entry: entry)
}
// 小组件的显示标题
.configurationDisplayName("My Widget")
// 小组件的显示说明
.description("This is an example widget.")
// 小组件包含哪些样式 总共只有三种 2x2 2x4 4x4
.supportedFamilies([.systemSmall,.systemMedium,.systemLarge])
}
}
2 自定义view 这个view就是主函数那里声明的那个view
// 自定义view swfitUI的view声明都是struct结构体形式
struct TestWidgetEntryView : View {
// 这里定义了一个SimpleEntry类型的变量 相当于初始化这个view的时候 需要一个SimpleEntry参数
var entry: SimpleEntry
// 这段代码描述这个view里面的具体内容 如果看不懂的话 就需要了解了swiftUI基础
var body: some View {
Text(entry.date, style: .time)
}
}
3 声明SimpleEntry结构体 也就是自定义TestWidgetEntryView中需要的那个参数 要注意的是 这个结构体的声明要继承TimelineEntry 同时里面有个let date: Date属性 这个date的具体作用后面具体解释
// 声明一个继承TimelineEntry的结构体
struct SimpleEntry: TimelineEntry {
let date: Date
}
4 到了这里 我们知道了 哪个view用来显示小组件 哪个参数用来初始化小组件view 那么问题来了 在什么时候实例化SimpleEntry对象并创建view呢 就是靠主函数中声明的参数provider
// 声明一个遵守TimelineProvider协议的结构体 实现里面的3个方法
struct Provider: TimelineProvider {
// 在编辑小组件的时候 会调用这个方法 返回一个SimpleEntry实例 然后用这个实例去创建 前面说到的自定义view 然后显示那个view
// 因为编辑小组件的时候只是一个样式展示 数据不需要真实的 所以用固定的数据初始化一个SimpleEntry对象直接返回就好了 不需要网络请求等异步处理
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
// 这是第一次显示小组件的时候 调用这个方法 这里就是要显示真实的数据了 所以可以在代码块里 做网络请求的异步的操作
// 在completion回调中把实例好的entry对象传出去 之后系统就会用这个实例去初始化一个TestWidgetEntryView对象并显示
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
// 这个方法返回一个时间线 用来描述小组件的刷新规则
func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
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)
entries.append(entry)
}
// 读懂上面的代码我们知道entries里面有5个SimpleEntry元素 每个元素里面都有一个date属性
// 而这个date属性就是用来标识在哪个时间点 用这个SimpleEntry实例去初始化TestWidgetEntryView
// policy参数表示 刷新机制
// .atEnd:当entries数组里面所有的元素都刷新完毕了 会调用getTimeline方法获得一个新的时间线
// .after(date:Date):在哪个具体的date调用getTimeline方法获得一个新的时间线
// .never:当entries中的元素都刷新完毕了 就不在获取新的时间线
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
1 swiftUI的预览
// 这个view是用来预览swiftUI的 点击xcode顶部菜单的 Editor->Canvas会显示实时画布 如果不需要预览 这段代码可以直接删除
//struct TestWidget_Previews: PreviewProvider {
// static var previews: some View {
// TestWidgetEntryView(entry: SimpleEntry(date: Date()))
// .previewContext(WidgetPreviewContext(family: .systemSmall))
// }
//}
2 getTimeline中的时间线设置
官方文档中有提到 每个小组件的一天的刷新次数、频率是有限制的 ,可能为了避免所有APP不停的刷新会影响手机性能吧。而且刷新不会很及时 比如我自己设置的3分钟的间隔,一般刷新的时间是在3~4分钟之间,如果时间设置的是十几秒的话就完全不刷新,但是官方文档又有说测试环境不限制,但是不知道为什么频率高了刷新直接无效。
3 APP主体强制刷新小组件
虽然在小组件中可以按一定的逻辑,预先设置好TimeLine的刷新方案,不过在某些业务逻辑不可避免的要执行立刻刷新,比如天气小组件,当主体APP更新了当前定位数据,那么小组件就应该立即更新数据。实现方案其实也简单,只不过OC的项目可能涉及到OC中调用Swift的问题。不清楚如何在OC中调用Swfit的小伙伴可以参考下这篇文章 iOS_OC和Swift的相互调用_wzz_1992的博客-CSDN博客
if #available(iOS 14.0, *) {
// WidgetCenter.shared.reloadAllTimelines() // 刷新全部小组件
WidgetCenter.shared.reloadTimelines(ofKind: "WeatherWidget") // 指定刷新
} else {
// Fallback on earlier versions
}
4 断点调试
如果要调试小组件,记得运行target选择小组件,不过不知道我是哪里配置的有问题,模拟器可以执行断点,真机就不行。
由于是直接在公司项目上做开发,所以不方便附上代码Demo,不过网上的Demo不少,本文主要做一个iOS小组件运行流程及代码的解释。