はじめに
Listには色々種類がありますが、中でもInsetGroupedListStyle()
が気に入っています。
他にどんなUIのListがあるのかは、「【SwiftUI】Listの使い方」とかを確認ください。
SwiftUIで実装する上でいつも参考にさせていただいてます。
今回は、UIはInsetGroupedListStyle()
なのですが、SidebarListStyle()
のように開閉できるListを実装したかったので、それを紹介します。
SidebarListStyle()
をそのまま使用するとセルの右にchevronが存在すると思うのですが、そのアイコンや挙動もカスタマイズしたかったので、結局InsetGroupedListStyle()
を使用した実装になりました。
成果物
以下ができます。
- Sectionを押下した時に開閉する
- 開いているときと閉まっている時で、表示するアイコン(矢印)を変更する
- 開いているときと閉まっている時で、Section下の(コード上だとSectionの中になりますが)線の表示を切り替える
動画で見たい場合は、以下のGithubリポジトリで公開しているので、そのREADMEを確認ください。
実装
struct ContentView: View {
// テスト用データ(APIから取得したデータを格納したり、、今回は辞書を使っているので順番が保証されないのに注意)
private let testData = ["食べ物": ["明太子パスタ", "ショートケーキ", "プロシュート", "パウンドケーキ"], "飲み物": ["コーラ", "ルイボスティー"]]
// 選択されたSectionがどれかを格納しておくためのSet
@State private var selection: Set<String> = []
var body: some View {
let keys: [String] = testData.map { $0.key }
let values: [[String]] = keys.compactMap { testData[$0] }
VStack {
List {
ForEach(keys.indices) { keyIndex in
Section(header: sectionHeader(title: keys[keyIndex], isExpanded: self.selection.contains(keys[keyIndex]))) {
if self.selection.contains(keys[keyIndex]) {
ForEach(values[keyIndex].indices) { valueIndex in
Text(values[keyIndex][valueIndex])
.foregroundColor(Color.black)
}
}
}
}
}
.listStyle(InsetGroupedListStyle())
}
}
}
extension ContentView {
private func sectionHeader(title: String, isExpanded: Bool) -> some View {
Button(action: {self.selectDeselect(selectedSection: title)}) {
VStack {
HStack {
Text(title)
Spacer()
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
}
if !isExpanded {
Divider()
} else {
Divider()
.frame(height: 0)
}
}
}
.foregroundColor(Color.gray)
}
// Sectionが押下された時に、Setの中身を変更するメソッド
private func selectDeselect(selectedSection: String) {
if selection.contains(selectedSection) {
selection.remove(selectedSection)
} else {
selection.insert(selectedSection)
}
}
}
コメントにも書いてますが、今回は表示するデータの格納する型が、Dictionaryなので順番は保証されていないです。
順番を保証したい場合は、別のデータ型を使用しないといけないですが、今回はサンプルなのでご了承。。
Set型を準備して、そこに選択されたSectionの文字列を保持しています。
今回は「食べ物」「飲み物」とかが入ってますが、多分IDとかもっと一意っぽい値の方が良いとは思います。
参考にした記事
終わりに
まだまだSwiftUI勉強中の身なので、実はSidebarListStyle()
を使用してもっと上手くできるのかもしれないです。より良い方法をご存知の方、ぜひコメントで教えてください!