13 報

Share this post

SwiftUI 專欄 #4 Color 不只是顏色

www.ethanhuang13.com
SwiftUI 專欄

SwiftUI 專欄 #4 Color 不只是顏色

SwiftUI 讀完就入坑 2022/07/15

13
Jul 15, 2022
10
2
Share this post

SwiftUI 專欄 #4 Color 不只是顏色

www.ethanhuang13.com

13 的話

SwiftUI 專欄是一個付費專欄,只是我沒有設立付費牆,而是採用「誠實訂閱」制度。

我的目標是累積到 100 位 Patreon 支持者,目前進度來到 25%,感謝新舊朋友們的支持。

很高興專欄寫到第 5 篇了。今天來跟大家聊 Color 這個我很喜歡的主題,相信你會覺得有趣!

如果你是第一次讀這個專欄,建議先回到目錄,照順序閱讀。

(看前一期 · 回專欄目錄 · 回首頁)


首先來說 Color 的排版特性。

等一下 13,Color 不就是顏色嗎,哪來的排版特性?

我說過了呀,別把 UIKit 的思考習慣帶來 SwiftUI 嘛。色塊為什麼不能直接拿來排版呢?

🧱填滿畫面

我們可以把 Color 放進任何的 View Builder 裡,包括 View 的 body。

顯示出來的效果,就是佔滿整個「畫面」,或者說,有多少就用多少。

無情業配

用在有瀏海的 iPhone,系統會把 Safe Area 留白。如果想把顏色填滿,可以加個 .edgesIgnoringSafeArea(.all) 。

🥞多個 Color 自動等分

那如果我們把多個 Color 擺在一起呢?很簡單,它們會自動平均分配空間。

千萬別小看這個特性,很多需要等分的排版需求,用多個 Color 就可以解決。

舉例來說,當你需要自己做固定數量的 tab bar。

如果直接用 Button 下去排,因為文字不一樣,寬度顯得不平均,而且會縮在一起,而不是填滿全部的寬度。會變成這樣:

很多人會想說:「只要先知道全部的寬度,再拿來除以 tab 數量就好了。」但是,照著這個思路來做,很容易就會用上一期我說盡量別使用的 GeometryReader。這裡就不做錯誤示範了。

其實遇到這種需求,用 Color 來排版真的很簡單。

你看這樣子,橫向寬度是否每個就是 1/3 了呢?

稍微解釋一下。首先我們在 HStack 裡放了 3 個 Color,但是不需要真的有顏色,所以直接用透明的 .clear。

接下來在每個 Color 加上 .overlay 這個 modifier,裡面放實際要按的 Button。

.overlay 也是個好東西,其實我一直很想花大篇幅來介紹,但今天的主角不是他。目前記得拿它跟 Color 一起服用就好了。

🔧修正 Button 點按範圍

上面這段程式碼看似排版正確了,但實際行為有點問題。是哪裡呢?原來是按下去的範圍仍然僅限於有文字的地方。因為 Button 的範圍僅限於 label 的內容。

讓我們對調一下內外順序,把 Color.clear.overlay 放到 Button 的 label 裡,就可以了。

🌄.foregroundColor 與 .background

有一次,同事在 review 我的 SwiftUI 程式碼時,問了非常好的問題:

「同樣是設定顏色,為什麼 .foregroundColor(color) 的名稱中有 color ,但是 .background(color) 卻沒有 color 呢?」

這是因為,這兩個 modifier 其實不是一對的,意義完全不同。

  • .foregroundColor 是用來幫文字、圖形上色的。我們傳入的 Color 是拿來當顏色使用的。

  • .background 是用來幫 View 在它背後同樣範圍,放入另一個 View 的。我們不一定要傳入 Color!

當然,如果傳入 Color 的話,它就會被當成 View 並且填滿「整個範圍」。這就回到我一開始說的 Color 排版特性。

每個 Color 都可以當成 View 來使用。

我的說明有簡化過,因為 background 有好幾種版本。這邊指的是傳入 View 的 background<V>(),有興趣深入的話,請直接看連結的文件吧。

🌃.background 與 .overlay

再跟你說一件秘密吧。我們一開始不是教了 Color 搭配 .overlay 嗎?其實這個 .overlay 跟 .background 的排版行為完全一樣!

兩者都是在目前的 View 同樣範圍再放入另一個 View。差別只是在於,加在 z 軸的上方還是下方而已。

上圖的範例中,兩個 Text 完全相同,尺寸也相同。Color 放在 .overlay 與 .background 的差別只在於跟 Text 是誰蓋掉誰。

結果我還是忍不住介紹了 .overlay😂

🈳Color.clear、 Spacer(),與 EmptyView()

這三個東西,乍看之下很像,實際上完全不同。

先說 EmptyView()。當你需要指定或回傳一個 View,但是又不希望看到它,就可以使用 EmptyView,因為它不會參與排版。

雖然 Apple 官方文件說我們很少會用到,不過實際在寫程式的過程中,我還滿常拿來當作「暫時讓 code 可以 build 過的東西」。

這東西不佔視覺空間,所以我不稱它為「placeholder」,以免混淆概念了。

接下來比較 Color.clear 與 Spacer() 的不同。

我們已經知道 Color 會填滿範圍了,Spacer() 也是。但是 Spacer() 一般來說是用在 VStack 跟 HStack,在其他 View 之間拉開距離用的。此時只會在單一軸向發生作用。

