编者按:本文选自《创作者的 iOS 独立开发指南》,为了感谢大家的支持,我们延长本教程的 9 周年八折优惠至 3 月 31 日,欢迎购买。


你好,欢迎来到第五章!

上一章介绍了阅读器 MVP 应用 Reader,它支持显示某一个网址的文章内容。本文中,我将其扩展为一个完整的文章阅读器应用,介绍用于订阅各类网站的「百页签」。你可以将其作为灵感来源,用来了解我对该应用功能、存储等方面的构思及开发考量。

为什么创作「百页签」?

在数字信息时代,每个门户应用都在努力提供个性化的功能,将自己的信息筑作围城。当我们想看知乎日报时,需要先下载登录知乎应用;想了解科技大小事,常需先下载对应客户端;想看个新闻,需要与聚合类新闻客户端时间黑洞的推荐算法斗争;用网页标签,时常想不起来去点击它外,文章目录也不很直观。

我曾经认为微信公众号是最完美的解决方案,因为它懂得克制,严抓排版,且素材来源较广。很可惜,微信公众号体系又是一个更大的围城,要求所有作者在该平台上单独上传文章,较高的时间及运营成本令其内容的覆盖面有限,许多我感兴趣的网址并不支持微信公众号。

「百页签」应用的灵感来源于传统的报刊亭。传统报刊亭的思路很直接,老板不会一下子拿一堆书塞到你手上,而是允许你在老板筛选的店内书目中慢慢翻看,最后再由你筛选出你中意的那几本杂志长期关注。若你有额外需求,也可以和老板说一声,老板会去帮你把书找来。

Artstation @潘静 - 报刊亭概念设计

如下图右侧所示,「百页签」中的分类快速订阅列表扮演了老板筛选书籍的角色,提供了一份约一百个优质订阅源的清单。若用户感兴趣的内容不在快速订阅中,也可以使用手动订阅功能问老板单独索要某个网址的文章列表。如下图左侧所示,当用户选择自己感兴趣的文章来源后,「百页签」便会自动在主页自动加载关注列表,以助于让用户快速定位感兴趣的内容。

点开感兴趣的订阅源后,「百页签」会以纯文字的方式显示文章目录。之前我思考过是否显示文章配图,但考虑到各大网站本身的客户端已经能做这件事,「百页签」就没必要增加这些显示了。

以「理想生活实验室」为例,下图左侧是该订阅源的最新文章列表。纯文字排版可以让用户极快速地扫视感兴趣的内容。作为文章作者,我个人在写文章时是最先定文章标题来定调,最后选题图来抓眼球。也因此深知剔除了题图这件事,反而可以降低用户的决策负担。

如上图右侧所示,进入文章后,「百页签」会自动全屏调用 Safari 浏览器的阅读器视图。

使用浏览器视图的目的是规避网站上影响浏览体验的各类弹窗型广告,将注意力放在内容本身。直接调用 Safari 的浏览器视图还有个意外的好处,比如文字大小调整、切换字体、网站翻译等对阅读体验加分的功能都一应俱全。

特色功能

核心体验构建完成后,我开始思考我对文章阅读的需求是什么,以及为什么不同网站要有账号系统。在我的观察中,账号系统除了能组建用户的信息外,主要还起到了个性化书签的用途。比如某个用户点赞一篇文章,不少时候是认可文章中的概念,想把它收藏起来。

「百页签」作为纯阅读器应用,自身不具备广告,不需要提供复杂的评论等功能,也没有收集用户隐私的需求,因此不需要账号系统。但文章收藏这一点确实有必要,而且该收藏列表最好不与订阅源本身挂钩,即便用户今天收藏了「知乎日报」的某一篇文章,日后取消关注「知乎日报」,那收藏的内容应该继续保留。

基于以上思路,「百页签」在应用下方提供了 书签 选项卡。用户在栏目中看到的任何文章,都可以一键收藏倒书签中。书签列表采用 iCloud 与其他设备同步,因此在 iPhone、iPad 设备上都可以阅读。书签存储时会自动标注日期,并以最新的书签显示在最上方的顺序排列。

有了书签之后,我的思考又回到了阅读文章的目的上来。我会关注不少网站的文章,但不会一一去读取它,因此有个能让文章被我「看见」的机制就很重要。

iOS 14 之后的桌面小组件刚好能满足这个需求。如下图所示,用户可以自由定制小组件显示的订阅源内容,订阅源来源与应用中用户关注的栏目自动同步。当用户想看到多个订阅源的内容时,可以直接将设置为不同订阅源的桌面小组件叠加在一起。此时 iOS 系统会根据用户阅读偏好,自动在内容刷新时将最新的文章内容提到界面最前方。

至此,「百页签」基本构思完成。它由「栏目、书签、小组件」三个部分组成,共建出一个不侵犯隐私,无广告的纯粹阅读体验。点此即可在 App Store 中下载体验

技术方面

