关于ios:网易云音乐-iOS-14-小组件实战手册

13次阅读

共计 14107 个字符,预计需要花费 36 分钟才能阅读完成。


图片起源: https://unsplash.com/photos/a…

本文作者:闫冰

前言

苹果在往年的 WWDC20 上公布了小组件(WidgetKit),反对在 iOS、iPadOS 主屏幕展现动静信息和个性化内容。加上 iOS 零碎利用抽屉的退出,苹果对一贯激进主屏幕大动干戈,导致用户也对小组件十分期待。但小组件的运行限度很多,如何在无限的机制上怎么做好用户体验就成为须要实现的挑战。

小组件简述

小组件能够在主屏幕上实现内容展现和性能跳转。
零碎会向小组件获取工夫线,依据以后工夫对工夫线上的数据进行展现。点击正在展现的视觉元素能够跳转到 APP 内,实现对应的性能。
云音乐的小组件成果如下:

开发思路浅谈


首先须要明确的是小组件是一个独立于 App 环境 (即 App Extension),小组件的生命周期 / 存储空间 / 运行过程都和 App 不同。所以咱们须要引入这个环境下的一些基础设施,比方网络通信框架,图片缓存框架,数据长久化框架等。
小组件自身的生命周期是一个很有意思的点。直白的来讲小组件的生命周期是和桌面过程统一的,但这不意味着小组件能随时的执行代码实现业务。小组件应用 Timeline 定义好的数据来渲染视图,咱们的代码只能在刷新 Timeline (getTimeline)和创立快照(getSnapshot)时执行。一般而言,在刷新 Timeline 时获取网络数据,在创立快照时渲染适合的视图。
大多数状况下都须要应用数据来驱动视图展现。这个数据能够通过网络申请取得,也能够利用 App Groups 的共享机制从 App 中获取。
在刷新 Time Line 时获取到数据后,即可依照业务需要合成 Timeline。Timeline 是一个以 TimelineEntry 为元素的数组。TimelineEntry 蕴含一个 date 的工夫对象,用以告知零碎在何时应用此对象来创立小组件的快照。也能够继承 TimelineEntry,退出业务所须要的数据模型或其余信息。
为了使小组件展现视图,须要用 SwiftUI 来实现对小组件的布局和款式搭建。在上面会介绍如何实现布局和款式。
在用户点击小组件后,会关上 App,并调用 AppDelegateopenURL: 办法。咱们须要在 openURL: 中解决这个事件,使用户间接跳转至所需的页面或调用某个性能。
最初,如果须要凋谢给用户小组件的自定义选项,则应用 Intents 框架,事后定义好数据结构,并在用户编辑小组件提供数据,零碎会依据数据来绘制界面。用户抉择的自定义数据都会在刷新 Time Line (getTimeline)和创立快照(getSnapshot)时以参数的模式提供进去,之后依据不同的自定义数据执行不同的业务逻辑即可。

App Extension

如果你曾经有了 App Extension 的开发教训,能够略过这个章节。
依照苹果的说法:App Extension 能够将自定义性能和内容扩大到应用程序之外,并在用户与其余应用程序或零碎交互时向用户提供。例如,您的利用能够在主屏幕上显示为小部件。也就是说小组件是一种 App Extension,小组件的开发工作,根本都在 App Extension 的环境中。
App 和 App Extension 有什么关系?
实质上是两个独立的程序,你的主程序既不能够拜访 App Extension 的代码,也不能够拜访其存储空间,这完完全全就是两个过程、两个程序。App Extension 依赖你的 App 本体作为载体,如果将 App 卸载,那么 App Extension 也不会存在于零碎中了。而且 App Extension 的生命周期大多数都作用于特定的畛域,依据用户触发的事件由系统控制来治理。

创立 App Extension 和配置文件

上面简述一下如何创立小组件的 App Extension 并配置证书环境。
在 Xcode 中新增一个 Widget Extension(门路如下:File-New-Target-iOS 选项卡 -Widget Extension)。如果你须要小组件的自定义性能,则不要遗记勾选 Include Configuration Intent


