#22 Xcode-用UIBezierPath畫圓形、矩形、圓角矩形

目錄

⦿ 不規則圖案與規則圖案
⦿ UIBezierPath(ovalIn:
⦿ UIBezierPath(rect:
⦿ print path
⦿ 加入填滿的漸層
⦿ UIBezierPath(roundedRect:
⦿ byRoundingCorners設定圖片部分圓角

不規則圖案與規則圖案

前篇文章中,使用 UIBezierPath 畫了楓葉這類不規則形狀,我們應用 path 的移動( move )與畫線( addLine )來達成目的,但在 UIBezierPath 中也有許多寫好供我們代入參數的函式,如 UIBezierPath(roundedRect: CGRect, cornerRadius: CGFloat) ,是用來畫圓角矩形這類規則圖案的函式,我們試著揭開它的面紗吧。

UIBezierPath ( ovalIn:

在這個函式裡,oval 即是橢圓,需代入 CGRect 來畫出這個曲線,我們在 viewDidLoad() 中加入如下:

        let bounds = UIScreen.main.bounds        
let path = UIBezierPath(ovalIn: CGRect(x: bounds.midX - 100,
y: bounds.midY - 50,
width: 200,
height: 100))

// 外框
let shape = CAShapeLayer()
shape.path = path.cgPath
shape.lineWidth = 8.0
shape.fillColor = UIColor.orange.cgColor
shape.strokeColor = UIColor.black.cgColor
view.layer.addSublayer(shape)

從 width、height 可看出,這將會是一個躺著的 2D 鵝蛋形,在 CGRect 中,x 與 y 表示你要從哪裡開始畫這個橢圓,我們希望在中間去畫,所以取用 UIScreen.main.bounds 去找它的 midX 與 midY,如果直接用 midX、midY,你的圖會偏右下,所以我們分別要減去寬、高的一半,來讓圖案置中。

調整 x、y 的位置讓圖案置中

不過,若只是設定曲線,畫面上是不會有任何反應的,我們永遠要記得設置 CAShapeLayer,再讓 layer 的 path 與 UIBezierPath 的 cgPath 相等,最後再把 layer 加到 view 中才完成。

為什麼不用 mask(遮罩) 呢?因為 view 是本來就存在的底圖,如果你直接用 mask 的話,會變成下圖:

不過,你當然可以另外加一個 view 進去,再使用遮罩去繪製它的新形狀,如下:

    private func setupView() {
let size = UIScreen.main.bounds.size
var leftView = UIView(frame: CGRect(x: 0,
y: 0,
width: size.width/2,
height: size.height/2))
leftView.backgroundColor = .systemBrown
view.addSubview(leftView)

var leftPath = UIBezierPath(ovalIn: CGRect(x: bounds.midX - 100,
y: bounds.midY - 50,
width: 200,
height: 100))

let leftShapeLayer = CAShapeLayer()
leftShapeLayer.path = leftPath.cgPath

leftShapeLayer.lineWidth = 8.0
leftShapeLayer.fillColor = UIColor.clear.cgColor
leftShapeLayer.strokeColor = UIColor.black.cgColor

leftView.layer.mask = leftShapeLayer
}

一樣地,把它寫成 function,先在 view 中放入一個褐色的 view,還記得文章前面的橢圓是置中的嗎?

所以這個褐色 view 以橢圓為 mask(遮罩),就會變成下面這個形狀:

左邊新放入的褐色 view;右邊是這個 view 以橢圓貝茲曲線為遮罩
繼續閱讀|回目錄

UIBezierPath ( rect:

在這個函式中,畫出來的曲線是矩形,我們加入下面的程式碼:

        let path = UIBezierPath(rect: CGRect(x: bounds.midX - 50,
y: bounds.midY - 50,
width: 100,
height: 100))

會得到這個結果:

print path

回到文章前面的程式碼,當我們使用的是 UIBezierPath( rect: ,如果把 UIBezierPathshape.path 印出來會得到結果如下:

<UIBezierPath: 0x600003b9de00; <MoveTo {164, 413}>,
<LineTo {264, 413}>,
<LineTo {264, 513}>,
<LineTo {164, 513}>,
<Close>

Optional(Path 0x600002790a20:
moveto (164, 413)
lineto (264, 413)
lineto (264, 513)
lineto (164, 513)
closepath
)

UIBezierPath 是如何畫出矩形的?我們看到它先移動到 164, 413 即是矩形左上角的點,矩形邊長是 100,所以接下來 LineTo 依序是 264, 413264, 513164, 513,也就是順時針畫完這個矩形。

由於 shape.path 是 CGPath,所以長相跟 UIBezier 有點不一樣,並且它是 Optional。

我們再印出 UIBezierPath(ovalIn: 看看,得到如下結果:

<UIBezierPath: 0x600003d1ac00; <MoveTo {314, 463}>,
<CurveTo {214, 513} {314, 490.61423748999999} {269.22847497999999, 513}>,
<CurveTo {114, 463} {158.77152502000001, 513} {114, 490.61423748999999}>,
<CurveTo {214, 413} {114, 435.38576251000001} {158.77152502000001, 413}>,
<CurveTo {314, 463} {269.22847497999999, 413} {314, 435.38576251000001}>,
<Close>

Optional(Path 0x600002118900:
moveto (314, 463)
curveto (314, 490.614) (269.228, 513) (214, 513)
curveto (158.772, 513) (114, 490.614) (114, 463)
curveto (114, 435.386) (158.772, 413) (214, 413)
curveto (269.228, 413) (314, 435.386) (314, 463)
closepath
)

我們可以看到畫橢圓形時,是先移動到中間右邊的點 314, 463,我們忽略 CurveTo 的其他點,只關注第一個點,即依序移動到中間下面 214, 513中間左邊 114, 463中間上面 214, 413,最後回到 314, 463,一樣是順時針的方式,但起點與矩形不同。

繼續閱讀|回目錄

加入填滿的漸層

既然已經能藉由 UIBezierPath 去繪出想要圖形,我們試著在鵝蛋形狀中加入漸層效果,於之前的程式碼後面,再加入一些程式碼,如下:

        // 設定漸層
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [UIColor.red.cgColor,
UIColor.green.cgColor]

// 設定遮罩
let shapeMask = CAShapeLayer()
shapeMask.path = path.cgPath
gradient.mask = shapeMask

view.layer.addSublayer(gradient)

設定 gradient 的 frame 是 view 的 bounds,即在螢幕範圍內畫出漸層,然而你不使用遮罩的話,將會是整個螢幕由紅色過渡到綠色;如果你希望保留黑邊,view.layer.addSublayer(shape) 就得留著;如果註解掉 view.layer.addSublayer(shape) 就會變成沒有黑邊的漸層,如下:

美中不足的是這漸變的顏色並不明顯,因為漸變的範圍太大了,我們只圈住中間範圍。

繼續閱讀|回目錄

UIBezierPath ( roundedRect:

貝茲曲線畫圓角矩形的函式有兩個,我們先看其中一種,將 path 改為下面這樣:

        let path = UIBezierPath(roundedRect: CGRect(x: bounds.midX - 50,
y: bounds.midY - 50,
width: 100,
height: 100),
cornerRadius: 20)

這很好理解,roundedRectCGRect 跟畫矩形時一樣,只要設定後方的 cornerRadius(圓角半徑)就好,結果如下圖左:

如果你不想要有圓角,cornerRadius 設定為 0 就好了嗎?很可惜的,這會像上圖右一樣有個缺角。

我們再看看另個設定圓角矩形的函式,將 path 改為下面這樣:

        let path = UIBezierPath(roundedRect: CGRect(x: bounds.midX - 100,
y: bounds.midY - 50,
width: 200,
height: 100),
byRoundingCorners: [
.topLeft,
.bottomLeft,
.topRight,
.bottomRight
],
cornerRadii: CGSize(width: 20, height: 0))

cornerRadii 實測 CGSize 裡只有 width 會影響圓角,如果我 width 跟 height 都設為 0,就會得到下面第三張圖,一樣是缺角的矩形:

這個圓角矩形的函式中,byRoundingCorners 最好玩,因為它可以設定你想要哪邊圓角,當我註解掉 .topRight.bottomRight 時,就會變成上面第一張圖,第二張圖則是正常情況下的圓角矩形了。

繼續閱讀|回目錄

byRoundingCorners 設定圖片部分圓角

在學會 UIBezierPath 如何畫出部分圓角,我們可將之應用在圖片的部分圓角,首先,加入 Cocoa Touch Class 選擇 UIImageView 來客製我們要的 imageView。程式碼如下:

class RoundedCornerImageView: UIImageView {
var corners: UIRectCorner = .allCorners
var width: Double = 10.0

override func layoutSubviews() {
super.layoutSubviews()

let bezierPath = UIBezierPath(roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: width,
height: 0))
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
layer.mask = shapeLayer
}
}

覆寫 layoutSubviews(),加入貝茲曲線及遮罩,設定 class 的兩個屬性,corners 和 width,分別放入 byRoundingCornerscornerRadii 中,兩個屬性皆需經過初始化。

接著你在 storyboard 中放入的 UIImageView 需讓它從屬於 RoundedCornerImageView,記得與 ViewController 繫結,如此才能在 viewDidLoade() 中改變屬性。

最後結果如下:

上圖左是預設的結果,上圖右是在 viewDidLoad() 中加入下面這段程式碼:

        imageView.corners = [.topRight, .topLeft]
imageView.width = 20

圓角變得更明顯,但只剩下左上跟右上有圓角。

這邊要注意的是,因為放入 imageView 的 image 可能沒填滿,記得要讓它填滿,否則切圓角的效果可能會出不來。

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

繼續閱讀|回目錄

附上 GitHub 連結:

Reference:

--

--

Chun-Li 春麗
彼得潘的 Swift iOS / Flutter App 開發教室

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.