我是 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
在踩了無數坑浪費了大量時間之後,我終於把這玩意搞出來了。我之所以想要堅持完成 Watch app,是因為我認為執行在手錶上是番茄鍾這樣一個計時工具應該有的形態,而我之前所構思的「上發條」的互動也可以在手錶上得到很好的體現。
我不知道為什麼沒聽別人說起過那些糟心的問題,是不是因為我建立工程的那個 Xcode 版本有 bug,還是說這一切僅僅是因為我的 Watch 太卡了。不管怎麼樣,希望這篇文章中提到的解決方案能夠對今後有跳坑意願的同學有所幫助,再次感謝 格里芬顧 出給我的二手 Apple Watch 讓這個 Watch app 得以面世。如果你願意支援我,歡迎在 App Store 購買 PomoNow。
好了就說這麼多,我得去歇一會了。
PomoNow:AppStore
注:
1.
本文內容為作者在初代 Apple watch 上的個人開發體驗,不保證覆蓋全部其他情況。
2.
由於商標權原因,PomoNow 尚未在美國和歐洲地區上架(整個 App 不能含有 Pomodoro 這個單詞或這個單詞的一部分),以後改名了可能會再上架。
3.
題圖是我實拍來搞笑的,我為了拍這照片剁了兩個蘋果(一會還要吃掉)。希望沒有嚇到人(笑)。