如何进行 iOS Widget 开发?

简介: iOS 14 重磅推出了新功能 Widget,可以在主屏上展示一些关键信息,如日程、待办事项、设备电量等。Widget 的设计定位是什么?有哪些限制?如何进行 Widget 开发?本文基于一个小游戏——盒马小镇的 Widget 开发,分享在登录授权、数据更新、界面渲染以及审核上的实践经验。

image.png

Widget 简介

Widget 是 iOS 14 重磅推出的新功能,使得用户可以在主屏幕添加小组件,快速浏览 app 提供的重要信息。它的设计与旧版本 macOS 的 Widget 一脉相承,甚至连添加的动画也是去掉了拟物化的水波纹效果。

image.png

设计定位

用户可以通过 Widget 对主屏幕进行个性化定制,但是 iOS 14 的 Widget 跟其他系统上的小组件有很大的区别。在 Widget 的设计上苹果也保持了一贯的克制,定位于轻量化、仅用作关键信息的展示。比如系统自带 Widget 中的股票、天气、电量、运动信息,他们的共同特征是更新频率高、提供的信息重要,让用户不用打开 app 就可以浏览关心的内容。苹果也不希望开发者将 Widget 仅仅当作 app 的一个快捷入口,这样的需求更适合用 contextual menu 来实现。

限制

苹果基于上面的设计定位,同时也为了节省系统资源保证续航,对 Widget 的做了一些限制:

  • 不支持动画,仅支持静态页面展示。
  • 更新频率由系统通过机器学习来动态分配。
  • 不支持拖拽、滚动等复杂的交互,不支持 Switch 等控件。
  • 用户点击 Widget 一定会跳转到 app。

Widget 开发实践

盒马小镇 Widget 设计

image.png

盒马小镇是盒马 app 内的一个小游戏,用户可以通过签到、购物、内容互动来获取盒花,用户最关心的就是有没有盒花可以收取,或者是否有盒花可以帮摘等信息。这正好可以契合 Widget 的设计初衷,我们期望能借此提升用户粘性。

要实现一个 Widget 首先要升级到 Xcode 12,然后在工程中增加一个新的 Widget Extension,Xcode 会自动生成需要的模板文件。

我们现在要做的只有两件事:数据加载、渲染界面。

登录及授权

加载数据之前首先要解决的是授权问题。由于集团的登录 SDK 暂时不支持 extension,加上全家桶依赖复杂、体积很大,我们不能使用 MTOP 来进行 API 调用,因此我们设计了一套认证机制,主 app 在登录后调用 MTOP 接口获取 token,并且将 token 写入 app group 中,Widget 通过 HTTPS 接口加上 token 来访问用户数据。

image.png

其中 token 通过 UserDefaults 来共享到 Widget,因为开发过程中不同的证书打包的 bundle id 不同,因此我们将 group name 设置成 group.[bundle id] 的形式,保证能正确读取 token。

var token: String? {
    let bundleIdentifier = Bundle.main.bundleIdentifier!
    let defaults = UserDefaults(suiteName: "group." + bundleIdentifier)    
    return defaults?.object(forKey: "widget.town.homeinfo.token") as? String}

Token 的同步有几个时机:

  • App 启动后,如果已经登录并且 app group 中没有 token,则获取 token。
  • 用户进入盒马小镇时获取 token。
  • 用户登录时获取 token。
  • 用户登出时删除 token。

数据更新

image.png

这里苹果借用了 timeline 的概念,timeline 里面包含了一个个 entry,entry 就是我们在特定时间需要展示的数据。系统先回调我们来获取 timeline 数据,再在特定时间来回调我们渲染界面。

我们在返回 timeline 时可以设置更新策略:

  • atEnd:在 timeline 中所有的 entry 都展示完之后更新。
  • never:仅在主 app 触发更新。
  • after:在特定时间后触发更新。

当然这些都只是我们建议系统的更新的时机,实际是否更新还是取决于系统。对于可以预测信息的,比如天气预报,可以在 timeline 中添加多个 entry,并且选择 atEnd 作为更新时机;对于不可以预测信息的,比如股市,可以选择 after 策略,在一定时间后更新信息。

除此之外,我们还可以在主 app 中调用 WidgetCenter 的方法来触发更新,经我们测试使用这种方法每次调用都是能够真正触发数据更新的。更多信息可以参考官方文档[1]。

