#21 運用 UIBezierPath 繪製可愛貓貓蟲咖波

Rose
彼得潘的 Swift iOS / Flutter App 開發教室
10 min readApr 9, 2021

我們要運用 UIBezierPath 來繪製可愛的「貓貓蟲咖波」

上圖是完成圖,雖看起來是線條簡單的插畫,程式碼卻很長很長啊~

說實在為了做這個作業我看了十幾二十篇的文章,這是一個需要耐心的作業。
因為每個線段手刻實在太耗時了,我如果用繪圖軟體做的話二三下就完成了,本來為了偷懶還想直接用 SVG Converter 把 SVG 檔轉成 Swift 的格式。

SVG Converter 把 SVG 檔轉為 Swift 語法的網站

但是!事情永遠沒有那麼簡單,轉簡單的 icon 線條還可以,但是「貓貓蟲咖波」線條太多轉出來都是壞掉的啊啊啊啊~
其實 fontawesome 下載的很多 SVG Icon 檔,透過 SVG Converter 轉存出來都是圖案破碎的。

只好回到原點乖乖拉線,做「貓貓蟲咖波」之前還做了很多熱身練習,例如畫三角形、畫西瓜、直線星星、曲線星星…,然後彼得潘的文章裡沒有畫曲線的星星過程,只有提供方法,只能自己研究摸索畫出的方式。

畫曲線有幾個方式,我用的是 addQuadCurve 來畫曲線星星。

構建路徑用到的幾種語法

  • func move(to: CGPoint) 將路徑的當前點移動到指定位置。
  • func addLine(to: CGPoint) 在路徑上附加一條直線。
  • func addArc(withCenter: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) 在路徑上附加一個弧線。(圓形)
  • func addCurve(to: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint) 在路徑上附加一條立方貝塞爾曲線。
  • func addQuadCurve(to endPoint:CGPoint, controlPoint:CGPoint) 在路徑上附加一條二次貝塞爾曲線
  • func removeAllPoints() 從路徑中刪除所有點,有效地刪除所有子路徑。
  • func append(UIBezierPath) 將指定路徑對象的內容附加到路徑中。
  • var cgPath: CGPath 路徑的核心圖形表示。
  • var currentPoint: CGPoint 圖形路徑中的當前點。

建立 SwiftUI App 專案

建立專案時,這次 Interface 選擇 SwiftUI。

在 ContentView.swift 貼上以下程式

將 ContentView.swift 原本的程式刪除,貼上以下程式。以下程式將利用 SwiftUI 幫我們預覽繪製的圖案。此次作業的重點在繪圖,所以不懂以下程式沒關係。

以下是基礎架構

import SwiftUI
struct DrawView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView()

return view
}

func updateUIView(_ uiView: UIView, context: Context) {
}

}
struct ContentView: View {
var body: some View {
DrawView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

選擇要模擬的「貓貓蟲咖波」圖片原檔

載入圖片在背景,方便描圖參考

let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 330, height: 210))
imageView.image = UIImage(named: "貓貓蟲咖波")
imageView.alpha = 0.0
view.addSubview(imageView)

描繪中

座標參考是把圖片匯入 Figma 取得轉折點座標, 再取得曲線位置的初步座標進去 Swift 調整,因為如果採用繪圖軟體的貝茲曲線座標,曲線會太扁,須在 Swift 慢慢微調

或是將圖片上傳到查詢座標顏色的網站 iview

參考圖片繪圖時最困難的莫過於如何得知圖片裡形狀的座標和顏色。不過別擔心,我們只要將圖片上傳,即可查詢圖片的座標顏色。(ps: 如果覺得圖片太小不容易查座標,可按 cmd + 將網頁放大。)

https://yangcha.github.io/iview/iview.html

整理需要單獨繪製的形狀

  • 二隻前腳另外畫,因為線條沒封包
  • 頭頂曲線
  • 眼睛
  • 嘴巴
  • 背上的藍線
  • 肚子的白色
  • 背景裝飾圖案三個曲線和音符和地平線

初步完成身體外框線條繪製

身體外框的程式碼

完整的程式碼請下載下方的 GitHub

// 曲線咖波
let path = UIBezierPath()
path.move(to: CGPoint(x: 118, y: 63))
path.addQuadCurve(to: CGPoint(x: 163, y: 56), controlPoint: CGPoint(x: 153, y: 20))
path.addQuadCurve(to: CGPoint(x: 173, y: 60), controlPoint: CGPoint(x: 168, y: 58))
path.addQuadCurve(to: CGPoint(x: 211, y: 75), controlPoint: CGPoint(x: 218, y: 26))
path.addQuadCurve(to: CGPoint(x: 229, y: 79), controlPoint: CGPoint(x: 216, y: 79))
path.addQuadCurve(to: CGPoint(x: 242, y: 86), controlPoint: CGPoint(x: 236, y: 72))
path.addQuadCurve(to: CGPoint(x: 235, y: 144), controlPoint: CGPoint(x: 246, y: 118))
path.addQuadCurve(to: CGPoint(x: 222, y: 153), controlPoint: CGPoint(x: 240, y: 161))
path.addQuadCurve(to: CGPoint(x: 203, y: 156), controlPoint: CGPoint(x: 211, y: 156))
path.addQuadCurve(to: CGPoint(x: 189, y: 154), controlPoint: CGPoint(x: 185, y: 171))
path.addQuadCurve(to: CGPoint(x: 164, y: 157), controlPoint: CGPoint(x: 175, y: 157))
path.addQuadCurve(to: CGPoint(x: 114, y: 143), controlPoint: CGPoint(x: 130, y: 155))
path.addQuadCurve(to: CGPoint(x: 118, y: 63), controlPoint: CGPoint(x: 66, y: 107))
// 填色
let capooLayer = CAShapeLayer()
capooLayer.path = path.cgPath
// 繪製完成前暫時不填色
capooLayer.fillColor = UIColor.clear.cgColor
capooLayer.strokeColor = CGColor(srgbRed: 90/255, green: 80/255, blue: 94/255, alpha: 1)
capooLayer.lineWidth = 5
view.layer.addSublayer(capooLayer)

調整線條端點形狀

最後,有一些開放式的線條端點都是直角的,我們要把它改成圓形

我們可以設定 CAShapeLayer 的 lineCap,調整線條端點的形狀,比方 .round 代表圓形。

percentageLayer.lineCap = .round

本來想讓咖波的腳可以動,但是那又是另一個坑,等以後比較熟悉再來改寫

血汗「貓貓蟲咖波」最終完成的樣子,感動😭

你這可惡又可愛的小東西,讓我花了那麼多時間還笑得這麼開心,果然不能小看咖波。

UIBezierPath 與原圖比較圖

GitHub網址

--

--

Rose
彼得潘的 Swift iOS / Flutter App 開發教室

Coding & Design 一直在學習的路上,從未停止,一有空檔就會摸摸我的兔子🐰