13 的話
SwiftUI 專欄是一個付費專欄,只是我沒有設立付費牆,而是採用「誠實訂閱」制度。
我的目標是累積到 100 位 Patreon 支持者,目前進度來到 28%,感謝新舊朋友們的支持。
前言
相隔一個多月,又與大家見面了。
坦白說,在過去這個月裡,我對 SwiftUI 的感受是五味雜陳,直接導致這個專欄的難產。
怎麼說呢?隨著 iOS 16.0 的正式發佈,開發者社群也發現大量的 SwiftUI bugs。有些 bugs 在 iOS 16 的 beta 版甚至是沒出現過的。那些 bugs 都是 Apple 不該交給開發者的東西。
這就讓我感到為難。雖然這個專欄開宗明義就說是讀完就「入坑」,但這個情況也實在慘烈。
![Twitter avatar for @fatbobman](https://substackcdn.com/image/twitter_name/w_96/fatbobman.jpg)
下面我會轉載一些 SwiftUI 在 iOS 16 上的問題,然後談談我對這些現象的猜想。
框架融合的難處
有個觀點不一定對,但我常常講,就是:SwiftUI 這個框架本身的問題其實不多。通常最嚴重的問題都是它跟其他框架串接的時候。
Text
、Color
、Shape
、VStack
這些「原生 SwiftUI」的東西很少遇到問題。
但像是用 UIKit 做的 List
,從 iOS 13 以來就是重災區。也是我在 #3 那些不該碰的 SwiftUI API 裡面特別不推薦的功能。
以下兩個 iOS 16 bugs 都跟 List
有關。
List
搭配 ScrollViewProxy
進行捲動,有可能會 crash:
![Twitter avatar for @fatbobman](https://substackcdn.com/image/twitter_name/w_96/fatbobman.jpg)
![Image](https://substackcdn.com/image/fetch/w_600,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fpbs.substack.com%2Fmedia%2FFcm2HQNaIAIQSfh.jpg)
![Image](https://substackcdn.com/image/fetch/w_600,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fpbs.substack.com%2Fmedia%2FFcm2IV9aAAESFTS.jpg)
![Image](https://substackcdn.com/image/fetch/w_600,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fpbs.substack.com%2Fmedia%2FFcm3I-4agAAgbw_.jpg)
List
有時不會觸發 .task
:
![Twitter avatar for @fatbobman](https://substackcdn.com/image/twitter_name/w_96/fatbobman.jpg)
Apple 在 WWDC22 指出,iOS 16 裡的 List
實作已經從 UITableView
改成 UICollectionView
。
我們可以合理猜想,SwiftUI 工程師在修改呼叫 UICollectionView
那邊,有些情況沒有考慮好。
或者我們也可以說,這兩種截然不同原理類型的框架要融合,天生就有難度?
SwiftUI 的原則是「改變狀態就改變畫面」。要用 UIKit 來實作這個要果,就變成「框架要明確追蹤到狀態改變,並且使用 UIKit 的方式把畫面設定正確」。
那麼這些橋接與融合的程式碼就要面臨挑戰:SwiftUI 的畫面更新週期,能夠與 UIKit 的能夠完全同步嗎?UIKit 的事件能夠完美地傳回 SwiftUI 嗎?
如果有任何一點做不到的話,就是災難的開始。
我沒有用過 React Native,但我猜 RN 在與 UIKit 溝通也會有類似的挑戰。
其他 Bugs
不過下面這幾個問題,我就沒辦法單純用 UIKit 來當擋箭牌了😅
ScrollView
(雖說它目前還是 UIScrollView
實作)裡的 lazy View
的狀態沒有維持住。奇怪的是這個 View
裡面有幾層還有差🤔
![Twitter avatar for @fatbobman](https://substackcdn.com/image/twitter_name/w_96/fatbobman.jpg)
onPreferenceChange
寫在 .background
裡會壞掉,但是改成放在 .overlay
裡面就正常。這真的顛覆我對這兩者差異的觀念🤯
![Twitter avatar for @fatbobman](https://substackcdn.com/image/twitter_name/w_96/fatbobman.jpg)
至於 SwiftUI 在有 ProMotion 的 iPhone 上無法達到 120 Hz,可能只是 Apple 沒有設定好。畢竟 iPad Pro 上的 SwiftUI 一直都可以。
![Twitter avatar for @ChristianSelig](https://substackcdn.com/image/twitter_name/w_96/ChristianSelig.jpg)
![Twitter avatar for @ChristianSelig](https://substackcdn.com/image/twitter_name/w_40/ChristianSelig.jpg)
開發者自救會?
框架的 bug 有很多種,但最慘的肯定是「原本好好的,升級之後就壞掉了」這種。
我說的是正統的 API 使用,沒有用 workaround,而且開發者也用了新版的 SDK。但原本的語法或呼叫方式,在新的作業系統壞掉了。
SwiftUI 目前在 iOS 16.0 就是屬於這種。
那麼問題是,在 Apple 還沒修復的情況下,開發者能夠處理這些 bug 嗎?能繞過嗎?
使用 UIKit 的時候,如果有 bug,通常很好繞過。因為開發者有很多招可以使用:
在 runtime 拿到 UIKit 物件的實體,然後針對各種參數進行監控與修改。也就是說,官方的實作有問題,我們就把實際上發生的事情再改成別的
很多時候,延後一點時間來觸發,做到調整畫面更新順序就能解決問題😌
寫下繼承原本物件的新物件,並且覆蓋某些行為。這是標準的物件導向程式給開發者的自由
這 1、3 兩個技巧也可以組合,比如前者就是在 runtime 可以「看到」UIKit 的部分實作,所以才要覆蓋哪些部分。
但是 SwiftUI 的實作是看不到的,它的 API 把這些細節藏起來。
更重要的是,因為 API 是封裝過的。就算開發者用了各種技巧得知某個作業系統版本的實作並加以修改,在下一個版本它被改掉的機率遠高於 UIKit。這方面可以參見本專欄之前的「不要使用 Introspect」。
無奈的就是目前是 Apple 自己把實作改壞,開發者除了換個方式實作(比如把那個部分改成用 UIKit 來寫),也沒別的辦法。
有很多開發者在成熟的產品上不想用 SwiftUI──除非它是開源的框架──現在你可以理解背後的原因了。關鍵就是,在系統框架有 bugs 的時候,開發者有多少自救管道的問題。可能對於這些開發者來說,只要自己無法動手修改,SwiftUI 就只能是玩具。
當然,從另一個角度來說,任何一個處理掉很多 UIKit 細節毛病的 app,裡面應該都是充滿了 workaround 掉 Apple bug 的程式碼🌚 想要有「潔癖」地使用 UIKit 是不可能的,而 SwiftUI 就更不可能了。
結論
我想到 SwiftUI 的坑大概可以分成以下幾種情況:
某個 API 設計地不是很好,所以你得用某種不那麼爽的姿勢使用它
某個 API 沒有開放自訂或參數,所以你得自己重新實作
這個 API 提供新功能,但是在舊版 OS 沒有,所以只能等新版 OS 時才能使用
某個既有 API 在新版 OS 上是壞的
前 3 種坑,我覺得都能隨著開發經驗的增長,學會優雅地應對。但是今天列出來的問題都是第 4 種吧。
讀到這裡你可能會想說,還好我還沒有開始學 SwiftUI。
本來寫這個專欄的目的是引起讀者對 SwiftUI 的興趣,並且降低學習的障礙跟焦慮。
但現在的情況比較像是我們只能說,看看 iOS 16.1 或是後續版本會不會比較好。
我個人是不抱太大期待就是了🌚
雖然今天的內容沒一句好話,但是我最近還是寫了不少 SwiftUI,搭配 Xcode 14 的 Preview 更新速度還是滿愉快的開發體驗。也不能說 SwiftUI 在 iOS 16 就完全壞掉,只是問題比較多。
對於熟悉 SwiftUI 的朋友還是可以繼續用,總之哪裡有坑就避開。而對於初學或在觀望的朋友,我已經跟你說坑在哪了,哈!現階段只能降低對 SwiftUI 的期待了,這樣就不怕受傷害?
那麼你可能會問:「這個世界以後會變好嗎?我是說 SwiftUI。」我猜,如果 Apple 繼續用 UIKit 來實現 SwiftUI 的功能,那麼狀況應該不會變好。
但是有個現象值得觀察。今年 UIKit 在 UITableView
與 UICollectionView
裡新增了 UIHostingConfiguration 這個機制,也就是讓 cell 直接放入 SwiftUI 的 View
。耶,這思路是顛倒的──用 SwiftUI 來實作 UIKit。你覺得有希望嗎?
總之,我還是會密切觀察 SwiftUI 的發展,請繼續支持本專欄~
喜歡本專欄的讀者,請到 Patreon 訂閱支持我🙏 我的目標是累積到 100 位支持者,目前 28%。也請按下愛心❤️、留言💬、回信✉️與我交流。這些回饋都會直接影響到我繼續寫作的動力與頻率喔。
希望今天的內容讓你可以少走一些彎路。我們下一期見!
那刚开始学习iOS开发的话,是比较推荐从UIKit学起吗?