在盒马小镇的场景下,我们是无法预知到将来的数据的,因此每次返回的 timeline 中只包含一个 entry,选取的策略是 after,每 20 分钟更新一次数据。

值得注意的是,在 getSnapshot 方法中也要返回真实的数据,这样用户在 Widget 的添加过程中看到的就是正确的界面,真正做到所见即所得。

image.png

在用户在主 app 中收取盒花之后,我们还会主动触发一次数据的更新。

渲染界面

苹果为了推广 SwiftUI,规定 Widget 只能使用 SwiftUI 来编写界面。SwiftUI 与 Flutter 类似,是随 iOS 13 推出一套声明式的 UI 框架。SwiftUI 在这一年中有了很大的进步,补齐了很多能力上的不足。它使得跨平台(仅苹果生态)的界面开发变得非常简单。此外 SwiftUI 还支持 preview(类似 Flutter 的 hot reload),代码的改动可以近乎实时地反应在 UI 上,极大提高了开发效率和体验。

如果有 Flutter 开发经验,只要熟悉 VStack、HStack、ZStack 以及常见的几个 modifier 就可以轻松上手。

image.png

Bundle 拆分

与集团的大多数 app 一样,我们的 app 工程也分为壳工程和业务 bundle,我们不希望每次修改 Widget 都去修改壳工程,于是要把 Widget 的业务逻辑拆分开来。经过尝试发现一个可行的办法:

  • 新建一个 Swift framework bundle,将 Widget 实现放在里面,并且将里面的类设置成 public。
  • 在壳工程中新建一个 Widget Extension,引入上面的 framework,在里面保留 @main 方法。
@main
struct HMTownWidget: Widget {

let kind: String = "HMTownWidget"

var body: some WidgetConfiguration {
    StaticConfiguration(kind: kind, provider: Provider()) { entry in
        HMTownWidgetEntryView(entry: entry)
    }
    .configurationDisplayName("盒马小镇")
    .description("快速查看盒花动态,访问盒马小镇收盒花")
    .supportedFamilies([.systemSmall, .systemMedium])
}
}

这种方法可以减少壳工程改动的风险,并且可以正常使用 SwiftUI 的 preview 和调试功能,缺点是 Widget 的配置信息仍在保留在壳工程中,暂时没有更好的办法在保证调试功能的同时完全分离壳工程和业务代码。

Swift Bridge

前面提到在用户摘取盒花之后,我们需要触发 Widget 的更新,这里需要调用 WidgetCenter 的 API,而这个 API 仅提供了 Swift 版本,而我们的主 app 是纯 OC 写的,需要做一个 bridge。要实现 bridge 必须开启 Define Modules,而我们的工程由于历史原因在开启后无法编译通过,现在的解决方法是新建一个 Swift framework,里面调用 Widget Center API,再由业务去引用这个 bridge 的 framework。

埋点

Widget 的曝光事件我们是无法感知的,由于点击 Widget 会直接跳转到主 app,所以我们在跳转到主 app 的 URL 上增加了埋点参数,主 app 解析 URL 中的参数调用 UT 来埋点。

审核

在我们一开始打完包提交到 TF 审核时被拒了:

image.png

理由是我们使用了无效的字体,这个字体是我们在很早的版本就已经依赖的 Flutter 三方库引入的,之前的审核也一直没问题,在去掉了依赖之后成功过审,原因不明。Flutter 官方 issue[2] 提到只有在增加了 Widget 之后才会遇到这个问题。

Swift 的副作用

目前盒马 app 最低支持 iOS 9.0,而 iOS 直到 12.2 才将 Swift 基础库集成到系统中,因此我们需要在 Build Settings 里面将“ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES”设置为 “YES” 才能在 12.2 及以下的系统上运行。

Swift 虽然在 5.0 版本完成了 ABI 稳定,但是在低版本操作系统中 (iOS 12.2 以下) 仍旧会有一些不够完美的地方。

在低于 iOS12.2 以下的操作系统会带来约有 3MB 的包大小问题,但幸运的是苹果放开了蜂窝数据网络下 200M 的下载大小。
在低于 iOS12.2 以下的操作系统会有 100-200ms 不等的启动耗时增加,但在团队同学的努力下上线了二进制重排,启动性能大幅度上升。具体分析请查看:手淘架构组最新实践 | iOS 基于静态库插桩的⼆进制重排启动优化[3]。
——《手淘 Swift 2019 大事记》

好在 iOS 的升级率比较高,相信随着时间的推移 Swift 的应用会越来越多。