在 Widget Extension 的 Target 中增加 App Groups,并放弃和主程序雷同的 App Group ID。如果主程序中还没有 App Groups,则须要这个时候同时减少主 App 的 App Groups,并定义好 Group ID。

如果你的开发者账号登录在 Xcode 中,那么此时应用程序的配置文件和 App ID 等配置都会是正确的。如果你没有登录 Xcode 中,则须要返回苹果开发者核心,手动创立 App Extension 的 App ID 和配置文件。此时不要遗记在 App ID 中配置 App Groups。

App Groups 数据通信

因为 App 和 App Extension 是不能间接通信的,所以须要共享信息时,须要应用 App Groups 来进行通信。App Groups 有两种共享数据的形式,NSUserDefaultsNSFileManager

NSUserDefaults 共享数据

应用 NSUserDefaults 的 initWithSuiteName: 初始化实例。suitename传入之前定义好的 App GroupID。

- (instancetype)initWithSuiteName:(NSString *)suitename;

之后即可应用 NSUserDefaults 的实例的存取方法来贮存和获取共享数据了。比方咱们须要和小组件共享以后的用户信息,则能够如下操作。

// 应用 Groups ID 初始化一个供 App Groups 应用的 NSUserDefaults 对象
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.company.appGroupName"];
// 写入数据
[userDefaults setValue:@"123456789" forKey:@"userID"];
// 读取数据
NSString *userIDStr = [userDefaults valueForKey:@"userID"];

NSFileManager 共享数据

应用 NSFileManager 的 containerURLForSecurityApplicationGroupIdentifier: 获取 App Group 共享的贮存空间地址,即可进行文件的存取操作。

