这是一系列正在进行的文章,记录我实现一个Mac App的过程,从最初的想法,到产品设计、编写代码,最后到产品上线。

本系列文章预计共有三篇,本文为第二篇。

上一篇《一个Mac App是如何诞生的:最初的火花》中,产品功能已定义完毕,按照企业工作流程,下一步是将需求文档交由相关人士,包括客户端开发、后端开发、设计师等,然后客户端开发在收到设计师的UI设计图后,方可开始具体的 UI 和交互开发。

Browser Deputy 是个纯客户端应用,不需要和和服务器通讯,所以不需要后端开发;而我对设计软件的使用实在不甚熟悉,所以也无法做出高保真设计图,而且应用界面就是一个应用中常见的命令面板,所以我选择直接编写代码,不画原型也不做设计。

不过在写代码前,我一般还是会参考下相似应用是如何设计的。

「聚焦」搜索

Spotlight
Spotlight

macOS 自带的「聚焦」搜索(Spotlight)。默认情况下只显示搜索框,输入内容后再展开结果。搜索框左侧是放大镜图标,字体的字号较大,右侧是当前选中的结果类型的图标。每个结果项只有一行,左侧是结果类型的图标,然后是内容,右侧可能有二级结果页的提示箭头。还可能有显示结果类型的章节标题(section header)。

Xcode 的 Open Quickly

Open Quickly
Open Quickly

Xcode 的 Open Quickly 毫无疑问是我使用次数最多的命令面板。在写代码时,可通过⇧ + ⌘ + O 唤起,输入大致的文件名称,按下回车,直接在 Xcode 编辑器打开当前选中的代码文件。它默认情况下和 Spotlight 一样,只有一个搜索框。每个结果项有两行文字,与左侧的文件类型图标垂直居中。标题行的内容是文件名称,详情行的内容是文件路径。当前选中行会使用 macOS 的主题色作为背景颜色,搜索的关键词使用更大的字重高亮。

Raycast

Raycast

相比之下,Raycast 的设计就比较复杂了。虽然是单行设计,但结果项从左往右依次是类型图标、标题、次标题、类型说明。此外也有章节标题,底部还有个固定的工具栏,提供更多操作的入口和说明。

上述三个是比较有代表性的,其他参考对象还有 Alfred、LaunchBar、Paletro、Arc 浏览器的 Command Bar 等。

Browser Deputy:内置命令

让我们回忆一下 Browser Deputy 设想的功能:

  1. 自定义搜索引擎
  2. 复制当前标签页的 URL
  3. 复制当前标签页的标题
  4. 以 Markdown 形式复制当前标签页
  5. 搜索浏览器书签、历史记录、标签页
  6. 搜索浏览器菜单项

我的开发习惯是,功能实现由简至难。这里面的六个功能里,二、三、四可划分为同一类功能,即内置命令。实现它们只需通过 Apple Script 获取当前浏览器的标签页,而这些我早已在 Anybox 中实现,所要做的事情只是复制粘贴。然后是搜索浏览器菜单项,搜索书签和历史记录,最后需要最多时间的,可能是自定义搜索引擎,因为涉及的界面较多。

把这些功能分类后,可以得到这些在代码内定义的功能类别:

  1. 内置命令,如拷贝当前标签页的 URL
  2. 搜索菜单项
  3. 搜索书签
  4. 搜索历史记录
  5. 自定义搜索引擎

其中原定的搜索标签页,在调查后决定放弃,因为菜单项中已包含标签页,而且我怀疑此功能的实用性。上述的功能顺序也是我们实现的顺序。

Browser Deputy:内置命令

经过思考后,决定采用单行设计,因为内置命令如拷贝URL,并无第二行内容可展示。结果项左侧为类型图标,然后是动作名称。底部加入工具栏,一则提供当前应用信息,二是提供设置入口。用户可随时进入设置页面,而无需通过菜单栏图标或者快捷键 ⌘ + ,

在做过多款 macOS 应用,接触了许多用户后,我有些体会,即使你做的是效率或者工具应用,也不能假设用户会知道一些如 ⌘ + , 可打开设置的惯例。应用的所有功能,应该是对用户可见的。可见并不是指把所有功能按钮都放在显眼的工具上,而是要能被用户发现。比如应用内可关闭的帮助提示和帮助指南、应用外的文档网站等。

Browser Deputy:搜索菜单项

实现内置命令后,需要实现菜单项。在通过 Accessibility API 获取应用的菜单项时,我才意识到菜单是树状结构,比如 Chrome 的 Developer Tools 菜单项,它位于 View → Developer 内。这个层级信息是有用的,应该展示给用户。但如今的单行设计内,我们没有更多的空间展示这个信息。所以决定将结果项改为两行内容。

Browser Deputy:搜索菜单项

这版设计的主要不同之处是把结果项从一行改为两行,然后在右侧添加了菜单项的快捷键标签,此外也更改了窗口的背景颜色,增大了对比度。

Browser Deputy:搜索书签

对于书签,第二行内容自然是 URL。而对于内置命令,第二行内容是拷贝结果,在 UI 上保持一致,也符合逻辑。

Browser Deputy:搜索书签

实现过程遇到了一些问题,比如不同浏览器对于书签的储存方式不一样,但大体上可分为三个阵营:Chromium、Safari 和 Firefox。但最复杂的是 Safari。即便这个应用已能直接访问文件系统,但若想访问 Safari 的书签文件,仍需用户手动授权。为此我们得增加一个授权按钮和提示说明,不过这是最后的问题了。

