SwiftUI Tutorial: Creating an Accordion Layout in SwiftUI
In this tutorial, we'll walk through the process of building an accordion layout using SwiftUI. This interactive UI component allows users to expand and collapse content sections, providing an efficient way to display information in a compact form. Let's break down the code and explain each part in detail.
Setting Up the Project
First, create a new SwiftUI project in Xcode. Replace the contents of your ContentView.swift
file with the following code structure:
import SwiftUI
// We'll add our code here
struct ContentView: View {
var body: some View {
// Main content here
}
}
Creating the AccordionItem Model
We'll start by defining a model for our accordion items:
struct AccordionItem: Identifiable {
let id = UUID()
let title: String
let content: String
var isExpanded: Bool = false
}
This structure represents each item in our accordion, with a unique identifier, title, content, and an isExpanded
flag to track its state.
Building the Main ContentView
Now, let's set up our main ContentView
:
struct ContentView: View {
@State private var accordionItems: [AccordionItem] = [
AccordionItem(title: "Accordion 1", content: "Content for Accordion 1"),
AccordionItem(title: "Accordion 2", content: "Content for Accordion 2"),
AccordionItem(title: "Accordion 3", content: "Content for Accordion 3"),
AccordionItem(title: "Accordion 4", content: "Content for Accordion 4")
]
var body: some View {
VStack(spacing: 12) {
Text("Tap to toggle an accordion's state")
.font(.system(size: 17))
.frame(maxWidth: .infinity, alignment: .center)
.frame(height: 44, alignment: .center)
.foregroundStyle(.white)
.multilineTextAlignment(.center)
.clipped()
ForEach($accordionItems) { $item in
AccordionView(item: $item)
}
}
.frame(minWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height, alignment: .top)
.background(Color(hex: 0x7fd2ff).ignoresSafeArea())
}
}
This view creates a list of AccordionItem
s and displays them using a custom AccordionView
.
Creating the AccordionView
The AccordionView
is responsible for rendering each accordion item:
struct AccordionView: View {
@Binding var item: AccordionItem
var body: some View {
VStack(spacing: 0) {
Button(action: {
withAnimation(.spring()) {
item.isExpanded.toggle()
}
}) {
HStack(spacing: 0) {
Text(item.title)
.font(.system(size: 17))
.foregroundStyle(Color(hex: 0x7fd2ff))
.opacity(0.6)
.fixedSize(horizontal: false, vertical: true)
.clipped()
Spacer()
Image(systemName: "chevron.down.circle.fill")
.font(.system(size: 22, weight: .regular))
.symbolRenderingMode(.hierarchical)
.foregroundStyle(Color(hex: 0x7fd2ff))
.frame(width: 44, height: 44)
.rotationEffect(.degrees(item.isExpanded ? 0 : -90))
}
.padding(.trailing, 4)
.padding(.leading, 16)
}
.frame(maxWidth: .infinity, alignment: .leading)
.frame(height: 56, alignment: .leading)
.background(.white.opacity(0))
if item.isExpanded {
VStack(spacing: 0) {
Text(item.content)
.font(.system(size: 17))
.foregroundStyle(Color(hex: 0x7fd2ff))
.fixedSize(horizontal: false, vertical: true)
.clipped()
.padding()
}
.frame(maxWidth: .infinity)
.background(Color.white.opacity(0.1))
.transition(.opacity.combined(with: .move(edge: .top)))
}
}
.frame(maxWidth: .infinity, alignment: .top)
.background(.white)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.shadow(color: .black.opacity(0.07), radius: 2, x: 0, y: 2)
.animation(.spring(), value: item.isExpanded)
}
}
This view creates a button for the accordion header and conditionally renders the content when the item is expanded.
Adding a Custom Color Extension
To use hex colors in our SwiftUI views, we'll add a custom extension to the Color
struct:
extension Color {
init(hex: Int, alpha: Double = 1.0) {
let red = Double((hex & 0xff0000) >> 16) / 255.0
let green = Double((hex & 0xff00) >> 8) / 255.0
let blue = Double((hex & 0xff) >> 0) / 255.0
self.init(.sRGB, red: red, green: green, blue: blue, opacity: alpha)
}
}