iOS14新特性-WidgetKit小部件 通过桌面编辑添加,将小部件放在iOS主屏幕或macOS通知中心上,使用户可以随时访问应用中的内容。同时小部件也可以保持更新,因此用户始终可以一目了然地获得最新信息,同时点击区域可以Deep Link跳转到主APP任意界面中。
在2020年苹果发布会推出Widget之后,贝壳就第一时间做出了尝试, 期间苹果中国提供了很多支持与帮助,目前已在贝壳和链家APP上线。
小部件具有三种不同的大小(小,中和大),可以显示各种信息。用户可以个性化小部件以查看特定于其需求的详细信息,并以最适合他们的方式安排其小部件。
不同分辨率机型,三种卡片的尺寸也不同:
前期准备:
Xcode 12 及Bate版, iOS 14 及Bate版, 了解SwiftUI控件
首先,File->New->Target:
有两种配置可供选择:
例如,显示一般市场信息的股票市场Widget,或显示趋势标题的新闻Widget。
例如,一个天气Widget需要一个城市的邮政编码或邮政编码,或者一个包裹跟踪Widget需要一个跟踪号码。
下图中「Include Configuration Intent」复选框决定了Xcode使用哪种配置。选择Include Configuration Intent 表示支持用户配置;不需要,则不勾选。
对象解析
识别Widget的字符串。
如果包含多个widget后可作为唯一的标识符。
符合TimelineProvider的对象。
一个符合TimelineProvider的对象,它能产生一个时间线,告诉WidgetKit何时渲染Widget。
时间线包含一个你定义的自定义TimelineEntry类型。
时间线条目标识了你希望WidgetKit更新Widget内容的日期。
在自定义类型中包含你的Widget的视图需要渲染的属性。
一个 SwiftUI 视图,WidgetKit 用来在第一次渲染Widget。
占位符是您的Widget的通用表示,没有特定的配置或数据。
一个包含SwiftUI视图的封闭。
WidgetKit调用它来渲染Widget的内容,从提供者那里传递一个TimelineEntry参数。
函数解析
1) placeholder
占位视图,在数据加载前展示,在xcode12 bate3和bate4中有所有所变化:
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), model: LJWidgetModel.preview_widget)
completion(entry)
}
2) getSnapshot
快照,在添加组件库中展示
func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
let date = Calendar.current.date(byAdding: .hour, value:12, to: Date()) ?? Date()
LJWidgetAPI.loadData{ (model, error) in
guard let model = model else {
let timeline = Timeline(entries: [SimpleEntry(date: date, model: LJWidgetModel.preview_widget)], policy: .after(date))
completion(timeline)
return
}
let timeline = Timeline(entries: [SimpleEntry(date: date, model: model)], policy: .after(date))
completion(timeline)
}
}
3) getTimeline
时间轴,控制刷新时机
struct SimpleEntry: TimelineEntry {
let date: Date
let model: LJWidgetModel
}
4) SimpleEntry
数据模型,类似model
struct SimpleEntry: TimelineEntry { let date: Date let model: LJWidgetModel}
5) WidgetEntryView
主内容,展示区分小中大卡片 ,可根据family来区分
struct LJWidgetEntryView : View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
@ViewBuilder
var body: some View {
switch family {
case .systemSmall:
let small = entry.model.small
LJWidgetSmall(small)
.previewLayout(.sizeThatFits)
case .systemMedium:
let medium = entry.model.medium
LJWidgetMedium(medium)
.previewLayout(.sizeThatFits)
case .systemLarge:
let large = entry.model.large
LJWidgetLarge(large)
.previewLayout(.sizeThatFits)
@unknown default:
Text("unknown")
}
}
}
6) Widget
主界面控制器。kind为标识符
struct LJWidget: Widget {
let kind: String = "LJWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
LJWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
Widget_Previews struct LJWidgetLarge_Previews: PreviewProvider {
static var previews: some View {
Group {
LJWidgetLarge(LJWidgetLargeModel.preview)
.frame(width: 329.0, height: 345.0)
.previewLayout(.sizeThatFits)
.colorScheme(.light)
LJWidgetLarge(LJWidgetLargeModel.preview)
.frame(width: 329.0, height: 345.0)
.previewLayout(.sizeThatFits)
.colorScheme(.dark)
}
}
Preview 预览
界面开发是SwiftUI (Apple要求),可参考:https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension
如下图,本身维护一个时间轴,在创建时填充不同时间节点,当到达时间节点位置时,触发刷新。after会在消耗完时间点后,再次填充,保持循环运行。
一种是push notification推送来更新widget,另一种则是客户端内通过调用接口主动 reload。
如果原有老项目为OC项目,想要实现和Swift的相互调用,需用到桥接文件,另外针对引入Swift依赖库,会引起包体积增加,大小大概7~8M左右,可以在ipa包内查看:
创建xxx(工程名)-Bridging-Header头文件, 并在Build Setting -> Objective-C Bridging Header 设置其路径
这样我们在桥接文件内,通过 import “xxx.h”引用OC的组件库,就可以在Swift使用了。
桥接文件不需要手动创建,系统帮我创建好了,可以查看xxx-swift,在文件夹查找不到,但是引用头文件后,可以点击进入查看:
因为Widget为新的Target项目,和之前主项目是两个进程的关系,所以如果想要资源共享,比如登录状态值token, 网络配置等,就需要用到共享区域来实现通信。苹果提供的共享方式有:
方式一:APPGroup 方式
1) 配置好证书,是Gourp功能正常使用中,主APP和Widget保持统一key
当我们配置完以后,会在文件目录下多出来一个.entitlements的文件。
2)主APP写下数据
//Main App 通过TextField来向共享文件appGroup.txt中写入数据
- (void)textFieldDidEndEditing:(UITextField *)textField {
//获取App Group的共享目录
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.simon.app.test"];
NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"appGroup.txt"]; //写入文件
[textField.text writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];}
3)Widget 内读取App Group的共享目录
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.simon.app.test"];
NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"appGroup.txt"]; //读取文件
NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil]; self.shareLabel.text = str;
方式二:KeyChain Sharing
KeyChain可将用户信息加密存储在钥匙串中,保证用户信息的安全性;另外多个应用可通过keyChain共享用户信息。
1)同样我们需要配置keychan的key, 保持和主APP一致,省事的是少了证书的配置
2)通过对钥匙串的读写来操作 (一般由SAKeychain库管理)比较方便,注意一点:实现共享的只是在钥匙串的缓存数据,如果一旦加载到内存中,它的修改不受主APP的影响了。
问题1 . 选择Intent (widget 配置),会出现configtion查找不到
解决:
方法一: 若不使用Inetent, 在生成widget时不勾选即可,避免这类问题;
方法二:见https://developer.apple.com/forums/thread/653910 (在官网提出后,目前没有官方人员回答,不过有其他答案,未验证)
问题2 . Xcode 12 bate3 运行贝壳的widget未显示preview
解决:Build systems: 选择新的编译方式
问题3 . 由于WidgetKit使用一些Swift的新特性,所以版本需要修改成Swift5.0
问题4 . Swift 桥接OC组件库
解决:
1) 生成LJShell-Bridging-Header.h桥接文件,
2)在 Build setting中找到Objective-C Bridging Header 设置对应路径 $
(SRCROOT)/LianJiaShell/LJShell-Bridging-Header.h
3)将 Build Settings 中的 Defines Module 选项设置为 YES
问题5 . 引入OC组件库报查找不到
解决:在podfile中在LJWidget的target引入对应 pod xxx
问题6 . 使用Widget Preview功能
bate版本目前发现在OC工程下混编情况,prview无法使用,可以尝试新建个swift工程来编写swiftUI,使用preview功能
问题7 . Error: Multiple commands produce
解决:
方法一:
不使用New Build System,在File > Project/Workspace Settings中的Share Project/Workspace Settings 里build system 将New Build System(Default)切换成Legacy build system。
方法二:在 target -> Build phase > Copy Bundle Resource 中找到info.plist,移除
问题8 . dyld: Library not loaded:
dyld: Library not loaded: /System/Library/Frameworks/WidgetKit.framework/WidgetKit
Referenced from: /var/containers/Bundle/Application/EA42E025-6CFA-4C90-950E-50D28255B4DA/LJShell.app/LJShell
Reason: image not found
解决:https://developer.apple.com/forums/thread/126506
https://developer.apple.com/documentation/widgetkit
https://developer.apple.com/tutorials/swiftui/creating-and-combining-views
本文转载自公众号贝壳产品技术(ID:beikeTC)。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货