您现在的位置是:首页 > 文章详情

《Widgets 边看边写》第三部分:Timelines的进阶使用

日期:2020-08-20点击:852

作者:雪人少校(William),iOS 开发者,现就职于字节跳动音乐团队。

Session: https://developer.apple.com/videos/play/wwdc2020/10036/

文中涉及的demo在这里[1]

整个《Widgets 边看边写》系列中的其他文章:《Widgets 边看边写》第一部分:冒险开始了《Widgets 边看边写》第二部分:Timelines 的基本使用

开始之前,我们先介绍下面三个对象(在本系列之前的 session 中已经提到过,但因为它们相对抽象,且能体现 Widget 的设计思想,为了方便理解,我们用简单的语言再描述一下)

  • Timeline:时间线,是一个关于更新策略的封装,里面包装了若干TimelineEntry

  • TimelineEntry:时间线策略的控制点,主要用来控制,更新的具体时间点(会结合Timeline的其他属性)

  • TimelineProvider:提供Timeline的那个对象,系统通过它拿到Widget的Timeline

TimelineEntry 是最本质的那个对象,用来控制Widget更新时机

按苹果一贯控制且克制的风格,关键事情要拿在自己手里,在整个 iOS 系统中进行平衡——对于 Widget 来说,“关键的事情”是电量 & 流量的开销

这种机制确保了一个 Widget 不能随意刷新自己的内容,而是要通过有计划,有规律的方式来更新

特别注意:

TimelineEntry里面的Date不是一个精确的时间,只是一种给系统的建议,系统根据自身情况来决定是否需要更新,极端情况下,比如低电,甚至不能保证更新

本篇讲动态配置,如果没有看之前部分,可以把demo下载下来直接从第三部分开始

本篇 Widget:Leadbard Widget —— 一个将角色按 health 排列的 Widget,同时点击每个角色头像能够唤起主app,并进入该角色的详情页面,先整体看一下:

主App Widget

URL Session

Widget可以通过网络请求来获取数据 —— TimelineProvider 是通过回调工作的,这样会让网络处理比较简单

直接看代码(入口 CharacterDetail.loadLeaderboardData)

主旨是:

  • 1 这里其实就是一个普通的 URLSession,利用 dataTask 的方式请求这个 URL,然后在回调里处理返回的数据
  • 2 这里把 fauxResponse(其实就是个字符串,没啥神秘的)写入了一个在临时目录的文件 “userData.json”

Q:但这是什么操作?!写个文件,然后请求文件 URL,然后在回调里处理文件数据?!

A:这里没问题,且说明了一件事:Widget 里可以调用 URLSession 系列的 API,间接证明了 Widget 是有网络能力的

说到这里,我们仔细想一下,为啥要在 demo 里证明 Widget 有哪些能力?

了解过 Extension 开发的同学应该明白,苹果有好多 extension 类型:

ShareExtension 点击“信息”

时间上先有虎嗅 App 进程,然后才有“信息”界面,我们合理推论一下

  • 低成本:“信息”的界面运行在“虎嗅”进程里了

  • 高成本:“信息”也是独立进程和“虎嗅”跨进程通讯

  • 从安全角度考虑,利用进程间的页表隔离,彻底隔离两个进程的数据(符合苹果一贯的谨慎)

无论哪种,都说明 extension 的进程和他的属主 app 的进程并不一定是同一个

之所以不一定,看 Extension 的类型,反例是 NotificationExtension

回到主题,Widget 也是 Extension,它有自己的运行时环境,且在 iPhone/iPad 这样的用电池的设备上,在能用的 API 集合上必然是受限的!

Demo 刚刚说明了,URLSession 的 API 是可以放心使用的

理解了这些,后面很多内容就比较 make sense 了!

Background Session

用过后台下载特性的同学都知道,background session 在完成的时候,可选后台唤起主 app,从 AppDelegate 的这两个回调

不理解 Background Session 的同学去看看这里,这里我们不展开

对于 Widget 来说,Widget(进程)同样有能力处理 Background Session 回调

在没有 AppDelegate 的情况下,Widget 要怎么做呢?

答案是:在 StaticConfiguration 中,有一个配置 “onBackgroundURLSessionEvents”,可以注册一些回调,可以用来代替 AppDelegate 的角色

比如,注册一个处理回调

Widget唤起宿主App

想要做到这样的效果,应该怎么做?

该 SwiftUI Link API 出场了,从 AllCharactersView 讲起(我们 Leaderboard Widget 的主要视图)

这里嵌入了一个 Link(角色的url)

Q:就是 Deeplink 而已,有什么特殊的?A:是 Deeplink,但不是通过 URLScheme 做的

感兴趣的读者可以去工程里求证

看这里(注意这是主 App 的视图,不是 Widget)

链接匹配则上面的 NavationLink 会通过 SwiftUI 的数据绑定,显示对应的界面

关于数据绑定的细节,可以参考这篇[2]

Widget Bundle

目前,我们在Widget Gallery里还只能看到一个Widget

就是被 @main 标识的那个Widget