「百页签」是什么说完了,接下来聊聊该应用在存储方案、桌面小组件、付费方案、框架选择上的思考。百页签用到的技术已经在「创作者的 iOS 独立开发指南」中详细阐述,因此不再在技术细节本身上赘述了,而是单独探讨下用到的技术背后的原因。

存储方案

虽然看似简单,但百页签实际上用到了 文章 4.13 中提到的多种存储方案的组合。对于栏目本身的快速订阅列表来说,我的目的是让它最快速地加载出来,因此直接将 JSON 存储在 Xcode 项目中,如下图所示。为了给项目列表分段,我先在 JSON 文件中用 catrgory 对其进行分类,最后直接在 SwiftUI 视图层面用 for 循环让列表分区显示出来。

除快速栏目的加载外,另一个需要存储的便是用户的订阅目录。比如用户订阅了「少数派、知乎日报」这两个栏目,订阅目录便要存储这两个订阅源的网址。对于订阅源网址来说,该信息需要跨平台可用,因此存储在 iCloud Key-Value Storage 中,以提供最快的同步速度。因为网址信息本身并不复杂,也完全可控制在 Key-Value 同步的 1MB 的范围限制中。

对于已经加载出的,带文章标题的列表,则是根本没有存储,每次启动直接拉取即可。这是因为不同订阅源对应的文章列表本身随时在更新,硬是要存储起来也没意义。

对于与桌面小组件互通的数据,其目的是让应用本体与额外的小组件应用扩展进行沟通,因此需要给数据搭桥。我的方法是在带文章标题的列表加载完成后,自动生成一份存储在本地沙盒共享文件区域的 JSON 文件,以确保小组件中的订阅源信息始终保持在最新状态。

对于书签目录而言,需要提供跨平台存储条目型数据的功能,因此直接用到了 CloudKit 数据库。在数据库中,直接存储用户加入文章的时间戳、标题和地址即可。

最后一提的是暗色模式开关,考虑到用户可能在不同设备上使用不同模式,「百页签」中的暗色模式开关通过 @AppStorage 将设置存储在本地。综上,你可以看到应用中的存储方案没有定数,而是为需求服务即可。在实际开发过程中,具体使用哪种方案还是要落在功能和预期上。

桌面小组件

对于桌面小组件而言,常见的方法是只提供变种,不提供输入或只提供微量定制,比如提供一个开关来切换桌面组件显示的信息。不过常见方法用到的 TimelineProvider 不支持动态数据选择,因此只能强行要求用户自行输入订阅信息。为了提供动态选择的支持,我在「百页签」中用到了基于 Siri 的 TimelineProvider,它的功能更强大些,允许本例中的更复杂表单数据选择与同步。

付费方案

付费方案上,「百页签」主要是个展示型项目,其创作初衷也只是为了展示应用上架和更新流程,因此不以过分盈利为目标。为方便用户体验,我选择了「免费 + 一次性内购」的方案。免费用户可以添加前几个订阅源,基于 Non-Consumable 的付费则可以永久解锁订阅数量的限制。

框架选择

对于框架来说,百页签用到了以下第一方和第三方框架,具体用途也在旁边阐述。除 WidgetKit 会在 教程第五章 中介绍外,其余的用法遵从教程前四章所介绍的知识,以及 Reader MVP 应用开发过程中介绍的方法,并对「百页签」应用的功能需求适当进行逻辑上的调整。

  • Combine:应用中的全部数据流构建
  • SwiftUI:应用中的全部 UI
  • CoreData + CloudKit:书签数据库存储
  • StoreKit:应用内购
  • BetterSafariView:浏览器文章显示
  • FeedKit:解析 RSS 订阅源
  • CoreHaptics:在收藏文章时提供触控反馈
  • Foundation:在书签界面自动解析时间
  • WidgetKit:构建小组件

总结

「百页签」是一款基于 Apple 现阶段提供的最新技术,最新开发思路的展示型应用,也是我对 2021 年后新型独立应用构建的思考所得。它没有用到任何 OC 语言、没有用到任何 UIKit 有关的界面写法、也没用到基于 Delegate 的数据流。

在「百页签」中,你可以看到 Swift + SwiftUI + Combine + WidgetKit + CloudKit 这一组合的实际效果。在构建过程中,也用到了 MVVM 架构、描述式编程、函数式数据处理、响应式数据流的思路。得益于以上新技术的易用性,「百页签」的开发,测试到上架的时间其实只有短短的三四天。我想,其开发时间本身就能证明新技术的优势了。

即便到目前,我依然在不同场合看到许多人对最新技术的质疑,个人认为大可不必。只要你清楚自身需求,理得清进大厂维护应用还是做独立开发的区别即可,不必「面向过去编程」。

> 下载 少数派 2.0 客户端、关注 少数派公众号,解锁全新阅读体验 📰

> 实用、好用的 正版软件,少数派为你呈现 🚀