SwiftUI: ListのDividerが気に入らない件
List
の項目間に自動で引かれるセパレータというかDivider
ですが、こいつの動きが気に入らないケースがあります。
例えば以下のようなケース。
リストの多くの項目が左寄せテキストで、一部、中央寄せボタンとかあるというようなパターンです。
Divider
は1個上の項目のテキストの頭から引かれているようで、中央寄せのボタンがあると、そこだけ左端が内側に入ったようになってしまいます。
このケースのソースは以下のような感じになります。
struct ContentView: View {
var body: some View {
List {
Text("説明の題字")
.font(.title)
Text("そして説明の文言。\n長かったり、改行したり。")
HStack {
Spacer()
Button(action: {}, label: {
Text("その上で何らかのボタン!")
})
.buttonStyle(.bordered)
Spacer()
}
Text("※ それでもって、補足説明とか")
.font(.footnote)
}
}
}
これを以下のように直したいわけです。
で、このDivider
の位置調整は、.alignmentGuide
モディファイアを使えばできるということです。
struct ContentView: View {
var body: some View {
List {
Text("説明の題字")
.font(.title)
Text("そして説明の文言。\n長かったり、改行したり。")
HStack {
Spacer()
Button(action: {}, label: {
Text("その上で何らかのボタン!")
})
.buttonStyle(.bordered)
Spacer()
}
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 } /// ← ここですね
Text("※ それでもって、補足説明とか")
.font(.footnote)
}
}
}
クロージャで0を返すと他のテキストと揃います。
ただ、見た感じ、中途半端という雰囲気が否めないのも確かなので、いっそ幅いっぱいに引きたい、という話もあるでしょう。
つまり↓の感じです。
あんまり良い方法が分からなかったのですが、とりあえず以下のようにすればできます。
struct ContentView: View {
var rowItemInsets = EdgeInsets(top: 8, leading: 20, bottom: 8, trailing: 20)
var body: some View {
List {
Text("説明の題字")
.font(.title)
.listRowInsets(rowItemInsets)
.alignmentGuide(.listRowSeparatorLeading) { _ in -rowItemInsets.leading}
Text("そして説明の文言。\n長かったり、改行したり。")
.listRowInsets(rowItemInsets)
.alignmentGuide(.listRowSeparatorLeading) { _ in -rowItemInsets.leading}
HStack {
Spacer()
Button(action: {}, label: {
Text("その上で何らかのボタン!")
})
.buttonStyle(.bordered)
Spacer()
}
.listRowInsets(rowItemInsets)
.alignmentGuide(.listRowSeparatorLeading) { _ in -rowItemInsets.leading}
Text("※ それでもって、補足説明とか")
.font(.footnote)
.listRowInsets(rowItemInsets)
.alignmentGuide(.listRowSeparatorLeading) { _ in -rowItemInsets.leading}
}
}
}
.listRowInsets
モディファイアはリストの各項目を親の枠からどんだけ引っ込めるかを設定する働きをします。
.alignmentGuide
のクロージャの引数にインセットが入って来て良さそうなものなのですが、実際には幅・高さしか入っておらずインセットが分からないため、自分でインセットを指定しておいてその値を使います。
struct ListRowFullWidthSeparatorModifier : ViewModifier {
@ScaledMetric private var verticalInset: CGFloat = 8
@ScaledMetric private var horizontalInset: CGFloat = 20
func body(content: Content) -> some View {
let rowItemInsets = EdgeInsets(top: self.verticalInset, leading: self.horizontalInset, bottom: self.verticalInset, trailing: self.horizontalInset)
content
.listRowInsets(rowItemInsets)
.alignmentGuide(.listRowSeparatorLeading) { _ in -rowItemInsets.leading}
}
}
extension View {
func listRowFullWidthSeparator() -> some View {
self.modifier(ListRowFullWidthSeparatorModifier())
}
}
こんな感じのカスタムモディファイアを定義すると、以下のように書けます。
struct ContentView: View {
var body: some View {
List {
Text("説明の題字")
.font(.title)
.listRowFullWidthSeparator()
Text("そして説明の文言。\n長かったり、改行したり。")
.listRowFullWidthSeparator()
HStack {
Spacer()
Button(action: {}, label: {
Text("その上で何らかのボタン!")
})
.buttonStyle(.bordered)
Spacer()
}
.listRowFullWidthSeparator()
Text("※ それでもって、補足説明とか")
.font(.footnote)
.listRowFullWidthSeparator()
}
}
}
ちなみに@ScaledMetric
は、「設定」App→「画面表示と明るさ」→「テキストサイズを変更」か、「設定」→「アクセシビリティ」→「画面表示とテキストサイズ」→「さらに大きな文字」でテキストサイズを変更したときに自動でスケーリングされる値です。
こうしておくと、文字のサイズを変えたときにリスト項目の間隔も自動で広がります。