在去(2021)年發布的SwiftUI更新中,最受到矚目的兩個物件是TimelineView(時間軸視圖)與Canvas(畫布或繪畫板),如果說TimelineView讓我們能夠精確控制子視圖的時間,那麼,Canvas則是讓我們精確控制視圖的每個畫素。
不過,為什麼需要Canvas?我們已有許多視圖物件,包括文字、圖片、幾何圖形、圖示、影片,還有動畫的效果等等,有什麼是之前視圖做不到,只有Canvas才做得到的嗎?
是的,Canvas 能做的太多了,從點、線、面、數學函數作圖,到影像處理、3D立體渲染,Canvas 提供我們一個豐富的電腦繪畫(Computer Graphics)工具。光是用Canvas就足以寫一本書,本單元後半課程,基本上都跟Canvas相關。
此外,Canvas 還提供過去我們學過的視圖所沒有的一項重要資料:視圖尺寸。例如在第1課的文字跑馬燈,我們如何確定一段文字的寬度,需要多少位移才剛好可以讓文字從螢幕外跑進來?這就得靠Canvas取得視圖尺寸,才能精確控制。
以下我們先以一個簡單範例,來看看Canvas所具備的基本功能。
// 4-5a 畫布 Canvas
// Created by Heman, 2022/04/16
import PlaygroundSupport
import SwiftUI
struct 畫布: View {
var body: some View {
Label("Swift程式設計第4單元", systemImage: "swift")
.font(.largeTitle)
.foregroundColor(.orange)
.padding()
Canvas { 圖層, 尺寸 in
let 寬 = 尺寸.width
let 高 = 尺寸.height
let 中心點 = CGPoint(x: 寬/2, y: 高/2)
let 半幅 = CGSize(width: 寬/2, height: 高/2)
let 全框 = CGRect(origin: .zero, size: 尺寸)
let 左上框 = CGRect(origin: .zero, size: 半幅)
let 右下框 = CGRect(origin: 中心點, size: 半幅)
let 字串 = "↖︎畫布座標原點\\n↔︎寬\\(寬)點 x ↕︎高\\(高)點"
圖層.draw(Text(字串).font(.title), in: 全框)
圖層.draw(Image(systemName: "aqi.medium"), in: 左上框)
圖層.draw(Image(systemName: "rectangle"), in: 右下框)
}
.foregroundColor(.cyan)
.border(Color.red)
Text("現在時間\\(Date())")
.padding()
}
}
PlaygroundPage.current.setLiveView(畫布())
範例中有3個視圖,分別是Label, Canvas 與 Text,顯示如下圖。Canvas 本身也是一個視圖,在螢幕上所佔位置與大小,由上層視圖(如VStack或根視圖)所配置。
注意這裡不需用Spacer()就可將Text壓到螢幕最底下,也就是說,Canvas 不像 Label 或 Text 有預設尺寸,如果沒有指定 .frame() 大小的話,會佔掉剩餘的螢幕空間。
雖然Canvas 是個視圖,有尾隨匿名函式 { },但並非視圖容器(View Container),這是Canvas獨特的地方,在Canvas { } 裡面的元素,不是其他視圖,而是底層的繪畫物件。
在Apple的物件庫中,最底層(最基本)的2D繪畫物件庫稱為 Core Graphics(Core是核心的意思),其中物件都以 CG 開頭命名,其中幾個會先用到的基本物件如下表:
# | 物件名稱 | 中文名稱 | 用途說明 |
---|---|---|---|
1 | CGFloat | 浮點數 | 繪圖用的基本數值(等同於 64-bit Double類型) |
2 | CGSize | 寬高尺寸(點) | (width, height) 用兩個CGFloat定義螢幕寬、高點數 |
3 | CGPoint | 座標點 | (x, y) 用兩個CGFloat值定義螢幕座標位置 |
4 | CGRect | 矩形/畫框/視框 | (origin, size) 指定矩形左上角位置(原點)與寬高尺寸 |
5 | CGSize.zero | 零尺寸 | (width: 0.0, height: 0.0) |
6 | CGPoint.zero | 原點(origin) | (x: 0.0, y: 0.0) |
7 | CGRect.zero | 空畫框 | (CGPoint.zero, CGSize.zero) == (0.0, 0.0, 0.0, 0.0) |
Canvas 語法和其他視圖容器很不一樣,{ } 裡面不是寫視圖與修飾語,而是寫繪圖指令,Canvas 會傳遞兩個很重要的參數到 { } — 圖層(context)與尺寸(size):