SwiftUIで角丸のボーダーを実装する際、ScrollViewなどで角丸ボーダーViewを呼び出すとボーダーが見切れてしまう問題に遭遇したことはないでしょうか。
角丸ボーダーの基本的な実装
まず、SwiftUIで角丸のボーダーを実装します。
このコードで角丸ボーダーを作ることができます。
.overlay(RoundedRectangle(cornerRadius: 8).stroke(.red, lineWidth: 1))
うーん!ちゃんと作成できてそうですね🎉
角丸ボーダーの実装については、こちらの記事で詳しく解説されています!
しかし、これをScrollViewやLazyVGridなどで呼び出すとレイアウト崩れが起きてしまうのです。
ボーダーViewが見切れてしまう
早速ボーダーViewをScrollView内で呼び出してみます。
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 16) {
ForEach(0..<10) { _ in
ListContentView()
}
}
}
.scrollIndicators(.hidden)
}
}
private struct ListContentView: View {
var body: some View {
Text("Hello, world!")
.padding(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(.red, lineWidth: 1)
)
}
}
上記のコードを描画したものがこちらです。
少し分かりにくいかもですが、上下のボーダーが見切れてしまっているのが伝わるでしょうか。
解決方法
結論から書きますが.strokeの代わりに.strokeBorderモディファイアを利用することで回避できます。
private struct ListContentView: View {
var body: some View {
Text("Hello, world!")
.padding(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(.red, lineWidth: 1) // ここを置き換える
)
}
}
仕組みを理解する
Appleの公式ドキュメントによると、.strokeBorderは以下のような仕組みで動作します。
Returns a view that is the result of insetting self by style.lineWidth / 2, stroking the resulting shape with style, and then filling with the foreground color.
日本語訳:
style.lineWidth / 2分だけselfを内側に縮小し、その結果得られた図形をstyleでストロークし、フォアグラウンドカラーで塗りつぶしたビューを返します。
なるほど、つまり↓ということだと思います。
- 例えば半径100の円があるとする
- この半径98の円に線幅4の線を描くとする
- style.lineWidth / 2分だけselfを内側に縮小 = 円を4 ÷ 2 = 2だけ内側に縮小 → 半径98の円になる
- 結果、線は半径内側2pt(97,98)と外側2pt(99,100)の範囲に描画されるので、完全に元の円の内側に収まる!☝️
まとめ
-
.stroke()は境界線を中心として内外に描画するため、はみ出す可能性がある -
.strokeBorder()は事前に図形を縮小してから描画するため、完全に内側に収まる - ScrollViewなどでボーダーが見切れる場合は
.strokeBorder()を使用する
余談
この問題は下記のようにボーダー分の.paddingを追加で付与することでも解決はできますが、.strokeBorderの方がよりシンプルで直感的な解決方法と言えるでしょう。
private struct ListContentView: View {
var body: some View {
Text("Hello, world!")
.padding(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(.red, lineWidth: 1)
)
.padding(1) // ボーダーの分余白
}
}


