#10 Xcode-用UIBezierPath從程式繪製國旗、可愛圖案

目錄

⦿ 土法煉鋼,一步一步來
⦿ Return UIView
⦿ CGAffineTransform
⦿ UIBezierPath

土法煉鋼,一步一步來

在 Xcode 裡,想繪製出像下面的圖案,UIKit 中有個 UIBezierPath 可用。

第一張圖的程式碼如下:

    private func setupOriginal() {
let size = UIScreen.main.bounds.size
var rect = UIView(frame: CGRect(x: 0,
y: 0,
width: 350,
height: 250))
rect.center = CGPoint(x: size.width/2, y: size.height/2)
rect.backgroundColor = UIColor.white

self.view.addSubview(rect)

// 日本國旗中心紅圓
var circle = UIView(frame: CGRect(x: 0,
y: 0,
width: 250,
height: 250))
circle.center = CGPoint(x: rect.bounds.width/2, y: rect.bounds.height/2)
circle.backgroundColor = UIColor.red
circle.layer.cornerRadius = 125
rect.addSubview(circle)
}

我們將欲加入的圖形寫入 function 中,再在 viewDidLoad() 去呼叫這個 function,一個白色背景的矩形加入一個紅色背景的圓,就成了偽日本國旗

再來,二三張圖,我們先繪製矩形外圍,程式碼如下:

        let size = UIScreen.main.bounds.size
var rect = UIView(frame: CGRect(x: 0,
y: 0,
width: 350,
height: 250))
rect.center = CGPoint(x: size.width/2, y: size.height/2)
rect.backgroundColor = UIColor.white
rect.layer.borderWidth = 15
rect.layer.borderColor = CGColor(red: 42/255, green: 43/255, blue: 42/255, alpha: 1)
self.view.addSubview(rect)

變更 rect.layer.borderColor = CGColor() 可得到不同的描邊顏色,如下:

我們再來繪製內部直的、紅色的矩形,加入下面這段程式碼:

        func addAdd() {
var factor = CGRect(x: 0, y: 0, width: 40, height: 250)
let redBack1 = UIView(frame: factor)
redBack1.center = CGPoint(x: rect.bounds.width/2, y: rect.bounds.height/2)
redBack1.backgroundColor = UIColor.red
rect.addSubview(redBack1)

factor = CGRect(x: 45 , y: 25, width: 20, height: 60)
let redBack3 = UIView(frame: factor)
redBack3.backgroundColor = .red
rect.addSubview(redBack3)
factor = CGRect(x: 45, y: 165, width: 20, height: 60)
let redBack5 = UIView(frame: factor)
redBack5.backgroundColor = .red
rect.addSubview(redBack5)
factor = CGRect(x: 280 , y: 25, width: 20, height: 60)
let redBack7 = UIView(frame: factor)
redBack7.backgroundColor = .red
rect.addSubview(redBack7)
factor = CGRect(x: 280 , y: 165, width: 20, height: 60)
let redBack9 = UIView(frame: factor)
redBack9.backgroundColor = .red
rect.addSubview(redBack9)
}

我們只用一個 factor 去改變它的 CGRect,去得到各種位置的 view,也別忘了將這些長條矩形加入 rect 中,得到如下結果:

我們接著再加入下面的程式碼:

            factor = CGRect(x: 0, y: 0, width: 350, height: 40)
let redBack2 = UIView(frame: factor)
redBack2.backgroundColor = .red
redBack2.center = CGPoint(x: rect.bounds.width/2, y: rect.bounds.height/2)
rect.addSubview(redBack2)
factor = CGRect(x: 25, y: 45, width: 60, height: 20)
let redBack4 = UIView(frame: factor)
redBack4.backgroundColor = .red
rect.addSubview(redBack4)
factor = CGRect(x: 260, y: 45, width: 60, height: 20)
let redBack6 = UIView(frame: factor)
redBack6.backgroundColor = .red
rect.addSubview(redBack6)
factor = CGRect(x: 25 , y: 185, width: 60, height: 20)
let redBack8 = UIView(frame: factor)
redBack8.backgroundColor = .red
rect.addSubview(redBack8)
factor = CGRect(x: 260, y: 185, width: 60, height: 20)
let redBack10 = UIView(frame: factor)
redBack10.backgroundColor = .red
rect.addSubview(redBack10)

便完成了偽喬治亞國旗

這邊要注意的是 addAdd() 寫在 setupOriginal() 裡面,所以也要記得在裡面呼叫。

繼續閱讀|回目錄

Return UIView

如果我們希望程式碼可以更靈活,可採用 func setupOriginal() -> UIView 的方式去撰寫 function,那麼,我們便不用急著將 rect 加入主 view 中,不過在 viewDidLoad() 中仍要記得這樣寫: view.addSubview(setupOriginal())

此時便可以用這個 function 傳出的 view 做各種變化了。

CGAffineTransform

如果要做到繪製出的圖案能夠縮放、移動與旋轉,在 UIKit 中,我們可以使用 CGAffineTransform,由於 setupOriginal() 會回傳 view,我們既然希望這個 view 可以搭配 CGAffineTransform,那麼,在 setupOriginal() 裡,我們還是把 rect 加到 view 裡面。

