Swift 與 iOS 開發心得
原文由 Ellie Huxtable 于 發布,訂閱此部落格
一開始我想先說明,我絕對不是什麼專家。雖然我寫程式已經很久了,但對 Swift 和 iOS 來說還是個新手,也從來沒做過行動 App :) 以下這些,就是我在打造Pillion 第一個 iOS 版本時一路跌跌撞撞學到的東西!
Pillion 是一款幫你尋找機車停車位的 App——目前在全球收錄了大約 10,000 個停車地點,有些來自 OpenStreetMaps,有些來自線上的資料集,剩下的則是由使用者提供。除了停車資訊外,它也提供騎乘紀錄追蹤的功能。
很多內容都是我在還搞不太清楚狀況時摸索出來的,所以在不少情況下,我的做法並不是「正確」的做法。後來學到更好的方法時,我也把兩種做法都寫出來了。
如果這篇文章反應不錯,之後我可能會針對每個部分(尤其是 MapBox)另外寫更詳細的文章。
如果你發現任何錯誤,請告訴我 😊
SwiftUI
我直接就用了 SwiftUI,也就是 Apple 隨 iOS 13 推出的全新 UI 框架。不過要注意的是,它只支援執行 iOS >= 13 的裝置——但這也涵蓋了 iPhone 6S 以後的所有機型。
因為我已經用了好幾年的 React,所以用起 SwiftUI 感覺很不錯。雖然還是有差異,但元件/子元件/狀態的處理方式都很自然。我甚至覺得它的狀態綁定比 React 處理狀態的方式更好,寫出來的程式碼也乾淨不少。
我沒用過 UIKit,所以沒什麼好比較的,不過就我目前看到的 SwiftUI 來說,它乾淨許多。
我在使用 SwiftUI 時也遇到了一些問題,感覺大多是因為它還不夠成熟——下面會再提到。
比較煩人的是,我在 GitHub 上找到大多數不錯的 UI 元件都只有 UIKit 版本。也許之後我會試著把它們包裝起來用,但 SwiftUI 的生態系目前看起來還是有點稀疏,很能反映出它還不成熟的現狀。這點要放在心上。
除錯
Xcode 的除錯器倒是給了我一個驚喜!我平常用 Python 的 ipdb 來除錯,本來沒期待在程式跑在手機上、又是編譯過的情況下,還能有這麼靈活的工具。
我發現除錯器可以很順暢地檢視數值、執行 Swift 運算式,想做的都能做到!Tab 自動完成也非常好用。工具給力真好 :)
偶爾會有點慢,但想想也是可以預期的。
UI 執行緒相關
我的 App 在很大程度上就是向 API 發送請求,然後把結果渲染出來。我發現如果嘗試從背景執行緒(例如 HTTP 回應的非同步處理器)去更新 UI,就會出錯。應該改用這種寫法:
DispatchQueue.main.async {
// do ui
}這樣就能超輕鬆地避免卡住 UI 執行緒 😁
其實這是我剛開始學 Swift 時很早就讀到的東西。後來我才學到,如果可以的話,用帶有 Published 屬性的 ObservableObjects 來抓取和更新資料才是更推薦的做法!不過我還是把它留在這裡,以備不時之需。我對目前專案裡處理 HTTP 請求的方式還是不太滿意,所以之後大概會再回頭重構這部分。
隱藏鍵盤
只是個小細節,但我想要在某些互動時能把鍵盤收起來。舉例來說,當搜尋列處於聚焦狀態、你點了清單中的某個項目時,就把鍵盤隱藏起來。
extension UIApplication {
func hideKeyboard() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}UIApplication.shared.hideKeyboard()Just
在 Swift 裡發送 HTTP 請求其實不算太糟,但因為我已經很習慣 Python 的 requests,所以想找個類似的東西。
Just 感覺就很符合需求!它輕量、簡潔、速度快,還能讓你寫出像這樣的程式碼:
Just.get("https://example.com") { r in
print(r.text)
}……這樣寫超讚!
它還可以讓你建立一個預先設定好標頭的 session,對於需要授權的 HTTP 請求很實用。
Mapbox
在這個專案的大部分時間裡,我都在使用MapBox Maps SDK for iOS。Mapbox 在 JavaScript 上用起來很愉快,但老實說 iOS 版本就有些不足。要客製化東西時會有點彆扭,而且文件寫得也不是很好。我常常發現做同一件事有好幾種方法,卻幾乎沒有解釋為什麼。感覺 Android 版本在這方面好像好一點!
我不是說它很糟,只是跟 JS 版本相比,感覺有點未完成、或是開發還不夠成熟——跟我所見的 Android 版本比起來也是如此。等我把 Android 版 App 做完之後,再來做個比較。
另外還有一點是,有些文件比較偏向 UIKit——畢竟 SwiftUI 還很新,這也很合理!只是要記住這點。不過還是有一些文件會教你怎麼把地圖包裝起來給 SwiftUI 用。
Swift 型別檢查
從 Rust 過來,Swift 的型別檢查器感覺有點……不太夠力。偶爾它會完全無法推斷出看起來很明顯的型別,或是花太久時間,然後要求開發者把程式碼拆開一點。雖然不是什麼大問題,但有點煩人。聽說這部分一直在持續改進中!
有些情況下,它會在某個位置標示錯誤,但那裡其實沒問題。把那段程式碼刪掉後,才會顯示出真正的底層問題。我覺得這比較跟 SwiftUI 的 DSL 有關。當 DSL 裡面有型別錯誤時,如果它無法推斷型別,整個就會有點崩潰。我現在手邊沒有很好的範例,之後找到再來更新。
在更新到 Catalina 和最新版 Xcode 後,我冒出了一堆之前從沒出現過、奇怪的「ambiguous member」錯誤。Swift 似乎很常給出誤導性的錯誤訊息,這點……有點奇怪。我從沒用過其他現代語言會這樣,尤其是背後還有這麼大一家公司在支援的語言。
後來我漸漸比較會寫出 Swift 能理解的程式碼了,儘管就我所知,我之前寫的那些在語意上也是正確的。
SwiftFormat
我習慣在其他專案中使用格式化工具,所以在這裡也找了一個。SwiftFormat 用起來似乎還不錯。
SwiftUI DSL 的問題
在 SwiftUI 裡,View 的 body 是用一種小型的 DSL 來宣告的
var body: some View {
ComponentOne {
AnotherOne {
// so much nesting :O
}
}
}……這樣看起來都很好。
不過,當你想要做條件式渲染時,DSL 似乎就還沒那麼到位。
if thing.ouch {
RenderMe(thing.ouch.thing)
}然而,像 if let 這樣的寫法,在撰寫本文的當下是行不通的
if let o = thing.ouch {
RenderMe(o.thing)
}雖然不是必要,但如果能支援會更好!
DSL 還有一些其他小問題需要解決,但 SwiftUI 還很新/還在 beta 階段,所以我並不意外。除了這些問題之外,用起來還是很愉快的。能跑的時候,跑得非常順!只是不能跑的時候就不行了 ;)
Auth0
我目前是使用 Auth0 Lock,搭配 Google 和 Apple 的 OAuth 登入。其實我現在已經改用自己的解決方案了,但還是把這段留下來,因為仍有參考價值。
Apple 的登入其實很酷!使用者可以選擇使用 Apple 的轉寄信箱來隱藏自己的電子郵件,還能用 Face ID 登入。
Lock 並沒有針對 SwiftUI 做好設定,但要包裝起來還算簡單:
struct Auth0Lock: UIViewControllerRepresentable {
typealias UIViewControllerType = LockViewController
var lock: Lock = Lock.passwordless().withOptions {
$0.passwordlessMethod = .magicLink
}
.onAuth { credentials in
print(credentials)
// save access token and use it to do stuff :)
}
.onError{ error in
print(error)
// you should probably do something other than just print errors
}
func makeUIViewController(context: UIViewControllerRepresentableContext<Auth0Lock>) -> LockViewController {
return lock.controller
}
func updateUIViewController(_ uiViewController: LockViewController, context: UIViewControllerRepresentableContext<Auth0Lock>) {
// there isn't really much updating I need to do atm
}
}Auth0 有內建憑證管理器,會幫你處理 refresh token、請求新的 access token、儲存與驗證 token 等等。所以你不需要花太多功夫就能讓使用者保持登入狀態!
稍微補充一下我為什麼放棄 Auth0:我想要在驗證的處理上有更多彈性,最後我把後端重寫成 Django App——所以直接整合 Django 現有的功能就變得很簡單。我改用 Django 是為了能更快地打造產品,而不用花時間去想有趣的架構問題。
Apple 開發者
為我的公司(Pillion Software Ltd)註冊 Apple Developer 帳號大概花了一到兩週——線上表單填寫很快,但審核花了一點時間,而且他們還得打電話給我。不過通過之後就一切順利了!
我試著用 Fastlane 來截圖,但發現模擬器/測試跑得不太順(Mapbox 在 UI 測試中會當掉),所以唯一可行的方法就是手動截幾張。不是很理想,我也想把這部分搞定。之後總會處理的。雪上加霜的是,我的筆電超慢又有點舊(主要是記憶體不足),所以模擬器跑起來非常卡。
我在第一次提交 App Store 後被告知,我的 App 功能不夠多。真是一大打擊。研究了一下後發現,好像任何「也可以做成網頁 App」的東西,Apple 都不太想核准。所以我就把開發路線圖上的一些功能提前實作了。
我實際上有一個版本從按下「提交」到正式上線,整個審核流程只花了大約 7 分鐘,讓我非常驚艷。
結論
回頭來看,我覺得在沿用舊網頁版的技術之前,應該要先多研究一下其他方案。也許 MapKit 就已經很夠用了,我根本不需要用 MapBox。
不過在這個過程中我也學到很多,所以我想我也不會改變太多。接下來我很快就要開始做同樣的 Android 版本了(而且我對 Android 開發也幾乎一竅不通 ;P),就看看結果如何吧!可以期待一篇類似的文章。
如果你有在騎車,決定試試看的話,我很想知道你的想法——所以請別猶豫,隨時跟我聯繫!
隨機一篇部落格
留言
登入後參與討論