作業#15 — 從程式製作國旗圖案

目的: 從程式製作畫面,學習 addSubview 和 UIBezierPath。

想去的國家太多了,就乾脆做個歐盟的旗幟,直接涵蓋 27 個國家吧!

Tools

旗幟原圖是從 Wikipedia 上找的。為了方便定位及著色,分別將圖片上傳至以下兩個網站:

  1. XY 定位生成器:
    這個網頁的 x, y 定位方式與 xcode 相同,都是最右上角定為 (0,0),故不需事先翻轉圖片或是換算 x,y 軸,定位出來的座標即可直接使用。同時,圖片上所有查詢過的定位點都會留存紀錄,方便後續查看。

2. Color Picker:
找出圖片 RGB 色號。

Preview

歐盟旗幟的元素為藍色底圖 + 12 顆星星,故預計步驟如下:

  1. 建立藍色底圖
  2. 繪製第一顆星星
  3. 建立繪製星星的 function,並設置可調整的座標參數
  4. 使用 function 建立 12 顆星星
  5. 使 12 顆星星成為藍色底圖的 subviews

預計圖層安排:

藍色底圖view
星星view
星星view
...
...
星星 view

Steps

1. 建立藍色底圖

建立旗幟尺寸的 UIView ,並設置其property backgroundColor

import UIKit

//背景底色
let euFlag = UIView(frame: CGRect(x: 0, y: 0, width: 610, height: 406))
euFlag.backgroundColor = UIColor(red: 0/255, green: 48/255, blue: 154/255, alpha: 1)

2. 繪製第一顆星星

這裡會使用到兩個新的 Type — UIBezierPathCAShapeLayer

目前已接觸到三種 Type 開頭的簡寫— UI、CA、CG。這三者部分功能上很相像、但又不能直接相互使用 (例如 UIColor 及 CGColor、UIBezierPath 及 CGPath)。查詢這三者的介紹如下:

  • UIKit框架(縮寫為UI):
    UIKit框架是iOS開發中最常用的框架之一,它是用於創建iOS界面和應用程序的框架。UIKit提供了大量的UI元素,例如按鈕、標籤、視圖等等,這些元素都是基於Core Animation和Core Graphics實現的。也就是說,UIKit中的UI元素通常都是基於Core Animation或Core Graphics的一些底層元素實現的,例如UIView是基於CALayer實現的。
  • Core Animation框架(縮寫為CA):
    Core Animation框架是一個用於iOS和macOS平台的高性能動畫框架。它提供了一個基於時間的繪圖系統,可以用於創建各種類型的動畫效果,例如緩慢的溶解、彈跳效果等等。Core Animation框架主要是通過CALayer來實現動畫效果的。
  • Core Graphics框架(縮寫為CG):
    Core Graphics框架是iOS開發中的圖形框架,它提供了一個底層的2D圖形繪製API,可以用於創建和操作各種類型的圖形。Core Graphics框架主要是通過CGContext來實現的。

單純的文字說明還是有點飄渺,故請 ChatGPT 協助舉例說明這三者的關聯與區別:

我們可以用一本立體書來說明 UIKit、Core Animation 和 Core Graphics 這三種框架的作用和區別。

首先,我們可以把這本立體書看作是我們的應用程序。UIKit 就像是這本書的外觀,包括封面、頁面的排版和文字等等,它負責創建整個書的基本外觀和交互元素,例如按鈕、文本框等。

接下來,我們可以使用 Core Animation 來給這本書添加一些動畫效果。例如,我們可以在書的某個頁面上添加一個動畫,當讀者翻開這個頁面時,它會以某種方式展開或變形。這樣可以讓讀者更加有趣地閱讀書籍。

最後,我們可以使用 Core Graphics 來添加一些圖案和圖形效果。例如,我們可以在某個頁面上繪製一個立體的房子或者花園,讓讀者感受到一種立體的視覺效果。

總體而言,UIKit、Core Animation 和 Core Graphics 這三種框架的作用和區別可以通過這本立體書的外觀、動畫和圖案效果來形象地展示出來。

了解這三種框架後,接下來再進一步學習這次作業會用到的兩個新 Type — UIBezierPathCAShapeLayer

  • CAShapeLayer (供繪製的向量圖層)

大概理解 UI 及 CA 的區別後,對於此次用到的 CALayer 及一直在使用的 UIView有點影影綽綽的概念了。依然請 ChatGPT 協助舉例更具體的說明:

- UIView 和 CALayer之間的關係就像一張白紙和透明紙的關係。你可以在白紙上畫一些基本圖形,但如果你想讓它們更加生動和有趣,你就需要使用透明紙。在 iOS 應用程序中,使用 UIView 來創建介面元素,使用 CALayer 來添加細節和動畫效果。

- UIView 就像一個人,負責處理視圖的佈局和事件處理等任務,而CALayer 就像一件衣服,負責處理視圖的繪製、動畫和其他視覺效果等任務。當你改變 UIView 的佈局時,CALayer 會自動更新,以反映這些改變。

而 CAShaperLayer 則是 CALayer 的一個子類 (subtype),它可以處理更複雜的幾何形狀,如線條、曲線、多邊形等等。它可以通過設置路徑來創建這些形狀,並提供了更多的控制選項,如線條寬度、線條顏色、填充顏色等等。同時,CAShapeLayer 也支持圖層遮罩和透明度等效果。

此次作業使用到兩種 Layer property:

  1. path : 繪圖路徑,Type 為 CGPoint
  2. fillColor :填充顏色,Type 為 CGColor。
  • UIBezierPath (繪製出幾何圖形的路徑)

UIBezierPath 是 UIKit 框架中的一個 Type,用來描述 2D 圖形的形狀和線條。當我們想在 UIView 上繪製一些自定義的形狀或線條時,可以使用 UIBezierPath 來描述它們。然而,雖然 UIBezierPath 是 UIKit 框架的一部分,但是它描述的是一個二維的路徑,而 UIView 對應的 Core Animation layer 需要的是一個形狀,因此需要把 UIBezierPath 轉換成對應的形狀,也就是 CAShapeLayer,然後再把 CAShapeLayer 添加到 UIView 對應的 layer上。

這裡用到 UIBezierPath 的三種的 method:

  1. move(to: CGPoint) :定義起始點座標。
  2. addLine(to: CGPoint) :從上一個落點畫線連接至下一個落點。
  3. close():從 current point 畫一條直線回到起始點。

* 雖然 UIBezierPath 都是用 CGPoints 繪製的,但它的作用是提供創建和管理路徑的方法,故仍屬於 UIKit 框架、而非 Core Graphics。因此,我們需要先將 UIBezierPath 轉換為 CAShapeLayer,然後再把 CAShapeLayer 添加到 UIView 的 layer 中,才能在 UIView 上顯示出這些形狀和線條。

接下來便要使用這些新學的用法進行星星的繪製。

  1. 單顆星星大小為 42*42,故先建立一個相同尺寸的 UIView (取名為 starView) ,以便進行座標定位。

2. Initialize 繪製路徑 UIBezierPath() (取名為 star)。

3. 使用 UIBezierPath 的 methods: move(), addLine(), colse() 連結 CGPoints 組成線段。

4. Initialize CAShapeLayer() (取名為 starLayer)。

5. 定義 CAShapeLayer 的 property path 。需先將 UIBezierPath 轉換為 CGPath 框架,才能將路徑放上 CAShapeLayer

6. 在 layer 上進行顏色填充。

7. 最後,將 starLayer 增添為 starView Layer 的 sublayer。

// star
var starView = UIView(frame: CGRect(x: 285, y: 46, width: 42, height: 42))

let star = UIBezierPath()

star.move(to: CGPoint(x: 21, y: 0))
star.addLine(to: CGPoint(x: 16, y: 16))
star.addLine(to: CGPoint(x: 0, y: 16))
star.addLine(to: CGPoint(x: 12, y: 26))
star.addLine(to: CGPoint(x: 8, y: 42))
star.addLine(to: CGPoint(x: 21, y: 33))
star.addLine(to: CGPoint(x: 34, y: 42))
star.addLine(to: CGPoint(x: 30, y: 26))
star.addLine(to: CGPoint(x: 42, y: 16))
star.addLine(to: CGPoint(x: 26, y: 16))
star.close()