換言之,就是留白。

Spacer() 的優先度很低,別的 View 決定好長度或寬度以後,剩下的才留給 Spacer()。相對的,Color 會吃掉一定的空間,甚至有可能會搶去其他 View 的。

上圖除了展示 Color 擠壓到 Text 的水平生存空間以外,它還往垂直方向長到滿。

所以,如果只是希望剩下的空間留白,應該用 Spacer(),而不是 Color。

通常也不會把 Color 跟 Text 擺在同一個 VStack 或 HStack 裡啦。

🌈Color 與 UIColor 近似的顏色功能與特性

最後,讓我們很快帶過 Color 在顏色方面的功能與特性。很多都跟 UIColor 很像,所以比較簡單。

建立 Color 方面:

  • 可以在 Asset Catalog 設定色碼與名字,並用 String 來 init

  • 可以用 rgb、hue 與 saturation,或是 white 的值再搭配透明度來 init

  • 可以從 UIColor、NSColor、CGColor 來 init,也可以反過來轉成 UIColor、NSColor、CGColor?

系統預設的彩色顏色,也跟 UIColor 沒什麼不同。有 red、orange、yellow 等等,可以直接從 Apple 的 Human Interface Guidelines 找到色碼表。

要注意的是,這些顏色都是動態的,會依照 Light / Dark Mode、Accessibility 的高對比設定等情況而有所不同。

這個特性可以在 Asset Color 設定(下圖右邊兩個箭頭)。

最基本的 black、white 當然也有,不過沒有提供 systemGray2 等灰階色。

UIKit element colors 也不在 SwiftUI Color 裡,只有 .primary 與 .secondary。畢竟 Color 還要支援非 iOS 的作業系統。真的有需要的話,自己轉就行了。

🎨Color 獨特的顏色功能與特性

  • .accentColor 是 SwiftUI Color 特別的 property,可以說是整個 app 的主題色調吧。你可以在 Asset Catalog 設定。現在新增專案預設都會看到它(上圖左邊箭頭)。

值得一提的是,Mac app 要使用 accentColor 的話,使用者必須把系統偏好設定的強調顏色設定成「多色」才會發生作用。

  • 可以用 .opacity() 直接幫 Color 修改透明度。

  • iOS 16 新增了很有趣的功能 .gradient,可以直接從 Color 產生漸層的 AnyGradient 。

Color 如何「保存」?

最後我想提一點,顏色的指定其實是滿複雜的事情。如果你想把 Color 存起來供未來使用(比如說把使用者選擇的顏色存在 UserDefaults),Color 沒有提供直接讀取 rgb 與 alpha 值的功能,可能要先轉成 UIColor 再處理。

由於 Color 跟 UIColor 都有動態數值的功能,以及可能要考慮 color space,務必把這些細節搞清楚,以免存錯數值。

結論

  1. Color 是 View,有排版特性,會填滿範圍

  2. 多個 Color 擺在一起時,會平分空間

  3. 搭配 .overlay 很好用

  4. .foregroundColor 與 .background 不是一對。傳入 Color 時,前者會拿來當顏色用,後者會拿來當 View 使用

  5. .overlay 與 .background 才是一對

  6. 在 V/HStack 中,Color 會往兩軸填滿、Spacer() 只會在單軸做留白

  7. EmptyView() 不會參與排版

  8. 顏色功能方面,許多都與 UIColor 相似

  9. 獨特之處在於 .accentColor、.opacity()、.gradient 等

  10. 如果要保存 Color 要特別小心


讀完這期 SwiftUI 專欄,有沒有覺得「好多東西其他 SwiftUI 教學都沒提過」呢?

喜歡本專欄的讀者,請到 Patreon 訂閱支持我🙏 我的目標是累積到 100 位支持者,目前 25%。也請按下愛心❤️、留言💬、回信✉️與我交流。這些回饋都會直接影響到我繼續寫作的動力與頻率喔。

希望今天的內容讓你可以少走一些彎路。我們下一期見!

2
Share this post

SwiftUI 專欄 #4 Color 不只是顏色

www.ethanhuang13.com
2 Comments
ribilynn
Jul 15, 2022·edited Jul 15, 2022Liked by 13

13 你好。斗膽發表一點反駁意見。

用 Color 搭配 .overlay 來排版完全是對 Color 的誤用。

添加了不必要的 View 層級,也大大降低了代碼可讀性。

Color 看起来有平分空间的特性不是因为它是 Color,而是因为:

1. 它被设置成「佔用所有可用空間(ProposedViewSize)」的效果

2. HStack 对拥有该效果且 LayoutPriority 相等的所有 Child View 进行可用空间的平分

1的效果所有 Shape(Rectangle、Circle)都有,GeometryReader 也有。

講2是因為如果你給 Child View 添加不同的 LayoutPriority,這種效果就會失效。

最直接且正統的做法應該是给需要这种效果的 Child View 添加 .frame(maxWidth(or Height): .infinity),即告訴 SwiftUI「我要让這個 View 佔用尽可能大的可用空間」。

當然,說實話我不太相信13會不懂這個,但 SwiftUI 新手看到這篇文章很有可能就這麼開始誤用了所以以免萬一。

Expand full comment
ReplyCollapse
1 reply by 13
1 more comment…
TopNewCommunity

No posts

Ready for more?

© 2023 ethanhuang13
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing