了解Widget
官方学习Widget Demo
Widgets显示相关的、可浏览的内容,允许用户快速访问你的App以获取更多详细信息。你的App可以提供多种小组件,让用户专注于对他们最重要的信息。
为我们的App添加新的Widget必须要明白其本身的限制:
- 弱交互。与之前仅可放置在负一屏的小部件相比,新版 Widget 只能借由点击固定的区域,打开主 app 内的特定页面或跳转链接。无法在不开启 app 的情况下,完成各种交互。
- 展示框架限定。新的 widget 仅允许使用苹果在去年(2019 年)才推出的 SwiftUI 进行布局,而不是开发者更熟悉的 UIView 框架。SwiftUI 作为一种申明式的页面布局方式,会默认的符合 苹果标准的留白和间隙,从某种角度而言自由度没有 UIView 那么高。好处是视觉统一性会更好,苹果味儿更足,坏处是会有些雷同。
- 刷新频率。为了省电,所有 widget 的刷新频率也是由系统统一调度,会根据用户的使用频率来调节。
根据这些我们可以知道,使用Widget,主要就是用来展示非常重要的信息,作为快捷入口等。尽量简单,不要将Widget搞成很复杂的东西。
下边主要贴代码进行一下说明,不过关于Widget的创建这里就省略了,毕竟网上多得是,例如:创建一个Widget Extension
一、布局
- 关于ZStack、HStack、VStack的使用。做Widget当然要先了解SwiftUI的使用,而在SwiftUI中这个三种Stack是必不可少的。可参考:您一直在等待的完整SwiftUI文档(需要科学上网),另外一个就是要学会Spacer()的使用,这个很重要。
- 封装。因为你的小组件可能有各种尺寸的,且可能需要多个小组件。因此尽可能的封装到每一个小的按钮,以便重复使用。
- 尺寸自适应。前边的限制中提到SwiftUI有自身的标准,因此可能与你的设计稿不相符,而获取到当前Widget的大小就很重要了(不仅仅只获取当前小组件的大小类型)。
- 黑暗模式。基本现在大家都需要适配黑暗模式吧?那么Widget也需要,而且因为SwiftUI的原因,我们可以非常简单的实现它。比如你在Widget的Assets中Add New Asset时选择Color set,就可以看到白色和黑暗模式两种颜色,按照你的需要设置色值之后,在代码中使用名字就可以了,系统会根据当前的模式自动选择颜色。而图片的使用则需要代码去判断了,以上的几条内容都可以在下边的示例代码中找到。
// 代码示例
struct OneBtnForFeaturesOneView: View {
let btnTitle: String
let btnUrl: String
@Environment(\.colorScheme) var colorScheme
//view使用@ViewBuilder声明,因为它使用的view类型不同。
@ViewBuilder
var body: some View {
Link(destination: URL(string: btnUrl)!){
ZStack {
Color("color_btn_bg")
.cornerRadius(16.0)
VStack {
HStack(alignment: .top, content: {
Image(uiImage: UIImage(named: colorScheme == .dark ? "white_icon_search" : "black_icon_search")!)
.resizable()
.frame(width: 20, height: 20, alignment: .center)
Text(btnTitle)
.foregroundColor(Color("color_btn_title"))
.font(.system(size: 14, weight: .medium, design: .default))
})
.padding(.all, 16)
Spacer()
HStack (alignment: .bottom, content: {
Spacer()
Image(uiImage: UIImage(named: colorScheme == .dark ? "img_logo_white" : "img_logo")!)
.resizable()
.frame(width: 28, height: 28, alignment: .center)
})
.padding(.all, 16)
}
}
}
}
}
struct NewWidgetEntryView : View {
var entry: Provider.Entry
//针对不同尺寸的 Widget 设置不同的 View
@Environment(\.widgetFamily) var family // 尺寸环境变量
//view使用@ViewBuilder声明,因为它使用的view类型不同。
@ViewBuilder
var body: some View {
// 使用 GeometryReader 获取小组件的大小
GeometryReader{ geo in
ZStack {
Color("color_theme")
.frame(width: geo.size.width, height: geo.size.height, alignment: .center)
}
}
}
}
在以上代码中,我们就可以看到与布局相关的内容
- OneBtnForFeaturesOneView这个函数本身就是一个Button的封装,传入对应的Title和Url即可展示与跳转
- 在这两个函数中我们可以看到ZStack、VStack的使用
- colorScheme == .dark 即表示当前为黑暗模式,然后判断需要展示的图片
- Color(“color_btn_title”) 即自动根据当前模式展示我们设置好的颜色
- GeometryReader{ geo in }方法可以得到当前Widget的尺寸大小geo.size
二、数据请求
- 使用Swift的方式。
- 封装。
- 请求失败/默认数据的处理。在添加小组件的页面看到的样式就是加载的默认数据的样式,还有因为网络的问题请求失败的样式,将默认数据封装一下返回即可。
struct PosterData {
static func getTodayPoster(completion: @escaping (Result<Poster, Error>) -> Void) {
let url = URL(string: "https://nowapi.navoinfo.cn/get/now/today")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error==nil else{
completion(.failure(error!))
return
}
let poster=posterFromJson(fromData: data!)
completion(.success(poster))
}
task.resume()
}
static func posterFromJson(fromData data:Data) -> Poster {
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
guard let result = json["result"] as? [String: Any] else{
return Poster(author: "Now", content: "加载失败")
}
let author = result["author"] as! String
let content = result["celebrated"] as! String
let posterImage = result["poster_image"] as! String
//图片同步请求
var image: UIImage? = nil
if let imageData = try? Data(contentsOf: URL(string: posterImage)!) {
image = UIImage(data: imageData)
}
return Poster(author: author, content: content, posterImage: image)
}
}
以上为一个请求示例,接口可用,类似于带图片的每日名言。来自:iOS14 Widget小组件开发(Widget Extension)
三、多个小组件以及点击跳转事件
多小组件非常的简单,其最重要的代码就是如下:
@main
struct Widgets: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
NewWidget()
FeaturesFourWidget()
FeaturesTwoWidget()
}
}
添加 WidgetBundle{},然后将@main给它,在内部实现各自的一套小组件就可以了,而每套小组件都是小中大三种样式。
参考:iOS 14 小组件(2):WidgetExtension 自定义样式与交互
不过其中有一段是不对的。
iOS14 的Widget小组件可以使用AppDelegate中的 openURL 来打开。
而关键就在于,对于最小格式的systemSmall必须使用widgetURL,但是对于中大型的小组件,你的点击内容就需要包裹在Link中,记得封装就行,这样就很简单了。
主要参考的文档有:
Build Your First Widget in iOS 14 With WidgetKit(科学上网)
对了,还有个踩坑的,有问题可以看下。
iOS小组件Widget踩坑