Building a Custom Selection View in SwiftUI
In this tutorial, we'll create a custom selection view using SwiftUI. This view will allow users to select from multiple options, with a visually appealing design and smooth animations. Let's break down the code and explain each part step by step.
1. Setting Up the Project
First, we'll start by importing SwiftUI and setting up some initial variables:
import SwiftUI
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
let safeAreaTop = window?.safeAreaInsets.top
These lines help us get the safe area insets of the device, which we'll use later for proper padding.
2. Creating the ContentView
Next, we'll define our main ContentView
struct:
struct ContentView: View {
@State private var selectedIndex: Int? = 0
var body: some View {
// Main view content
}
}
We use a @State
property wrapper for selectedIndex
to keep track of the currently selected item.
3. Building the Main Layout
Inside the body
property, we'll create our main layout:
VStack(alignment: .leading, spacing: 12) {
Text("Tap to change selection")
.font(.system(size: 22, weight: .semibold))
.frame(maxWidth: .infinity, alignment: .center)
.foregroundStyle(Color(hex: 0x7fd2ff))
.multilineTextAlignment(.center)
.padding(.top, safeAreaTop ?? 20)
.padding(.bottom, 10)
VStack(spacing: 12) {
ForEach(0..<5) { index in
selectionItem(index: index)
}
}
}
.padding(.horizontal, 16)
.frame(minWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height, alignment: .topLeading)
.background(Color(hex: 0xdff4ff).ignoresSafeArea())
This creates a vertical stack with a title and five selection items.
4. Implementing the Selection Item
The selectionItem
function creates each individual selectable item:
private func selectionItem(index: Int) -> some View {
let isSelected = selectedIndex == index
return HStack(spacing: 0) {
VStack {}
.frame(width: 48, height: 48, alignment: .topLeading)
.background(Color(hex: 0x7fd2ff))
.cornerRadius(24)
.opacity(isSelected ? 1 : 0.2)
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.white)
.clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: 24, style: .continuous)
.stroke(Color(hex: 0x7fd2ff), style: StrokeStyle(lineWidth: 2, lineJoin: .round))
.opacity(isSelected ? 1 : 0)
)
.scaleEffect(isSelected ? 1.04 : 1.0)
.onTapGesture {
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
selectedIndex = index
}
}
}
This function creates a rounded rectangle with a circular indicator. When selected, the item scales up slightly and shows a border.
5. Adding a Custom Color Extension
To make working with hex colors easier, we'll add an 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)
}
}
This allows us to use hex values for colors throughout our code.
6. Preview
Finally, we'll add a preview for our ContentView:
#Preview {
ContentView()
}
This allows us to see our view in the Xcode preview pane.