🎯
SwiftUIで箇条書きを実現する
UIKitで箇条書きを実現する際、NSParagraphStyle
のheadIndent
を使ってNSAttributedString
を整形することになります(本記事ではやり方については触れません)。
しかし、SwiftUIでは上記に該当するAPIがありません[1]。
試しに、普通に複数行のテキストを箇条書きっぽく実装してみます。
VStack(spacing: 4) {
Text("Hello world!")
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
// 通常テキスト
Text("""
・foo
・Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
・bar
""")
// Markdown形式
Text(.init("""
- [foo](https://foo.bar)
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
- **bar**
"""))
}
.font(.body)
.padding()
.background {
Color.gray
}
結果は次の通りです。
Markdown形式も試してみましたが、SwiftUIのMarkdownが箇条書きに未対応[2]のため正しく反映されていないことが分かります。
そこで、試行錯誤の結果Label
のicon
にImage以外もセットできることからicon
に箇条書きのドット(・
)を埋め込んでみました。
VStack(spacing: 4) {
Text("Hello world!")
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
Label {
Text("foo")
} icon: {
Text("・")
}.frame(maxWidth: .infinity, alignment: .leading)
Label {
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
} icon: {
Text("・")
}.frame(maxWidth: .infinity, alignment: .leading)
Label {
Text("bar")
} icon: {
Text("・")
}.frame(maxWidth: .infinity, alignment: .leading)
}.font(.body)
.padding()
.background {
Color.gray
}
結果は以下の通りです。
それっぽく見えるようになったことが分かります。
ただ、これを毎回書くと面倒なのでまとめちゃいますw
public struct BulletPointsText: View {
var items: [AttributedString]
var spacing: CGFloat
var bullet: String
public init(bullet: String = "・",
spacing: CGFloat = 4,
_ items: [AttributedString]) {
self.items = items
self.spacing = spacing
self.bullet = bullet
}
public init(bullet: String = "・",
spacing: CGFloat = 4,
_ items: [String]) {
self.items = items.map(AttributedString.init)
self.spacing = spacing
self.bullet = bullet
}
public var body: some View {
VStack(spacing: spacing) {
ForEach(items, id: \.self) { item in
Label {
Text(item)
} icon: {
Text(bullet)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
public extension BulletPointsText {
init(bullet: String = "・",
spacing: CGFloat = 4,
_ items: AttributedString...) {
self.init(bullet: bullet,
spacing: spacing,
items)
}
init(bullet: String = "・",
spacing: CGFloat = 4,
_ items: String...) {
self.init(bullet: bullet,
spacing: spacing,
items)
}
}
BulletPointsText(
"foo",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"bar"
)
これである程度スッキリ書けるようになったかなと思います。
ただし、iOS16以上ならGridView
が使えるのでそれで実現できるかもしれません。
-
2023年8月17日現在 ↩︎
Discussion