繪圖動畫基礎-畫布篇
-
畫布 Canvas
在2021年發布的SwiftUI更新中,最受到矚目的兩個物件是TimelineView(時間軸視圖)與Canvas(畫布或繪畫板),如果說TimelineView讓我們能夠精確控制子視圖的時間,那麼,Canvas則是讓我們精確控制視圖的每個畫素。
不過,為什麼需要Canvas?我們已有許多視圖物件,包括文字、圖片、幾何圖形、圖示、影片,還有動畫的效果等等,有什麼是之前視圖做不到,只有Canvas才做得到的嗎?
是的,Canvas 能做的太多了,從點、線、面、數學函數作圖,到影像處理、3D立體渲染,Canvas 提供我們一個豐富的電腦繪畫(Computer Graphics)工具。光是用Canvas就足以寫一本書,本單元後半課程,基本上都跟Canvas相關。
此外,Canvas 還提供過去我們學過的視圖所沒有的一項重要資料:視圖尺寸。例如在第1課的文字跑馬燈,我們如何確定一段文字的寬度,需要多少位移才剛好可以讓文字從螢幕外跑進來?這就得靠Canvas取得視圖尺寸,才能精確控制。
操作步驟如下:
選取畫面左方的「+」號新增電子書頁面。
將新增的電子書面頁命名為「(30)SwiftUI動畫-2畫布篇」。
在「Main」模組中撰寫程式:
// 4-5a 畫布 Canvas // Created by Philip, Heman, Jean 2022/04/16 // Revised by Jean 2025/01/26 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(畫布())
程式執行結果,如下圖。
注意這裡不需用Spacer()就可將Text壓到螢幕最底下,也就是說,Canvas 不像 Label 或 Text 有預設尺寸,如果沒有指定 .frame() 大小的話,會佔掉剩餘的螢幕空間。
雖然Canvas 是個視圖,有尾隨匿名函式 { },但並非視圖容器(View Container),這是Canvas獨特的地方,在Canvas { } 裡面的元素,不是其他視圖,而是底層的繪畫物件。
在Apple的物件庫中,最底層(最基本)的2D繪畫物件庫稱為 Core Graphics(Core是核心的意思),其中物件都以 CG 開頭命名,其中幾個會先用到的基本物件如下表:
Canvas 語法和其他視圖容器很不一樣,{ } 裡面不是寫視圖與修飾語,而是寫繪圖指令,Canvas 會傳遞兩個很重要的參數到 { } — 圖層(context)與尺寸(size):
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: 右下框) }
「圖層」是我們可以繪圖的地方,Canvas是一個數位畫布,可包含多個圖層,圖層之間(類似用ZStack)疊加起來就形成整個Canvas視圖,上面程式片段最後3行在圖層上畫(draw)出三個元素,一個Text字串以及兩個Image圖形。
「尺寸」類型是CGSize,為上層視圖配置給Canvas的視圖大小,包含寬(width)與高(height)兩個屬性,寬(width)相當於視圖範圍內X軸的最大值,高(height)則是Y軸最大值。
要在Canvas圖層裡面繪製物件,常用到CGRect指定畫框位置,CGRect畫框包含一個點座標與寬高尺寸,點座標代表畫框左上角位置,例如最後一行我們將一個矩形畫在「右下框」,左上角在中心點。
Canvas 對於元素的佈局,也跟之前的視圖容器排版方式完全不一樣,Canvas裡面的Text文字會對齊CGRect畫框左上角,Image圖形則對齊左下角。
因此,我們通常在Canvas裡面先定義一些基本的點、尺寸或畫框位置,這樣在圖層中畫圖,就容易多了,如下圖:
由此可見,Canvas 之所以能夠帶給我們更多的繪圖方式,一方面是採用比較低階(基礎)的繪圖功能,一方面則是所有細節(包括位置、尺寸等)都可由程式設計師指定,雖然稍微麻煩一些,但是帶來更多彈性。