iOS14小组件(Widget)实战三
前言
前面两章我们了解小组件的创建和小组件内部的工作机制,现在我们自己动手开发我们小组件,希望通过这一节大家都能很好的理解和掌握小组件的使用。项目demo我已经上传到码云,项目链接小组件demo
重新定义@main入口
- 创建一个文件MyMainWidget
- 去掉系统默认的小组件@main入口
- MyMainWidget中添加自定义入口代码
- 重新运行主程序,小组件能正常显示说明@main入口替换成功了
新建自定义小组件
- 创建一个文件MyDIYWidget
- 创建Widget的方式有两种
- 一种是动态配置创建,系统默认提供的就是这种!
- 第二种是静态配置创建,今天我们就先用静态配置,后续再回过头来讨论两者创建方式的区别
- 我们用静态初始化配置创建一个Widget,然后定义MyDIYWidgetProvider实现TimelineProvider协议,方法中用到的数据模型MyDIYWidgetEntry需要遵循TimelineEntry协议,编写后的代码如图,同时附上代码
import SwiftUI
import WidgetKit
struct MyDIYWidgetEntry: TimelineEntry {
let date: Date
}
struct MyDIYWidgetProvider: TimelineProvider {
// 默认占位数据
func placeholder(in context: Context) -> MyDIYWidgetEntry {
return MyDIYWidgetEntry(date: Date())
}
// 快照
func getSnapshot(in context: Context, completion: @escaping (MyDIYWidgetEntry) -> Void) {
completion(MyDIYWidgetEntry(date: Date()))
}
// 根据时间线刷新,这里可以进行网络请求
func getTimeline(in context: Context, completion: @escaping (Timeline<MyDIYWidgetEntry>) -> Void) {
var entries: [MyDIYWidgetEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 60 {
let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!
let entry = MyDIYWidgetEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
// 用于显示的UI
struct MyDIYWidgetEntryView: View {
// 接收数据
var entry: MyDIYWidgetEntry
// UI展示
var body: some View {
Text("我的小组件\(entry.date)")
}
}
struct MyDIYWidget: Widget {
let kind = "MyDIYWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: MyDIYWidgetProvider()) { entry in
MyDIYWidgetEntryView(entry: entry)
}
.configurationDisplayName("自定义小组件")
.description("输入你想描述的说明文案")
}
}
如果你的代码跟我的写的一样,那么把刚才我们编写的MyDIYWidget小组件添加到@main入口处,再到桌面上添加上我们自定义的小组件就可以看到了
运行项目后,添加我们的小组件,变成如下
卡住不动,因为我们只创建了60个数据,系统根据每秒刷新一个数据后,一分钟过后就没有数据了。我们设置的刷新机制是.atEnd,也就是说没有数据系统就会调用getTimeline方法重新填充数据,但是由于1分钟刷新一次太频繁,苹果会自动屏蔽掉,按照网上的说法是小于5分钟的刷新就会屏蔽掉,这里我也没探讨过,只知道这里频繁刷新会被屏蔽掉。
需求分析
我们先把我们需要做的样式先摆出来,让大家看看是个什么样式;我们要做四个item,横向排列,点击任意一个item跳转到指定的页面
这样分析我们的数据模型一个字段是title, 另外一个就是跳转链接urlScheme。
网络请求
- 创建一个网络请求类
添加网络请求方法,数据解析
import SwiftUI

// 需求分析中定义的两个字段
struct MyDIYWidgetModel {
var title: String
var urlScheme: String
}
struct MyNetWorkHelper {
// 请求url(这里改成你自己的url地址,我这里是模拟网络请求,就随便写一个)
static let url_str = "https:www.baidu.com"
// 网络请求方法
static func request(completion: @escaping (Array<MyDIYWidgetModel>) -> Void) {
let url = URL.init(string: url_str)
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
// 这里模拟网络请求造的假数据
let models = modelFromJson()
completion(models)
}
task.resume()
}
// 数据序列化(解析)
fileprivate static func modelFromJson() -> Array<MyDIYWidgetModel> {
var models: [MyDIYWidgetModel] = []
let data = [["title": "快捷选车", "urlScheme": "a"],
["title": "销量排行", "urlScheme": "b"],
["title": "厂家直购", "urlScheme": "c"],
["title": "汽车保养", "urlScheme": "a"]]
for item in data {
var title = ""
var urlScheme = ""
if let n = item["title"] {
title = n
}
if let n = item["urlScheme"] {
urlScheme = n
}
let model = MyDIYWidgetModel(title: title, urlScheme: urlScheme)
models.append(model)
}
return models
}
}
- 给自定义数据模型添加models: [MyDIYWidgetModel]字段,用于存储网络获取到的数据
- getTimeline里用网络请求类请求数据,并把数据添加到entries,再创建时间线回调
- 同时需要更新placeholder和getSnapshot方法里MyDIYWidgetEntry的创建方式,因为多了个modes的字段,总的代码如下
struct MyDIYWidgetEntry: TimelineEntry {
let date: Date
let models: [MyDIYWidgetModel]
}
struct MyDIYWidgetProvider: TimelineProvider {
// 默认占位数据
func placeholder(in context: Context) -> MyDIYWidgetEntry {
var models: [MyDIYWidgetModel] = []
let data = [["title": "快捷选车", "urlScheme": "a"],
["title": "销量排行", "urlScheme": "b"],
["title": "厂家直购", "urlScheme": "c"],
["title": "汽车保养", "urlScheme": "a"]]
for item in data {
var title = ""
var urlScheme = ""
if let n = item["title"] {
title = n
}
if let n = item["urlScheme"] {
urlScheme = n
}
let model = MyDIYWidgetModel(title: title, urlScheme: urlScheme)
models.append(model)
}
return MyDIYWidgetEntry.init(date: Date(), models: models)
}
// 快照
func getSnapshot(in context: Context, completion: @escaping (MyDIYWidgetEntry) -> Void) {
var models: [MyDIYWidgetModel] = []
let data = [["title": "快捷选车", "urlScheme": "a"],
["title": "销量排行", "urlScheme": "b"],
["title": "厂家直购", "urlScheme": "c"],
["title": "汽车保养", "urlScheme": "a"]]
for item in data {
var title = ""
var urlScheme = ""
if let n = item["title"] {
title = n
}
if let n = item["urlScheme"] {
urlScheme = n
}
let model = MyDIYWidgetModel(title: title, urlScheme: urlScheme)
models.append(model)
}
completion(MyDIYWidgetEntry.init(date: Date(), models: models))
}
// 根据时间线刷新,这里可以进行网络请求
func getTimeline(in context: Context, completion: @escaping (Timeline<MyDIYWidgetEntry>) -> Void) {
var entries: [MyDIYWidgetEntry] = []
// 网络请求数据
MyNetWorkHelper.request { (models) in
let entry = MyDIYWidgetEntry.init(date: Date(), models: models)
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .never)
completion(timeline)
}
}
}
// 用于显示的UI
struct MyDIYWidgetEntryView: View {
// 接收数据
var entry: MyDIYWidgetEntry
// UI展示
var body: some View {
Text("我的小组件\(entry.date)")
}
}
struct MyDIYWidget: Widget {
let kind = "MyDIYWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: MyDIYWidgetProvider()) { entry in
MyDIYWidgetEntryView(entry: entry)
}
.configurationDisplayName("自定义小组件")
.description("输入你想描述的说明文案")
}
}
页面展示
- 创建UI用于展示获取到的数据,这里直接修改MyDIYWidgetEntryView处的代码,并且添加跳转链接
// 用于显示的UI
struct MyDIYWidgetEntryView: View {
// 接收数据
var entry: MyDIYWidgetEntry
// UI展示
var body: some View {
GeometryReader{ geo in
HStack(spacing: 0) {
ForEach(entry.models, id: \.title) { model in
let url = URL(string: model.urlScheme)
// 添加跳转链接url
Link(destination: url!) {
Text(model.title)
.frame(width: geo.size.width / CGFloat(entry.models.count), height: geo.size.height)
.font(.system(size: 11, weight: .regular, design: .default))
.foregroundColor(.black)
.lineLimit(1)
}
}
}
}
}
}
- 删除SceneDelegate,设置AppDelegate的根视图为ViewController,添加openURL的方法,代码如下
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
ViewController *vc = [ViewController new];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:vc];
[self.window makeKeyAndVisible];
return YES;
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSLog(@"%@", url.absoluteURL);
return YES;
}
这里就可以根据url里边接收到的a,b,c,d来确定跳转到不同的页面了