在去(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或根視圖)所配置。

截圖 2022-04-19 上午10.01.10.png

注意這裡不需用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):