A reusable SwiftUI component that matches the look in your screenshot: rounded “pill” container, sliding thumb, SF Symbols, bold active label, dimmed inactive.
import SwiftUI
// MARK: - Model
enum SegTab: String, CaseIterable, Identifiable {
case location = "Location"
case settings = "Settings"
var id: String { rawValue }
var icon: String {
switch self {
case .location: return "mappin.and.ellipse" // or "location.circle"
case .settings: return "gearshape"
}
}
}
// MARK: - Control
struct PillSegmentedControl<T: Hashable & Identifiable & CaseIterable>: View {
struct Item: Identifiable, Hashable {
var id: T
var title: String
var systemImage: String
}
@Binding var selection: T
var items: [Item]
var height: CGFloat = 56
var cornerRadius: CGFloat = 28
var padding: CGFloat = 6
@Namespace private var thumbNS
var body: some View {
GeometryReader { geo in
let w = geo.size.width
let h = max(geo.size.height, height)
let segmentW = (w - padding * 2) / CGFloat(items.count)
ZStack(alignment: .leading) {
// Background
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.fill(.black.opacity(0.65))
.overlay(
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.stroke(.white.opacity(0.08), lineWidth: 1)
)
.shadow(color: .black.opacity(0.35), radius: 16, y: 6)
// Thumb
if let idx = items.firstIndex(where: { $0.id == selection }) {
RoundedRectangle(cornerRadius: cornerRadius - 4, style: .continuous)
.fill(
LinearGradient(colors: [
Color.white.opacity(0.08),
Color.white.opacity(0.02)
], startPoint: .topLeading, endPoint: .bottomTrailing)
.blendMode(.plusLighter)
)
.background(
RoundedRectangle(cornerRadius: cornerRadius - 4, style: .continuous)
.fill(Color.white.opacity(0.06))
)
.overlay(
RoundedRectangle(cornerRadius: cornerRadius - 4, style: .continuous)
.stroke(.white.opacity(0.12), lineWidth: 1)
)
.frame(width: segmentW, height: h - padding * 2)
.padding(padding)
.offset(x: CGFloat(idx) * segmentW)
.matchedGeometryEffect(id: "thumb", in: thumbNS)
.animation(.snappy(duration: 0.28), value: selection)
}
// Segments
HStack(spacing: 0) {
ForEach(items) { item in
Button {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
withAnimation(.snappy(duration: 0.28)) {
selection = item.id
}
} label: {
HStack(spacing: 10) {
Image(systemName: item.systemImage)
.font(.system(size: 20, weight: .semibold))
Text(item.title)
.font(.system(.headline, design: .rounded))
.fontWeight(.semibold)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.foregroundStyle(selection == item.id ? Color.white : Color.white.opacity(0.5))
.accessibilityLabel(Text(item.title))
.accessibilityAddTraits(selection == item.id ? .isSelected : [])
}
}
.padding(padding)
}
.frame(height: h)
}
.frame(height: height)
}
}
// MARK: - Convenience wrapper for your two tabs
struct LocationSettingsSegmented: View {
@Binding var selection: SegTab
var body: some View {
PillSegmentedControl(
selection: $selection,
items: SegTab.allCases.map { .init(id: $0, title: $0.rawValue, systemImage: $0.icon) },
height: 56,
cornerRadius: 28,
padding: 6
)
.padding(.horizontal, 16)
.environment(\.colorScheme, .dark) // matches your dark look
}
}
// MARK: - Demo
struct ContentView: View {
@State private var tab: SegTab = .location
var body: some View {
VStack(spacing: 24) {
Text("PickerStyle - Segmented")
.font(.caption)
.foregroundStyle(.white.opacity(0.7))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 24)
LocationSettingsSegmented(selection: $tab)
// Example of reacting to selection
Group {
if tab == .location {
Text("Location view")
} else {
Text("Settings view")
}
}
.foregroundStyle(.white.opacity(0.9))
.padding(.top, 8)
}
.padding(.vertical, 32)
.background(Color(red: 0.12, green: 0.12, blue: 0.12))
}
}
#Preview {
ContentView()
}- Uses
matchedGeometryEffectfor the smooth sliding thumb. - Dark, slightly glossy style to mirror the screenshot.
- Works on iOS 17+/macOS 14+/visionOS; swap symbols if you prefer (
location.circlevsmappin.and.ellipse). - Tweak
height,cornerRadius, andpaddingto fit your layout.