接著,就可以使用 CGAffineTransform.identity 來玩了,如下:

我們要先宣告 var angle = CGFloat.pi/180 這個變數。

再加入下面的程式碼:

   setupOriginal()
setupOriginal().transform = CGAffineTransform.identity.translatedBy(x: 0, y: 0).scaledBy(x: 0.7, y: 0.5).rotated(by: angle * 45)
setupOriginal().transform = CGAffineTransform.identity.translatedBy(x: 25, y: 120).scaledBy(x: 0.5, y: 0.5).rotated(by: angle * 90)
setupOriginal().transform = CGAffineTransform.identity.translatedBy(x: -25, y: -120).scaledBy(x: 0.5, y: 0.5).rotated(by: angle * 135)
setupOriginal().transform = CGAffineTransform.identity.translatedBy(x: 50, y: 200).scaledBy(x: 0.5, y: 0.5).rotated(by: angle * 180)
setupOriginal().transform = CGAffineTransform.identity.translatedBy(x: -50, y: -200).scaledBy(x: 0.5, y: 0.7).rotated(by: angle * 225)

會得到下面的結果:

繼續閱讀|回目錄

UIBezierPath

前面是宣告 view 的方式去繪製簡單的圖案,如果我們要畫出不規則的形狀就必須借助 UIBezierPath 了,如果你想繪製校徽也是沒問題的。

我們可以利用 https://www.geogebra.org/calculatorhttps://yangcha.github.io/iview/iview.html 這兩個網址來查圖中的點座標。

結果像上面這樣,這讓我想到一個數學問題,張三追著李四,張三每隔一陣子都追上他跟李四剩下距離的一半,張三還可以追到李四嗎?

不過,數學是很科學的,但是拿時間來拼這個,我覺得不太科學,所以還是從加拿大的楓葉著手吧。

我們也搭配 https://www.schemecolor.com/image-to-color-generator ,來查各色塊的顏色代碼,選用楓葉的話,自然就簡單地 .red 了。如下:

可以看到從 C 開始到 O,每個點座標都有了,繪圖時使用 CGFloat,這邊只取到小數後兩位,同樣套路程式碼如下:

    private func setupMapleLeaf() -> UIView {

let size = UIScreen.main.bounds.size

let redView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let mapleView = UIView(frame: redView.frame)
mapleView.backgroundColor = UIColor.red
redView.addSubview(mapleView)

let path = UIBezierPath()
path.move(to: CGPoint(x: 162.87, y: 14.05))
path.addLine(to: CGPoint(x: 150.53, y: 36.54))
path.addLine(to: CGPoint(x: 137.40, y: 29.70))
path.addLine(to: CGPoint(x: 143.47 , y: 67.85))
path.addLine(to: CGPoint(x: 126.06, y: 52.41))
path.addLine(to: CGPoint(x: 120, y: 60))
path.addLine(to: CGPoint(x: 100, y: 60))
path.addLine(to: CGPoint(x: 108.26, y: 78.65))
path.addLine(to: CGPoint(x: 100.26, y: 83.28))
path.addLine(to: CGPoint(x: 132.45, y: 110.39))
path.addLine(to: CGPoint(x: 128.48, y: 122.08))
path.addLine(to: CGPoint(x: 158.68, y: 116.79))
path.addLine(to: CGPoint(x: 158.68, y: 149.42))
path.addLine(to: CGPoint(x: 162.87, y: 149.42))
path.close()

let mapleShapeLayer = CAShapeLayer()
mapleShapeLayer.path = path.cgPath
mapleView.layer.mask = mapleShapeLayer
redView.center = CGPoint(x: size.width/2, y: size.height/2)
self.view.addSubview(redView)

return redView
}

首先,宣告一個 redView,讓 mapleView 的 frame 與之相等,然後宣告一個繪圖路徑,先 move 到欲繪製的起點,即是文章前段的 C 的座標,接著 addline,把線連到下一個點上,重複 addline 這動作。

此時還需宣告一個 mapleShapeLayer,讓這個 layer 的路徑與 path 相同,並將之作為 mapleView 的遮罩,結果如下:

怎麼才半張?不過沒關係,我們仍可以搭配前面的 CGAffineTransform 中的 scale(x: -1, y: 1) 來達成水平鏡像的效果。

viewDidLoad() 裡,我們這麼做:

    setupMapleLeaf()
setupMapleLeaf().transform = CGAffineTransform.identity.translatedBy(x: 25, y: 0).scaledBy(x: -1, y: 1)

又或者,我們可以在 setupMapLeaf() 中這樣做:

let moveDistance = path.bounds.maxX * 2
let transform = CGAffineTransform(translationX: moveDistance, y: 0).scaledBy(x: -1, y: 1)
mapleShapeLayer.setAffineTransform(transform)

第二行宣告轉換,第三行把轉換拿來用,第一行是設置偏移量,而鏡像便是讓 x 為 -1;y 為 1。最後結果:

如此加拿大楓葉便完成了。

這次分享就到這,感謝您的閱讀。

繼續閱讀|回目錄

附上 GitHub 連結:

Reference:

--

--

春麗 S.T.E.M.
春麗 S.T.E.M.

Written by 春麗 S.T.E.M.

Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage, against the dying of the light.