但一个 Widget Bundle 可以容纳多个 Widget,@main 可以移动到 bundle 这里

⚠️:从这里开始,我们看的是 Demo 里 Game Status Final 目录下的工程

现在能看到多个 Widget 了

动态配置 Widget

此 Target 主要是给 Widget 提供配置项目,下面详细说

Widget 可以有一些静态配置,在 part2 里介绍过

视频有点抽象,补一张图,“编辑小组件”就是这里所讨论的配置

一个配置可以通过 XCode 增加一个 IntentDefinition 来定义

不要被 SiriKit 带偏了,我们后面讨论的东西和 Siri 没有直接关系

这里之所以会出现 SiriKit 的身影,是因为现代版本的 iOS 里,Springboard 和 SiriKit 是分不开的,所以 Springboard 上的配置很多都和 SiriKit 相关

那这个配置到底干嘛的?

定义点了编辑之后,用户能看的项目

讲动态之前,先看静态配置:

注意几个细节

  • 左边栏里上面那个才是动态配置,这个是静态配置,

  • 配置项目是hero

  • 静态配置一般是通过Enum来表达,那故名思议,这当然是静态配置

  • 动态配置当然是程序控制有哪些项目的

再看Enum

一系列固定值,符合预期

再看看动态配置

这和刚刚的静态配置看起来一样的?

静态配置 动态配置

发现了吗?配置项的类型不同

这里在视频中没有详细说,但有必要了解清楚,那按苹果工具的思路,我们尝试探索一下:点这里

然后对新加的参数调整一下类型

<![](https://images.xiaozhuanlan.com/photo/2020/56e41fe7e9ec53348745e9df8ce5c62a.png,>

答案了出现了!

为了逻辑上的连贯,在这里稍微展开介绍一下视频里没提到的知识点:

XCode 对于 intentdefinition 文件是有自动代码生成的,我们点定义看一下(注意上方文件导航)

没有工程路径!说明这是一个生成的类

Show in Finder 看实际路径

一路追上去,是 DerivedData

对于动态配置,这里是关键

然后我们看一下 Hero 属性,看起来是一个形式化定义,是不是又生成代码了?

是的,读者有兴趣可以点Hero自行查看

让我们回到主线,打开 IntentHandler.swift 文件看一眼:

把两个集合合并好后,给系统回调

但系统怎么知道是 IntentHandler.swift 文件?

iOS 开发里代码逻辑的上找不出关联的东西,找 Info.plist

看到了吧?

长按编辑 Hero 配置项出现 配置内容

remoteCharacters 的内容在最后

到这里,视频就完了,但为了理解上的完整,我们再说一下如果用户真的选择了某个角色会发生什么:

我们开篇就说了这个类——提供 Timeline 的那个对象

Widget 配置中有它

Provider 有个回调

public func timeline(for configuration: DynamicCharacterSelectionIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        let selectedCharacter = character(for: configuration)
        let endDate = selectedCharacter.fullHealthDate
        let oneMinute: TimeInterval = 60
        var currentDate = Date()
        var entries: [SimpleEntry] = []
        
        while currentDate < endDate {
            let relevance = TimelineEntryRelevance(score: Float(selectedCharacter.healthLevel))
            let entry = SimpleEntry(date: currentDate, relevance: relevance, character: selectedCharacter)
            
            currentDate += oneMinute
            entries.append(entry)
        }
        
        let timeline = Timeline(entries: entries, policy: .atEnd)
        
        completion(timeline)
    }

注意第 2 行

func character(for configuration: DynamicCharacterSelectionIntent) -> CharacterDetail {
        let name = configuration.hero?.identifier
        
        return CharacterDetail.characterFromName(name: name)
    }

会去拿 hero.identifier,然后和自己定义的角色匹配,匹配到了就会更新界面!

所以,当用户选择了 Widget 的具体配置项目之后,会触发这里的刷新

func timeline(for configuration: DynamicCharacterSelectionIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> Void)

总结

终于讲完了,为了理解上的完整性,本文做了很多扩展性的说明,感谢各位读者的耐心阅读;这是 Widget 系列的最后一篇,WidgetKit 的概念很棒,也非常好用!希望大家能真正读懂原理,创造更多优秀的 Widget!

推荐阅读

✨ Apple Widget:下一个顶级流量入口?

为 Widgets 构建 SwiftUI 视图

《Widgets 边看边写》第一部分:冒险开始了

《Widgets 边看边写》第二部分:Timelines 的基本使用

关注我们

我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。

支持作者

这篇文章的内容来自于 《WWDC20 内参》。在这里给大家推荐一下这个专栏,专栏目前已经创作了 99 篇文章,只需要 29.9 元。点击【阅读原文】,就可以购买继续阅读 ~

WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。

参考资料

[1]

演示 Demo: https://developer.apple.com/documentation/widgetkit/building_widgets_using_widgetkit_and_swiftui

[2]

数据绑定: https://xiaozhuanlan.com/topic/0528764139


本文分享自微信公众号 - 老司机技术周报(LSJCoding)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

原文链接:https://my.oschina.net/SwiftOldDriver/blog/4517650
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章