- (NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier;

SwiftUI 构建组件

应该是基于耗电量等方面的考量,苹果要求小组件只能应用 SwiftUI,也不能通过 UIViewRepresentable 桥接 UIKit 来应用。
小组件的交互方式简略,只有点击,且视图较小。开发所须要的 SwiftUI 常识比较简单,正当构建出小组件视图即可,一般而言不会波及到数据绑定等操作。
这个章节次要介绍如何应用 SwiftUI 构建小组件,我会假如读者曾经有了对 SwiftUI 的基础知识。如果你对 SwiftUI 还较为生疏,能够通过参考资料中的两个视频教程来增进理解(【十五分钟搞懂 SwiftUI】布局篇 /【十五分钟搞懂 SwiftUI】款式篇)。也能够查阅开发文档或者 WWDC19/20 的相干专题获取 SwiftUI 更多常识。

应用 SwiftUI 实现小组件视图

上面应用一个简略的开发例子,来帮忙大家应用 SwiftUI 开发小组件视图。
首先看小组件的视觉稿:

简略剖析一下视觉稿中的视图元素:

  1. 铺满全副的背景图片(Image)
  2. 从底部至上的彩色突变(LinearGradient)
  3. 右上角的云音乐 Logo(Image)
  4. 小组件两头的日历图标(Image)
  5. 日历图标上面两行文字(Text)

通过剖析,不难发现要实现视觉稿的成果,须要应用 TextImageLinearGradient 三个组件即可实现。
将视觉元素 1/2/3 归为背景视图,不便其余组件复用。随后把组件内容类型相干的 4/5 归为前景视图。

先来实现背景视图:

struct WidgetSmallBackgroundView: View {
 
 // 底部遮罩的占比为整体高度的 40%
 var contianerRatio : CGFloat = 0.4
 
 // 背景图片
 var backgroundImage : Image = Image("backgroundImageName")
 
 // 从上到下的突变色彩
 let gradientTopColor = Color(hex:0x000000, alpha: 0)
 let gradientBottomColor = Color(hex:0x000000, alpha: 0.35)
 
 // 遮罩视图 简略封装 使代码更为直观
 func gradientView() -> LinearGradient {return LinearGradient(gradient: Gradient(colors: [gradientTopColor, gradientBottomColor]), startPoint: .top, endPoint: .bottom)
 }
 
 var body: some View {
 // 应用 GeometryReader 获取小组件的大小
 GeometryReader{ geo in
 // 应用 ZStack 叠放 logo 图标 和 底部遮罩
 ZStack{
 // 构建 logo 图标, 应用 frame 确定图标大小, 应用 position 定位图标地位
 Image("icon_logo")
 .resizable()
 .scaledToFill()
 .frame(width: 20, height: 20)
 .position(x: geo.size.width - (20/2) - 10 , y : (20/2) + 10)
 .ignoresSafeArea(.all)
 // 构建 遮罩视图, 应用 frame 确定遮罩大小, 应用 position 定位遮罩地位
 gradientView()
 .frame(width: geo.size.width, height: geo.size.height * CGFloat(contianerRatio))
 .position(x: geo.size.width / 2.0, y: geo.size.height * (1 - CGFloat(contianerRatio / 2.0)))
 }
 .frame(width: geo.size.width, height: geo.size.height)
 // 增加上笼罩底部的背景图片
 .background(backgroundImage
 .resizable()
 .scaledToFill())
 }
 }
}

背景视图实现的成果如下图:

接下来把背景视图搁置在小组件的视图中,并实现两头的图标和文案视图,这样就实现了整个组件的视觉构建过程:

struct WidgetSmallView : View {
 
 // 设置大图标的宽高为小组件高度的 40%
 func bigIconWidgetHeight(viewHeight:CGFloat) -> CGFloat {return viewHeight * 0.4}
 
 var body: some View {
 
 GeometryReader{ geo in
 VStack(alignment: .center, spacing : 2){Image("iconImageName")
 .resizable()
 .scaledToFill()
 .frame(width: bigIconWidgetHeight(viewHeight: geo.size.height), height: bigIconWidgetHeight(viewHeight: geo.size.height))
 
 Text("每日举荐")
 .foregroundColor(.white)
 .font(.system(size: 15))
 .fontWeight(.medium)
 .lineLimit(1)
 .frame(height: 21)
 
 Text("为你带来每日惊喜")
 .foregroundColor(.white)
 .font(.system(size: 13))
 .fontWeight(.regular)
 .opacity(0.8)
 .lineLimit(1)
 .frame(height: 18)
 }
 // 减少 padding 使 Text 过长时不会涉及小组件边框
 .padding(EdgeInsets(top: 0, leading: 14, bottom: 0, trailing: 14))
 .frame(width: geo.size.width, height: geo.size.height, alignment: .center)
 // 设置背景视图
 .background(WidgetSmallBackgroundView())
 }
 }
}

通过上述简略的例子能够发现,在惯例的流式布局中,应用 VStackHStack 即可达到布局成果。而如果想要实现例子中 logo 图标的成果的话,就须要应用 position/offset 来扭转定位坐标来达成指标了。

对于 Link 视图的一点补充

Link 是一个能够点击的视图,如果可能的话,它将在关联的应用程序中关上,否则将在用户的默认 Web 浏览器中关上。中 / 大尺寸的小组件能够用它来给点击区域设定不同的跳转参数。因为下面的例子是小尺寸的组件,不能应用 Link 来辨别跳转,所以在这里补充一下。

Link("View Our Terms of Service", destination: URL(string: "https://www.example.com/TOS.html")!)

获取数据

网络申请

小组件中能够应用 URLSession,所以网络申请和 App 中基本一致,在此就不赘述了。
须要留神的点:

  1. 应用第三方框架须要引入小组件所在的 Target。
  2. 在刷新 Timeline 时调用网络申请。
  3. 如果须要和 App 共享信息,则须要通过 App Group 的形式存取。

图片的加载缓存

图片缓存则和 App 中不同。目前在 SwiftUI 中的 Image 视图不反对传入 URL 加载网络图片。也不能应用异步获取网络图片的 Data的形式实现网络图片的加载。
只能通过刷新 Timeline,调用网络申请实现后,再去获取 Timeline 上所有的网络图片的 data

 func getTimeline(for configuration: Intent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
 // 发动网络申请
 widgetManager.requestAPI(family : context.family, configuration: configuration) { widgetResponse, date in
 // 在接口回调中生成 Timeline entry
 let entry = WidgetEntry(date: Date(), configuration: configuration, response: widgetResponse, family : context.family)
 // 解析出 Timeline entry 所须要的网络图片
 let urls = entry.urlsNeedDownload()
 // 查问本地缓存以及下载网络图片
 WidgetImageManager().getImages(urls: urls) {let entries = [entry]
 let timeline = Timeline(entries: entries, policy: .after(date))
 completion(timeline)
 }
 }
 }

getImages 办法中,咱们须要保护一个队列去顺次查问本地缓存以及在缓存未命中时下载网络图片。

 public func getImages(urls : [String] , complition : @escaping () -> ()){
 
 // 创立目录
 WidgetImageManager.createImageSaveDirIfNeeded()
 
 // 去重
 let urlSet = Set(urls)
 let urlArr = Array(urlSet)
 
 self.complition = complition
 
 self.queue = OperationQueue.main
 self.queue?.maxConcurrentOperationCount = 2
 let finishBlock = BlockOperation {self.complition?()
 }
 
 for url in urlArr {
 let op = SwiftOperation { finish in
 self.getImage(url: url) {finish(true)
 }
 }
 
 finishBlock.addDependency(op)
 self.queue?.addOperation(op)
 }
 
 self.queue?.addOperation(finishBlock)
 }
 
 public func getImage(url : String , complition : @escaping () -> ()) -> Void {let path = WidgetImageManager.pathFromUrl(url: url)
 if FileManager.default.fileExists(atPath: path) {complition()
 return
 }
 
 let safeUrl = WidgetImageManager.filterUrl(url: url)
 WidgetHttpClient.shareInstance.download(url: safeUrl, dstPath: path) {(result) in
 complition()}
 }

预览状态的数据获取

在用户增加小组件时,会在预览界面看到小组件的视图。此时,零碎会触发小组件的 placeholder 办法,咱们须要在这个办法中返回一个 Timeline,用以渲染出预览视图。
为了保障用户的体验,须要为接口调用筹备一份本地的兜底数据,确保用户能够在预览界面看到实在的视图,尽量不要展现无数据的骨架屏。

TimeLine

小组件的内容变动都依赖于 Timeline。小组件实质上是 Timeline 驱动的一连串动态视图。

了解 TimeLine

在后面提到过,Timeline 是一个以 TimelineEntry 为元素的数组。TimelineEntry 蕴含一个 date 的工夫对象,用以告知零碎在何时应用此对象来创立小组件的快照。也能够继承 TimelineEntry,退出业务所须要的数据模型或其余信息。

在生成新的 Timeline 之前,零碎会始终应用上一次生成的 Timeline 来展现数据。
如果 Timeline 数组外面只有一个 entry,那么视图就是变化无穷的。如果须要小组件随着工夫产生变动,能够在 Timeline 中生成多个 entry 并赋予他们适合的工夫,零碎就会在指定的工夫应用 entry 来驱动视图。

Reload

所谓的小组件刷新,其实是刷新了 Timeline,导致由 Timeline 数据驱动的小组件视图产生了扭转。
刷新办法分为两种:

  1. System reloads
  2. App-driven reloads

System reloads

由零碎发动的 Timeline 刷新。零碎决定每个不同的 Timeline 的 System Reloads 的频次。超过频次的刷新申请将不会失效。高频应用的小组件能够取得更多的刷新频次。
ReloadPolicy:
在生成 Timeline 时,咱们能够定义一个 ReloadPolicy,通知零碎更新 Timeline 的机会。ReloadPolicy 有三种模式:

  • atEnd

    • 在 Timeline 提供的所有 entry 显示结束后刷新,也就是说只有还有没有显示的 entry 在就不会刷新以后工夫线

  • after(date)

    • date 是指定的下次刷新的工夫,零碎会在这个工夫对 Timeline 进行刷新。

  • never

    • ReloadPolicy 永远不会刷新 Timeline,最初一个 entry 也展现结束之后 小组件就会始终放弃那个 entry 的显示内容

Timeline Reload 的机会是由零碎对立管制的,而为了保障性能,零碎会依据各个 Reload 申请的重要等级来决定在某一时刻是否依照 APP 要求的刷新机会来刷新 Timeline。因而如果过于频繁的申请刷新 Timeline,很有可能会被零碎限度从而不能达到现实的刷新成果。换句话说,下面所说的 atEnd, after(date) 中定义的刷新 Timeline 的机会能够看作刷新 Timeline 的最早工夫,而依据零碎的安顿,这些机会可能会被延后。

App-driven reloads

由 App 触发小组件 Timeline 的刷新。当 App 在后盾时,后盾推送能够触发 reload;当 App 在前台时,通过 WidgetCenter 能够被动触发 reload。

调用 WidgetCenter 能够依据 kind 标识符刷新局部小组件,也能够刷新全副小组件。

/// Reloads the timelines for all widgets of a particular kind.
/// - Parameter kind: A string that identifies the widget and matches the
///   value you used when you created the widget's configuration.
public func reloadTimelines(ofKind kind: String)
/// Reloads the timelines for all configured widgets belonging to the
/// containing app.
public func reloadAllTimelines()

点击落地

用户点击了小组件上的内容或性能入口时,须要在关上 App 后正确响应用户的需要,出现给用户相应的内容或性能。
这须要分两局部来做,首先在小组件中对不同的点击区域定义不同的参数,之后在 App 的 openURL: 中依据不同的参数出现不同的界面。

辨别不同的点击区域

想要对于不同的区域定义不同的参数,须要把 widgetURL 和 Link 联合应用。

widgetURL

widgetURL 作用范畴是整个小组件,且一个小组件上只能有一个 widgetURL。多增加的 widgetURL 参数是不会失效的。

代码如下:

struct WidgetLargeView : View {
 var body: some View {
 GeometryReader{ geo in
 WidgetLargeTopView()
 ...
 }
 .widgetURL(URL(string: "jump://Large")!)
 }
}

Link

Link 作用范畴是 Link 组件的理论大小。能够增加多个 Link,在数量上是没有限度的。须要留神的是小组件的 systemSmall 类型下,不能应用 Link API。

代码如下:

struct WidgetLargeView : View {
 var body: some View {
 GeometryReader{ geo in
 WidgetLargeTopView()
 Link(destination: URL(string: "自定义的 Scheme://Unit")!) {WidgetLargeUnitView()
 }
 ...
 }
 .widgetURL(URL(string: "自定义的 Scheme://Large")!)
 }
}

URL Schemes

URL Schemes 是小组件跳转到 App 的桥梁,也是 App 之间互相跳转的通道。个别的开发者对其应该并不生疏。
注册自定义 URL Scheme 非常简单,通过 info.plist –> URL Types –> item0 –> URL Schemes –> 自定义的 Scheme 来设置。
之后,在小组件中,即可通过 自定义的 Scheme:// 拼接成的 URL 对象来关上本人的 App,在 :// 前面能够减少参数来表明所须要性能或内容。
须要留神:减少参数时,呈现的中文要进行本义。这里能够应用 NSURLComponentsNSURLQueryItem 来拼接跳转 URL 字符串。自带本义成果且操作 URL 更加标准。

NSURLComponents *components = [NSURLComponents componentsWithString:@"自定义的 Scheme://"];
NSMutableArray<NSURLQueryItem *> *queryItems = @[].mutableCopy;
NSURLQueryItem *aItem = [NSURLQueryItem queryItemWithName:@"a" value:@"参数 a"];
[queryItems addObject:aItem];
NSURLQueryItem *bItem = [NSURLQueryItem queryItemWithName:@"b" value:@"参数 b"];
[queryItems addObject:bItem];
components.queryItems = queryItems;
NSURL *url = components.URL;

落地 App 后的解决

点击小组件跳转 App 后会触发 AppDelegate 的 openURL 办法。

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options

在 openURL 办法中,通过解析 url 参数,明确用户须要的性能跳转或内容的展现,随后进行对应的实现。这对我的项目的路由能力提出了肯定的要求,因和小组件开发分割不大,不做详述。

动静配置小组件

小组件反对用户在不关上利用的状况下配置自定义数据,应用 Intents 框架,能够定义用户在编辑小组件时看到的配置页面。
这里用的词的定义而不是绘制,是因为只能通过 Intents 来生成配置数据,零碎会依据生成的数据来构建配置页面。

构建一个简略的自定义性能

构建一个简略的自定义性能须要两步:

  1. 创立和配置 IntentDefinition 文件
  2. 批改 Widget 的相干参数反对 ConfigurationIntent。

1. 创立和配置 IntentDefinition 文件

如果你在创立小组件 Target 时勾选了 Include Configuration Intent,Xcode 会主动生成 IntentDefinition 文件。
如果没有勾选 Include Configuration Intent 选项,那么你须要手动增加 IntentDefinition 文件。
菜单 File -> New -> File 而后找到 Siri Intent Definition File 之后增加到小组件 Target 中。

创立文件后,关上 .intentdefinition 文件进行配置。

首先须要记住左侧的 Custom Class 中的类名,Xcode 会依据这个名称,在编译后主动生成一个 ConfigurationIntent 类,这个类贮存了用户配置信息。当然这里也能够填写一个你指定的类名,须要留神我的项目编译过后才会生成这个类。
而后咱们须要创立自定义参数模板,点击 Parameter 下方的 + 号即可创立一个参数。
之后能够定义创立出的 Parameter 的 Type,除了绝对直观的零碎类型以外,还有两个比拟难以了解的 Enums 和 Types 分栏。

零碎类型
特定的类型有近一步的自定义选项来定制输出 UI。例如,Decimal 类型能够抉择采纳输入框(Number Field)输出或者是滑块(Slider)输出,同时能够定制输出的上上限;Duration 类型能够定制输出值的单位为秒、分或者时;Date Components 能够指定输出日期还是工夫,指定日期的格局等等。

Enum
简略的了解就是 Enums 是写死在 .intentdefinition 文件中的动态配置,只有发版才能够更新。
Type
Types 就灵便多了,能够在运行时动静的生成,一般而言咱们应用 Types 来做自定义选项。

反对输出多个值
大部分类型的参数反对输出多个值,即输出一个数组。同时,反对依据不同的 Widget 大小,限度数组的固定长度。

管制配置项的显示条件
能够管制某一个配置项,只在另一个配置项含有任何 / 特定值时展现。如下图,日历 App 的 Up Next Widget,仅在 Mirror Calendar App 选项没有被选中时,才会显示 Calendars 配置项。

在 Intent 定义文件中,将某一个参数 A,设置为另一个参数 B 的 Parent Parameter,这样,参数 B 的显示与否就取决于参数 A 的值。
例如,在下图中,calendar 参数仅在 mirrorCalendarApp 参数的值为 false 时展现:

2. 批改 Widget 的相干参数反对 ConfigurationIntent

替换 Widget 类中的 StaticConfigurationIntentConfiguration
旧:

@main
struct MyWidget: Widget {
 let kind: String = "MyWidget"
 var body: some WidgetConfiguration {StaticConfiguration(kind: kind, provider: Provider()) { entry in
 MyWidgetEntryView(entry: entry)
 }
 }
}

新:

@main
struct MyWidget: Widget {
 let kind: String = "MyWidget"
 var body: some WidgetConfiguration {IntentConfiguration(kind: kind, intent: WidgetConfiguratIntent.self, provider: Provider()) { entry in
 MyWidgetEntryView(entry: entry)
 }
 }
}

在 Timeline Entry 类中减少 ConfigurationIntent 参数
代码如下:

struct SimpleEntry: TimelineEntry {
 let date: Date
 let configuration: WidgetConfiguratIntent
}

批改 IntentTimelineProvider 的继承
Provider 的继承改成 IntentTimelineProvider,并且减少 Intent 的类型别名。
旧:

struct Provider: TimelineProvider {...}

新:

struct Provider: IntentTimelineProvider {
 typealias Intent = WidgetConfiguratIntent
 ...
}

顺次批改 getSnapshot / getTimeline 的入参以减少对自定义的反对。并在创立 Timeline Entry 时,传入 configuration。

应用接口数据构建自定义入口

Intent Target 中,找到 IntentHandler 文件,恪守 ConfigurationIntent 生成类中 ConfiguratIntentHandling 协定。
实现协定要求的 provideModeArrOptionsCollectionForConfiguration:withCompletion: 办法。
在这个办法中,咱们能够调用接口获取自定义数据,生成 completion block 所须要的数据源入参。

- (void)provideModeArrOptionsCollectionForConfiguration:(WidgetConfiguratIntent *)intent withCompletion:(void (^)(INObjectCollection<NMWidgetModel *> * _Nullable modeArrOptionsCollection, NSError * _Nullable error))completion {[self apiRequest:(NSDictionary *result){
 // 解决获取到的数据
 ....
 NSMutableArray *allModelArr = ....;
 // 生成配置所须要的数据
 INObjectCollection *collection = [[INObjectCollection alloc] initWithItems:allModeArr];
 completion(collection,nil);
 }];
}

小组件获取自定义参数

在小组件依据 Timeline Entry 生成视图时,读取 Entry 的 configuration 属性即可获取用户是否自定义属性,以及自定义属性的具体值。

总结

劣势和毛病并存


小组件是一个优缺点都非常明显的事物,在桌面即点即用的确不便,然而交互方式的匮乏以及不能实时更新数据又是十分大的缺点。正如苹果所说:”Widgets are not mini-apps”,不要用开发 App 的思维来做小组件,小组件只是由一连串数据驱动的动态视图。

劣势:

  1. 常驻桌面,大大增加了对产品的曝光。
  2. 利用网络接口和数据共享,能够展现与用户相干的个性化内容。
  3. 缩短了性能的拜访门路。一次点击即可让用户触达所需性能。
  4. 能够多次重复增加,搭配自定义和举荐算法,增加多个小组件款式和数据都能够不同。
  5. 自定义配置简略。
  6. 多种尺寸,大尺寸能够承载复杂度高的内容展现。

毛病:

  1. 不能实时更新数据。
  2. 只能点击交互。
  3. 小组件的背景不能设置通明成果。
  4. 不能展现动静图像(视频 / 动图)。

尾巴

小组件的开发实际到此告一段落,能够看到组件虽小,须要的常识还是挺多的。包含 Timeline、Intents、SwiftUI 等平时开发很难接触到的框架和概念须要理解学习。
小组件孱弱的交互能力和数据刷新机制是它的硬伤。苹果对于小组件的能力是十分克服的。在开发中,很多构思和需要都受限于框架能力无奈实现,心愿苹果在后续迭代中能够凋谢出新的能力。比方反对局部不须要启动 App 的交互模式存在。
但瑕不掩瑜,向用户展现喜爱的内容或提供用户想要的性能入口,放大小组件的劣势,才是以后小组件的正确开发方式。

参考资料

  • 意识小组件
  • Widgets 边看边写 -1
  • Widgets 边看边写 -2
  • Widgets 边看边写 -3
  • 使你的 Widget 反对个性化配置 & 智能化展示
  • 从开发者的角度看 iOS 14 小组件
  • 【十五分钟搞懂 SwiftUI】布局篇
  • 【十五分钟搞懂 SwiftUI】款式篇

本文公布自 网易云音乐大前端团队,文章未经受权禁止任何模式的转载。咱们长年招收前端、iOS、Android,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe(at)corp.netease.com!

正文完
 0