#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/calculator 或 https://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: