25
Nov
2022
SwiftUI Accordion View
Reading time: 2 min
This recipe shows how to implement an accordion view in SwiftUI. An accordion view is a layout that consists of a series of linked disclosure groups, only one of which can be expanded at the time.
The end result looks like this:
This solution works for SwiftUI 2+ (iOS 14+, macOS 11+).
Here's the recipe:
- An accordion is a series of vertically stacked
DisclosureGroup
s, which we'll refer to as sections. - We'll provide a way to customize the title label and the content view of each section.
- Assing a custom
Binding
to eachDisclosureGroup
will make sure only one group is expanded at the time.
struct AccordionView<Label, Content>: View
where Label : View, Content : View {
@Binding var expandedIndex: Int?
let sectionCount: Int
@ViewBuilder let label: (Int) -> Label
@ViewBuilder let content: (Int) -> Content
var body: some View {
VStack {
ForEach(0..<sectionCount, id: \.self) { index in
DisclosureGroup(isExpanded: .init(get: {
expandedIndex == index
}, set: { value in
expandedIndex = value ? index : nil
}), content: {
content(index)
}, label: {
label(index)
})
}
}
}
}
And here's some sample code, showcasing it in action:
struct AccordionViewTest: View {
@State private var expandedIndex: Int? = nil
private let sections = [
SymbolGroup(title: "Weather",
symbols: [
"sun.min", "sun.min.fill", "sun.max", "sunrise", "moon", "cloud.fog.fill", "cloud.hail"
]),
SymbolGroup(title: "Devices",
symbols: [
"keyboard", "airtag", "display", "pc", "macpro.gen2", "macmini", "flipphone"
]),
SymbolGroup(title: "Transport",
symbols: [
"airplane", "airplane.circle", "car.2", "tram.fill", "car.ferry", "bicycle", "sailboat.fill"
]),
SymbolGroup(title: "Fitness",
symbols: [
"figure.walk", "figure.boxing", "figure.golf", "figure.roll", "figure.outdoor.cycle", "dumbbell", "baseball.fill"
]),
SymbolGroup(title: "Time",
symbols: [
"clock", "clock.fill", "alarm", "deskclock", "timer.circle", "timer.square", "hourglass"
]),
]
var body: some View {
VStack {
Text("Expanded index: \((expandedIndex == nil) ? "none" : "\(expandedIndex!)")")
AccordionView(expandedIndex: $expandedIndex,
sectionCount: sections.count,
label: { index in
Text(sections[index].title)
},
content: { index in
HStack {
ForEach(sections[index].symbols, id: \.self) { symbol in
Image(systemName: symbol)
.frame(width: 32, height: 32)
}
}
})
Spacer()
}
.padding()
}
struct SymbolGroup {
let title: String
let symbols: [String]
}
}