SwiftUIでは角丸を実現するために cornerRadius
というAPIが存在しますが、これは全ての角に角丸を適用するものであり残念ながら部分ごとに角丸を適用出来るものではありません。
本投稿ではSwiftUIの機能のみで部分角丸を実現させてみます。
実装方法
SwiftUIにはCoreGraphics同様のAPIを提供するPath
というモジュールが提供されています。
また、Viewに対してmask
を呼び出すことでmaskをかけることも出来ます。
これらの機能を合わせることで角丸を実現させます。
struct RoundedView: View {
let radius: CGFloat = 10
var body: some View {
Rectangle()
.fill(Color.black)
.frame(width: 200, height: 200)
// Viewに対してマスクをかける
.mask(
// マスク対象Viewのフレームにアクセス出来るようにGeometoryReaderを利用
GeometryReader { geo in
Path { path in
let frame = geo.frame(in: .local)
path.move(to: CGPoint(x: frame.minX, y: frame.midY))
// 各角に弧を描いていき、角丸にする
path.addArc(tangent1End: CGPoint(x: frame.minX, y: frame.minY),
tangent2End: CGPoint(x: frame.midX, y: frame.minY),
radius: self.radius)
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: frame.minY),
tangent2End: CGPoint(x: frame.maxX, y: frame.midY),
radius: self.radius)
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: frame.maxY),
tangent2End: CGPoint(x: frame.midX, y: frame.maxY),
radius: self.radius)
path.addArc(tangent1End: CGPoint(x: frame.minX, y: frame.maxY),
tangent2End: CGPoint(x: frame.minX, y: frame.midY),
radius: self.radius)
}
}
)
}
}
実用的にする
上記の例では全ての角毎にradiasを指定しなければならないので、使い勝手がよくありません。
Extensionを作ることで角毎に角丸が適用出来るようにします。
// 角丸対象の角
enum RoundableCorner {
/// 右上
case topLeading
/// 左上
case topTrailing
/// 右下
case bottomLeading
/// 左下
case bottomTrailing
}
extension View {
/// 指定した角を丸める
/// - Parameters:
/// - radius: 角丸の半径
/// - corner: 丸める角
/// - Returns: 角丸を適用したあとのView
func cornerRadius(_ radius: CGFloat, corner: RoundableCorner) -> some View {
self.cornerRadius(radius: CGFloat, corners: [corner])
}
/// 指定した複数の角を丸める
/// - Parameters:
/// - radius: 角丸の半径
/// - corner: 丸める角
/// - Returns: 角丸を適用したあとのView
func cornerRadius(_ radius: CGFloat, corners: Set<RoundableCorner>) -> some View {
self.mask(
GeometryReader { geo in
Path { path in
let frame = geo.frame(in: .local)
path.move(to: CGPoint(x: frame.minX, y: frame.midY))
path.addArc(tangent1End: CGPoint(x: frame.minX, y: frame.minY),
tangent2End: CGPoint(x: frame.midX, y: frame.minY),
radius: corners.contains(.topLeading) ? radius : 0)
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: frame.minY),
tangent2End: CGPoint(x: frame.maxX, y: frame.midY),
radius: corners.contains(.topTrailing) ? radius : 0)
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: frame.maxY),
tangent2End: CGPoint(x: frame.midX, y: frame.maxY),
radius: corners.contains(.bottomTrailing) ? radius : 0)
path.addArc(tangent1End: CGPoint(x: frame.minX, y: frame.maxY),
tangent2End: CGPoint(x: frame.minX, y: frame.midY),
radius: corners.contains(.bottomLeading) ? radius : 0)
}
}
)
}
}
上記のようなExtensionを定義しておくことで、部分角丸を指定したいときは以下のようなコード書くだけでよくなります。
struct Content: View {
var rectangle: some View {
Rectangle()
.fill(Color.black)
.frame(width: 100, height: 100)
}
var body: some View {
VStack {
Text("指定箇所にまとめて角丸を適用")
self.rectangle
.cornerRadius(10, corners: [.topLeading, .bottomTrailing])
Text("部分毎に異なる角丸を適用")
self.rectangle
.cornerRadius(30, corner: .topLeading)
.cornerRadius(10, corner: .bottomLeading)
}
}
}
これは以下のように表示されます。