Browser Deputy:搜索书签

工具栏的中间还有很大空间,可展示更多信息,当然,此处信息仅用于 DEBUG。

应用到这个程度了,有些设置也可以先出台了,比如激活 Browser Deputy 的快捷键、内置拷贝动作的快捷键。

通过快捷键复制当前标签页的 URL 是非常方便的,但主流的浏览器均无提供,我第一次看到此快捷键,也是在 Arc 浏览器。

Browser Deputy:设置
Browser Deputy:设置

Browser Deputy:自定义搜索引擎

本来计划实现搜索书签功能后,继续做历史记录的,但研究后发现,历史记录可能是最麻烦的。Browser Deputy 支持大部分常见浏览器,包括 Chrome 以及其他基于 Chromium 项目的浏览器,如 Edge、Brave、Vivaldi、Arc、Opera 等、Safari 以及 Firefox。除 Safari 把书签存放在一个 plist 文件外,其余浏览器都把书签存放在一个 JSON 文件中,只是格式或许有差异。对于获取书签,我们只需解析对应的书签文件即可。而历史记录较为复杂:这些浏览器都把历史记录放在 SQLite 数据库中,有些还把内容存放在不同的数据表里。于是我决定先做自定义搜索引擎。

Browser Deputy:自定义搜索引擎

咋一看这表单还挺复杂的,因为除了设置名称,我们还支持设置图标和图标颜色,同时还添加了一些帮助信息以及模版参数按钮,便于用户使用。我考虑过去掉图标和图标颜色设置,让表单变得简单一些,但这些体现个性的设置,如果我是用户的话,我会希望有。而且考虑到用户不会频繁添加或更新搜索引擎,所以决定保留这些设置。

设置页面全部使用 SwiftUI 实现,使用了许多 macOS 13 才支持的特性,而设计则参考了 macOS 13 的设置应用。所以 Browser Deputy 只支持最新版本的 macOS,即 macOS Ventura。

Browser Deputy:设置

Mac 用户的升级热情不如 iPhone 用户,一般用户也期待应用至少支持最近的一两个版本。但考虑到因适配需额外增加的开发时间,以及支持系统版本数量的收益是逐渐递减的,因为用旧系统的人会越来越少,而且 macOS 14 还有几个月也要发布了,再加上我手上也只有 macOS 13,所以最终决定只支持 macOS 13。

这其实是个很轻松的决定:在应用新建项目前,我就决定只支持 macOS 13,因为我在 OK JSON 中使用了 SwiftUI 的 GroupedFormStyle,那时就发现使用 SwiftUI 的 Form 来实现设置页面极其方便。所以我决定在未来的项目里充分利用 SwiftUI。虽然中途也遇到了 SwiftUI 的一些问题,但通过组件化,我可以很轻松地复用这些组件:听起来稀松平常的,但「复用 UI 组件」在 AppKit 内并不容易。

Browser Deputy:高亮当前选中项

添加自定义搜索引擎功能后,最初的设想,直接使用 Google 站内搜索的功能也可以使用了。主窗口的设计也做了一些调整,主要为使用系统主题色高亮当前选中项的背景。

Browser Deputy:搜索历史记录

在开始啃最后一个难题时,我决定先折腾折腾。原来的面板窗口的背景是纯白的,虽然对比度没有问题,但我想加上类似 Spotlight 的毛玻璃效果,窗口背景会随着背后的内容而发生变化。

Browser Deputy:Spotlight 的材质

此外,应用首次启动时,应该显示的快速指南也先做了。而且到最后阶段了,本地化也可以一并做了,于是应用可以简体中文显示。

Browser Deputy:快速指南

在花费了一些功夫后,历史记录也完成了。

Browser Deputy:搜索历史记录

作为一款搜索书签应用,我在开发过程也不免拿它和 Anybox 比较,看看二者的体验差距如何。Anybox 的快速查找在体验仍有许多优势,比如高亮显示搜索关键词、左侧的网站图标、按上次打开顺序排列的结果、右侧的打开快捷键、可直接粘贴 URL 等功能。

Anybox:快速查找

有些功能因为设计无法在 Browser Deputy 实现,比如右侧的快捷键打开,Browser Deputy 此处已经放置自定义命令的关键词和菜单项的快捷键,而这些可能是更重要的。但显示网站图标、按上次打开时间排序等功能值得添加。此外也支持通过 ⌘ + ⏎ 直接粘贴书签或者历史记录的 URL。算是把体验差距拉小了。

Browser Deputy:搜索书签

结语

为了写这篇文章,我把代码仓库设置到最早状态,然后再一点点向前推进。这个在我设想中比较简单的应用,在近一个月的开发期间内,也遇到大量问题,需要做出大量决定。当然,绝大多数问题都是细节问题,如需不需要显示菜单项的 ✓ 状态、怎么排列搜索结果的顺序、是否提供禁用内置搜索的选项等。

虽然用时不到一个月,但往前回顾时,会觉得开发这应用也太难了,需要思考的问题太多了。但当初开发时,并没有这种感觉。我想主要是因为开发应用时,心里所想的是要一个一个地解决问题,实现功能。

到目前为止,最早定义的功能都已实现,所以下篇将会讲述上线前需要做的事情,包括设计应用图标、网站、在打包前的测试等。

我们下期再见。

第一篇:《一个Mac App是如何诞生的:最初的火花

第三篇:《一个Mac App是如何诞生的:上线的前夕

你可以在 Twitter 上找到我:@fengtieshan