上一節提到的「仿射變換」,可說是「向量繪圖」的最佳拍檔,有了仿射變換,即使是以正規化座標繪製的圖案,透過縮放、旋轉、位移、鏡像等操作,也能在不同尺寸的螢幕上,獲得最適合的顯示效果。

而且前一節還提到,transform 是畫布「圖層」的一個屬性,而不是方法,這意謂著 transform 是隨時都發生作用的,而不需要額外呼叫。事實上,當我們呼叫圖層方法 translateBy() 來產生「位移」時,其實就是變更 transform 屬性內容而發生作用,其他 rotate() 與 scaleBy() 也是如此。

當畫布Canvas送兩個參數「圖層」與「尺寸」進到匿名函式時,就已初始化這兩個物件,所以一開始 transform 就有初始值,讀者可以自行嘗試用「print(圖層.transform)」來觀察。

另外,不知道大家有沒有注意到,在前面這幾課的畫布(Canvas)範例程式,使用畫筆(Path)描繪圖形的程式碼佔了大部分篇幅,畫完後要落到圖層顯示出來,反而只需幾行程式而已。

如果畫布裡面需要描繪的圖形較多,畫筆(Path)的程式碼會變得非常冗長,有沒有可能將畫筆(Path)物件單獨抽離出來呢?最好是能個別描繪好圖形,再到畫布中構圖擺放。

Shape 就是這樣的物件類型,專門蒐集Path畫筆所描繪出來的圖形物件,之後可以用於畫布或視圖中顯示出來。Shape 原意是形狀、外形,在本課稱為「圖形」,由「畫筆(Path)」的線條所組成的輪廓外形。

從語法來看,Shape 是SwiftUI 課程到目前為止,除了視圖(View)之外第2個重要規範(Protocol),在第2單元第1課(2-1b)提過,規範是一種較大的物件類型,對物件的屬性或方法有所規定。Shape 規範必須含有一個名為 path() 的物件方法,會回傳一個已畫好圖形的畫筆(Path)物件。

若比較 View 與 Shape 兩個規範,語法上有何不同呢? View 規範的標準句型,視圖內容要放在主體(body)屬性裡面,我們已經非常熟悉:

struct 視圖: View {
    var body: some View {
        ...
    }
}

至於 Shape 規範的標準句型如下,所有畫筆的操作,都置於 path() 方法之中:

struct 圖形: Shape {
    func path(in 畫框: CGRect) -> Path {
        ...
        var 畫筆 = Path()
        ...
        return 畫筆
    }
}

注意 path() 方法有個特別的參數,參數名稱為「畫框」(或別的名稱),「畫框」前面的 in 稱為參數「標籤」(label),當要呼叫 path() 時所傳入的參數,就可以用 path(in: 左框) 來取代 path(畫框: 左框),閱讀起來比較順暢,所以參數標籤可看成參數名稱的別名。