我是 Megabits,是一个普通的 iOS 开发者。直到有一天我买了个 Apple Watch。
PomoNow 是我的一款集成 Todolist 的番茄钟应用,主打「平衡了仪式感和功能性」的交互设计,不仅能够让新手立即开始使用番茄工作法,也具备很高的灵活性,适合于各层次番茄工作法用户。目前我 Mac 版本以及同步功能正在开发中。具体介绍见:PomoNow 2,给你一个有仪式感的番茄钟丨Matrix 精选,Watch 版本使用效果见:微博视频
这是一个初代 Apple Watch,作为第一代产品,它完美的执行着随着时间越来越卡的历史使命。虽说就连随便打开一个 App 都要好些时间,但我硬是在这样一个卡的不要不要的玩意上面把 PomoNow Watch 做完了。
PomoNow
是一个番茄钟。对于一个番茄钟来说最重要的东西是什么?能用!没错就是能用,但就是做到这一点就已经非常困难了。番茄工作法的一般流程是这样的:25分钟工作,5分钟休息,重复四次后进行一次25分钟长休息。前两个都算轻松,坑就坑在这个重复四次。支持在
Watch
上长休息就意味着要在一次番茄结束之后自动开始下一个番茄,这就是难点所在了。接下来我会开始详细讲讲这个功能具体困难在哪,我又是怎么考虑的。也欢迎你和本文一起思考。之后我会讲讲我在开发过程中遇到的其他问题和解决方案,这些问题很多是
watchOS 或者 Xcode 本身的问题,我在实践中找到了一些不那么完美的解决方案,但希望能帮到你。
在第一次开始制作 Watch 版 PomoNow 的时候,我希望让 Watch app 能够独立运行。
对于
Apple Watch 这样一个小体积设备,长时间在后台运行 App 十分耗电,所以一旦 Watch app 进入后台就会暂停运行。想要直接在
Watch app 上计时可以说是很麻烦的一件事,一般来讲在 Watch
上做一个停表的思路是这样的:在开始计时的时候计算结束时间,用一个计时器每秒计算一次剩余时间然后显示出来。
这样一来就不会受后台冻结的影响了。因为时间是根据目标时间减去当前时间实时计算出来的。我们也可以通过把时间分成块来计算现在处在「工作时间段」还是「休息时间段」。
聪明的你也许已经想到该怎么通过类似的思路实现连续自动计时了。
按照上图的思路,从前向后一个格子一个格子的扫描,在「当前时间」的指针进入我们扫描到的格子之后,我们就清楚的知道现在在工作还是在休息,以及还有多长时间了。
所以问题解决了吗?Naive!最大的问题其实并不在计时,而是在推送通知上。我们上文已经提到,Watch
app 是不能在后台一直运行的,那么想要告诉用户计时结束就必须要靠一个定时发送的通知来实现。对于单个番茄来讲比较好办,两个通知就 OK
了,但在自动开始新番茄的情况下,假设用户一直不抬起手腕开一下 App
的话,就要定无数个定时通知在后台。也许你想说:一百个(有限个)应该够了吧,谁会计时一百个番茄啊?这也许是个事实,但这样的解决方案实在是不够优雅。
那有没有办法比较优雅的做到这一点呢?我当时想出了这样一种方案,虽然看起来和上面的计时的做法一样纠结。我们知道,推送通知是可以设置重复的。假如按照初始条件每四个番茄进行一次长休息,我们也许就可以指定如下
8 个位置定时重复的推送通知,使其按照相同的时间间隔(工作时间 x 4 + 休息时间 x 3 + 长休息时间)重复推送即可。
但很可惜这种做法是没有用的。因为定时推送通知的时间间隔必须以此刻为基准给出,根本无法做到在第一次让每个通知在对应时间弹出来。
所以我决定在第一版
PomoNow Watch 中放弃自动计时的功能,用户只能在 Watch 上手动启动计时。而且,由于不能在 Watch
上直接推送通知的系统限制,我还需要唤醒 iOS App 才能提醒用户时间到了。想要这个 Watch app 独立运行,根本做不到。
根据上面的分析,我第一版的 Watch app 使用这样的做法:在 Watch 上独立计时,只支持完成单个番茄,结束后需要用户手动开始新番茄。
在开始制作之后首先遇到的就是调试的问题。模拟器中问题不大,真机调试却是非常可怕的。由于
Watch app 不能从电脑直接安装,Xcode 需要首先把 swift 的运行时和一系列安装脚本先传到手机上,再由手机完成安装过程。之后
Xcode 还需要通过手机来和 Watch 建立连接才能调试。前者等待时间超长,后者失败概率极高。我来讲一讲调试过程中可能遇到的各种问题。
首先是连接问题。比如你在把
iPhone 插在电脑上之前 Watch 处于锁定状态,之后再解除锁定,Xcode
显示已经检测到手机和表,这时点击运行就会有一定概率出现无法连接的问题。虽然情况各有不同,但连接不上的状况确实时有发生。发生这种情况的时候,我一般会把数据线拔掉重新插一下,等到
Xcode 重新检测出 Watch 之后,一般就没什么问题了。
另外在调试的时候不管是手机还是表都必须要保持在解锁状态。在比较早的版本中,表没有解锁会直接导致调试失败,后期版本中才会给出提示等待解锁。这耽误了我不少时间。
好不容易不会直接提醒连接不上了,之后还有更坑的问题可能会出现。点击运行数秒后,没有经过安装过程就已经显示「正在运行」,Watch
上一点反应都没。这种情况可能有两种可能性,一种是显示错误,Xcode
虽然提示正在运行,但实际的状态却是在准备安装脚本,这时候你只要等一会就好了。另外一种可能性是 Xcode
炸掉了,有时候你重插数据线可能有用,有时候就需要你退掉 Xcode 重开。
那如果是经过安装过程之后出这个问题呢?这就有可能是你自己走神了。当屏幕熄灭数分钟之后,Apple Watch 就会自动回到表盘,这是一个方便用户的设计。所以先检查一下你的 app 在不在后台,先不要急着怪苹果。
不过上面这些问题都只是偶尔出现,或者在初次运行的时候出现一次,下面的问题则是经常出现。
终于把
Watch app 安装好之后,接下来就是等待连接的时间。你看着屏幕转啊转啊转啊转啊,等到天荒地老,然后发现你的 app
崩掉了。这就是之前说的连接问题。这种问题一般是玄学问题,尤其对于我这种初代用户,更是搞不懂是因为自己的表比较卡,还是哪里炸掉了。这时,你停止了调试操作,想要尝试手动打开自己的
app 试试能不能运行。可并没有什么用,结果依旧是转啊转啊转啊转啊转啊 Boom,多少次都没用。这时候有两种方法。第一种是在 app
加载的时候将其强制退出,方法是按住开关机按钮直至出现关机画面,然后按住表冠直到回到主屏幕。之后便可以再次尝试运行,成功概率较低。第二种方法比较简单粗暴,重启手表。如果你有时间等它重启完的话,基本上都能解决问题。(删掉
Watch app 重新安装有时候也能解决问题)
经过了九九八十一难终于把 Watch app 安装好之后,看一眼时间已经过去了十多分钟。我想起我大好的青春年华不再回来。为了不耽误我大好的青春年华,我每次点完那个调试按钮之后就会去玩一局以撒,等我死了以后回来看一眼差不多也就运行了。
所以有没有解决办法呢?还是有的。Watch
app 在模拟器和实机上差别不是很大,很多时候我们只是想看一下运行效果并不需要调试信息。这时候我们只要把运行目标改成 iOS
app,然后运行就可以。因为 iOS app 在安装的时候会把 Watch app
同样编译安装,之后只要手动运行就可以了。这样就可以避开问题一堆速度又慢的调试器连接过程。
在制作第一版的时候,其实我对
PomoNow 在 Watch 上的体验提出了诸多的「伟大构想」,如和 iPhone 计时同步、在 Watch 上添加任务等。但由于
Apple Watch 的调试体验实在是太糟糕,我不得不在把自己逼疯之前放弃。而且不管是从保有量还是从使用频率上来说,Apple Watch
都不值得我花太多的精力去开发。所以在做了完成一次番茄的功能之后,我就没有再添加新功能,并将其定义为「实验性」给一些比较爱用 Watch
的用户尝试。
今年 6 月 1 日,一位叫 madsights 的用户在 Github 上给我发消息询问关于不能自动计时以及长休息的问题。我真没想到会有人在这个问题上反馈,这让我重新把 Watch 版本的开发提上日程。
上文我已经讲过,第一版虽然可以独立计时,但是推送通知依然要由手机发送,独立计时基本上没有什么意义。既然我需要一个完整的计时逻辑,为什么不把计时的部分全部放在手机上?于是第二版
PomoNow Watch 成为了一个只有显示作用的扩展。当你在 Watch 上启动计时的时候,会唤醒 iOS
设备在后台计时,这样既解决了功能不完整的问题,也可以直接实现 Watch 和 iPhone 计时同步。而且还非常简单。
于是我在 6 月 19 日完成了新版本的制作。本以为之后只要修一些小 bug 就没事了,结果又遇到了新问题。
PomoNow
目前提供了四种语言的版本:简体中文、繁体中文、English、日本語,在做本地化的时候我遇到了相当大的玄学问题,困扰了我很长时间。我们知道,在
Xcode 中进行本地化主要是通过添加对应语言的翻译文件,可以手动输入也可以导入导出一个标准的交换格式。
问题是在我全部弄完之后发现的:Watch
app
在所有语言的设备上都显示日语(我的主语言是英语)。网上有些人这么说:「这个问题是因为你本地的缓存,你上传给苹果的版本是什么问题都不会有滴。」然而并不是所有时候都是这样。在我制作第一版的过程中,问题确实如他所描述,虽然在本地模拟器和设备中调试均只显示中文,但上架之后就会恢复正常。到了做第二版的时候,事情就不一样了。我像之前一样弄好了所有本地化,虽然看到模拟器里没显示英语,但是也没怎么当回事,直到有用户反馈邮件告诉我。
7
月 19 日,一位叫 Clifford D'Souza 的用户给我反馈邮件,指出 PomoNow 在他的 Watch
上显示了一堆看不懂的日文。???我不是都已经弄好了吗?说实话如果是一个中文用户显示英语我倒是可以理解,不就是本地化没生效嘛,可是这日文是怎么回事?我这会已经彻底蒙了,我把整个工程翻了个底掉也没有找到一处自己做错的地方。
最后我实在是折腾累了,差不多当最后稻草一样的删掉了全部语言文件从头来过。然后就。。。就解决了???WTF?好吧不管怎样能用了就行,我于是尽快打包了新版本上传。算是顺利解决了问题。
可平静的日子就过了几天,在我又发布了几个版本之后问题再次出现。这次是在审核阶段被打回。苹果的审核员问我:「为啥你这玩意在所有语言的设备上都显示日文,请你解释一下。」解释一下?这明明是你们的
Bug
好吧为毛要我解释一下?我怎么解释啊?我只好又把工程翻了个底掉,并没有什么卵用。删掉语言文件重做,也没有什么卵用。我这个时候已经想放弃了,实在不行我就不做
Watch app 了。
经过了士气低落的一个下午之后,第二天早上,我突然想到了一个可能的解决方案。
一般来讲,除 Storyboard 外,我们是不需要提供未翻译的语言文件的,因为在调用字符串的语句中就已经包含了未翻译的字符串。一条典型的语句如下:(这个「Time to work!」就是原文。)
content.title = NSLocalizedString("Time to work!", comment: "Time to work!")
在分析之前的问题之后我发现,这个错误并不影响 Storyboard
的翻译结果,这意味着什么呢?刚才我们讲到 Storyboard
包含了一个未翻译的语言文件,是不是就是因为缺少了这个文件才导致这些字符串显示异常呢?在制作之前版本的时候,我就已经尝试过添加未翻译的语言文件,但当时并没有起作用。不过已经到了这个份上了,试试就试试吧。
于是我添加了一个空的英文 Strings 文件,大家应该已经猜到发生什么了。
到现在我也没想通这是什么毛病。在这个修复过的版本推送更新之前,我先让 Junyi Lou 帮忙测试了一下之前的版本。令人惊奇的是,在他刷了 Beta 版系统的 Apple Watch 上本地化一切正常,我怕不是被苹果坑了一波。
在踩了无数坑浪费了大量时间之后,我终于把这玩意搞出来了。我之所以想要坚持完成 Watch app,是因为我认为运行在手表上是番茄钟这样一个计时工具应该有的形态,而我之前所构思的「上发条」的交互也可以在手表上得到很好的体现。
我不知道为什么没听别人说起过那些糟心的问题,是不是因为我创建工程的那个
Xcode 版本有 bug,还是说这一切仅仅是因为我的 Watch
太卡了。不管怎么样,希望这篇文章中提到的解决方案能够对今后有跳坑意愿的同学有所帮助,再次感谢 格里芬顾 出给我的二手 Apple Watch
让这个 Watch app 得以面世。如果你愿意支持我,欢迎在 App Store 购买 PomoNow。 好了就说这么多,我得去歇一会了。
PomoNow:AppStore
注:
1. 本文内容为作者在初代 Apple watch 上的个人开发体验,不保证覆盖全部其他情况。
2. 由于商标权原因,PomoNow 尚未在美国和欧洲地区上架(整个 App 不能含有 Pomodoro 这个单词或这个单词的一部分),以后改名了可能会再上架。
3. 题图是我实拍来搞笑的,我为了拍这照片剁了两个苹果(一会还要吃掉)。希望没有吓到人(笑)。
(或点击阅读原文,直达CocoaChina商城)