let starLayer = CAShapeLayer()

starLayer.path = star.cgPath

starLayer.fillColor = CGColor(red: 255/255, green: 205/255, blue: 0, alpha: 1)

starView.layer.addSublayer(starLayer)

starView
UIBezierPath 繪製階段預覽
放上 View 之後的預覽

*在 Playground 繪製 UIBezierPath 預覽會發現圖形是倒過來的,因 UIBezierPath 的座標原點 (0, 0) 位於左下角。待路徑轉會為 View Type 之後會正常顯示。

3. 建立繪製星星的 function,並設置可調整的座標參數

設置好星星 view 之後,只要把它復製成 12 份、分別設定定位就可以啦!不過若是手動複製步驟二的所有 code 再一一修改座標,整個頁面會非常非常長、也容易有出錯的風險。故較簡便的方式為:將步驟二的 code 合併成一個 Function。

這個 Function 的目標是產出 “starView”,而其 Type 為 “UIView”。故基本的 Function 寫法應為:

func makeStarView () -> UIView {
...
...
return starView
}

再將步驟二的所有 code 複製貼入此一 function 即可。不過,因每顆星星在旗幟上的位置不同,故特別於此 function 中設置兩個參數: xcoordinateycoordinate ,以方便公式運行時可直接進行座標定位。

完整公式如下:

//function to make star views
func makeStarView(xcoordinate: Int, ycoordinate: Int) -> UIView{
var starView = UIView(frame: CGRect(x: xcoordinate, y: ycoordinate, width: 42, height: 42))
let star = UIBezierPath()
star.move(to: CGPoint(x: 21, y: 0))
star.addLine(to: CGPoint(x: 16, y: 16))
star.addLine(to: CGPoint(x: 0, y: 16))
star.addLine(to: CGPoint(x: 12, y: 26))
star.addLine(to: CGPoint(x: 8, y: 42))
star.addLine(to: CGPoint(x: 21, y: 33))
star.addLine(to: CGPoint(x: 34, y: 42))
star.addLine(to: CGPoint(x: 30, y: 26))
star.addLine(to: CGPoint(x: 42, y: 16))
star.addLine(to: CGPoint(x: 26, y: 16))
star.close()

let starLayer = CAShapeLayer()
starLayer.path = star.cgPath
starLayer.fillColor = CGColor(red: 255/255, green: 205/255, blue: 0, alpha: 1)

starView.layer.addSublayer(starLayer)
return starView
}

4. 使用 function 建立 12 顆星星

  • 開始運行步驟三建立好的 function,並直接輸入每顆星星的座標定位:
//create 12 stars
let star1 = makeStarView(xcoordinate: 285, ycoordinate: 46)
let star2 = makeStarView(xcoordinate: 216, ycoordinate: 64)
let star3 = makeStarView(xcoordinate: 168, ycoordinate: 114)
let star4 = makeStarView(xcoordinate: 149, ycoordinate: 180)
let star5 = makeStarView(xcoordinate: 168, ycoordinate: 246)
let star6 = makeStarView(xcoordinate: 216, ycoordinate: 298)
let star7 = makeStarView(xcoordinate: 285, ycoordinate: 316)
let star8 = makeStarView(xcoordinate: 353, ycoordinate: 298)
let star9 = makeStarView(xcoordinate: 402, ycoordinate: 246)
let star10 = makeStarView(xcoordinate: 420, ycoordinate: 180)
let star11 = makeStarView(xcoordinate: 402, ycoordinate: 114)
let star12 = makeStarView(xcoordinate: 353, ycoordinate: 64)

5. 使 12 顆星星成為藍色底圖的 subviews

addSubview:

//add starviews to background
euFlag.addSubview(star1)
euFlag.addSubview(star2)
euFlag.addSubview(star3)
euFlag.addSubview(star4)
euFlag.addSubview(star5)
euFlag.addSubview(star6)
euFlag.addSubview(star7)
euFlag.addSubview(star8)
euFlag.addSubview(star9)
euFlag.addSubview(star10)
euFlag.addSubview(star11)
euFlag.addSubview(star12)

完成啦~

GitHub 交作業:

--

--