最后

目前新版本已经正式发布(4.54.1),欢迎下载体验,App Clip 商品溯源也同步上线。现在的版本仍存在一些问题,比如用户如果已经打开小镇页面,通过点击 Widget 打开 app 仍然会再次打开小镇页面,需要从路由的层面优化;中尺寸的卡片相对于小尺寸也并没有透出更多的信息,与苹果的设计初衷相违背。不过也算是对新技术的一次尝试,后面会持续优化,期待能产生业务上的一些增量。

相关链接

[1]https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date
[2]https://github.com/flutter/flutter/issues/65991
[3]https://mp.weixin.qq.com/s/YDO0ALPQWujuLvuRWdX7dQ

目录
相关文章
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
98 1
|
1月前
|
设计模式 安全 Swift
探索iOS开发:打造你的第一个天气应用
【9月更文挑战第36天】在这篇文章中,我们将一起踏上iOS开发的旅程,从零开始构建一个简单的天气应用。文章将通过通俗易懂的语言,引导你理解iOS开发的基本概念,掌握Swift语言的核心语法,并逐步实现一个具有实际功能的天气应用。我们将遵循“学中做,做中学”的原则,让理论知识和实践操作紧密结合,确保学习过程既高效又有趣。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你打开一扇通往iOS开发世界的大门。
|
1月前
|
搜索推荐 IDE API
打造个性化天气应用:iOS开发之旅
【9月更文挑战第35天】在这篇文章中,我们将一起踏上iOS开发的旅程,通过创建一个个性化的天气应用来探索Swift编程语言的魅力和iOS平台的强大功能。无论你是编程新手还是希望扩展你的技能集,这个项目都将为你提供实战经验,帮助你理解从构思到实现一个应用的全过程。让我们开始吧,构建你自己的天气应用,探索更多可能!
61 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
2月前
|
开发框架 数据可视化 Java
iOS开发-SwiftUI简介
iOS开发-SwiftUI简介
|
3天前
|
设计模式 前端开发 Swift
探索iOS开发:从初级到高级的旅程
【10月更文挑战第31天】在这篇文章中,我们将一起踏上iOS开发的旅程。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。我们将从基础开始,逐步深入到更高级的技术和概念。让我们一起探索iOS开发的世界吧!
|
6天前
|
设计模式 前端开发 Swift
探索iOS开发:从初级到高级的旅程
【10月更文挑战第28天】在这篇技术性文章中,我们将一起踏上一段探索iOS开发的旅程。无论你是刚入门的新手,还是希望提升技能的开发者,这篇文章都将为你提供宝贵的指导和灵感。我们将从基础概念开始,逐步深入到高级主题,如设计模式、性能优化等。通过阅读这篇文章,你将获得一个清晰的学习路径,帮助你在iOS开发领域不断成长。
31 2
|
12天前
|
安全 API Swift
探索iOS开发中的Swift语言之美
【10月更文挑战第23天】在数字时代的浪潮中,iOS开发如同一艘航船,而Swift语言则是推动这艘船前进的风帆。本文将带你领略Swift的独特魅力,从语法到设计哲学,再到实际应用案例,我们将一步步深入这个现代编程语言的世界。你将发现,Swift不仅仅是一种编程语言,它是苹果生态系统中的一个创新工具,它让iOS开发变得更加高效、安全和有趣。让我们一起启航,探索Swift的奥秘,感受编程的乐趣。
|
14天前
|
Swift iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】在苹果生态系统中,SwiftUI的引入无疑为iOS应用开发带来了革命性的变化。本文将通过深入浅出的方式,带领读者了解SwiftUI的基本概念、核心优势以及如何在实际项目中运用这一框架。我们将从一个简单的例子开始,逐步深入到更复杂的应用场景,让初学者能够快速上手,同时也为有经验的开发者提供一些深度使用的技巧和策略。
39 1
|
2天前
|
存储 数据可视化 Swift
探索iOS开发之旅:从新手到专家
【10月更文挑战第33天】在这篇文章中,我们将一起踏上一场激动人心的iOS开发之旅。无论你是刚刚入门的新手,还是已经有一定经验的开发者,这篇文章都将为你提供宝贵的知识和技能。我们将从基础的iOS开发概念开始,逐步深入到更复杂的主题,如用户界面设计、数据存储和网络编程等。通过阅读这篇文章,你将获得成为一名优秀iOS开发者所需的全面技能和知识。让我们一起开始吧!