Skip to content

Instantly share code, notes, and snippets.

@Codelaby
Created March 5, 2025 10:13
Show Gist options
  • Select an option

  • Save Codelaby/90adf40b56663b1e308d54322d163a3d to your computer and use it in GitHub Desktop.

Select an option

Save Codelaby/90adf40b56663b1e308d54322d163a3d to your computer and use it in GitHub Desktop.
Apperance Picker, Theme switch for swiftUI
struct AppearancePicker<SelectionValue, Content>: View where SelectionValue: Hashable & Sendable, Content: View {
private var items: [SelectionValue]
@Binding var selection: SelectionValue
let content: (SelectionValue) -> Content
// Public initializer
init(items: [SelectionValue], selection: Binding<SelectionValue>, @ViewBuilder content: @escaping (SelectionValue) -> Content) {
self.items = items
self._selection = selection
self.content = content
}
private var gridLayout: [GridItem] {
// Define the grid layout with adaptive columns
[GridItem(.adaptive(minimum: 80, maximum: 150))]
}
var body: some View {
LazyHGrid(rows: gridLayout, spacing: 16) {
ForEach(items, id: \.self) { item in
let isSelected = selection == item
Button(action: {
selection = item
}) {
content(item)
.frame(idealWidth: 80)
.foregroundStyle(isSelected ? .primary : .secondary)
.fontWeight(isSelected ? .medium : .regular)
}
.overlay {
ContainerRelativeShape()
.stroke(isSelected ? Color.accentColor : .clear, lineWidth: 2)
.animation(.smooth, value: selection)
}
.buttonStyle(.bordered)
.background(in: .buttonBorder)
.tint(isSelected ? Color.accentColor : .none)
}
}
.frame(maxWidth: .infinity, minHeight: 86, alignment: .leading)
.padding(.vertical, 8)
//.border(.red)
}
}
enum Theme: Int, CaseIterable, Identifiable, CustomStringConvertible {
var id: Self { self }
case light, dark, auto
var systemName: String {
return switch self {
case .light:
"sun.max"
case .dark:
"moon.fill"
case .auto:
"circle.righthalf.fill"
}
}
var description: String {
return switch self {
case .light:
"Light"
case .dark:
"Dark"
case .auto:
"System"
}
}
@MainActor @ViewBuilder
static func renderView(_ theme: Theme) -> some View {
VStack {
Image(systemName: theme.systemName)
.symbolRenderingMode(.hierarchical)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Text(theme.description)
.font(.subheadline)
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(8)
}
}
#Preview {
@Previewable @State var selectedTheme: Theme = .auto
Form {
// Icon segmented
Picker(selection: $selectedTheme, label: Text("Theme")) {
ForEach(Theme.allCases, id:\.id) { option in
Image(systemName: option.systemName)
}
}
.pickerStyle(.segmented)
// Menu with icon
Picker(selection: $selectedTheme, label: Text("Theme")) {
ForEach(Theme.allCases, id:\.id) { option in
HStack {
Text(option.description)
Image(systemName: option.systemName)
}
}
}
.pickerStyle(.menu)
// Custom picker
AppearancePicker(items: Theme.allCases, selection: $selectedTheme) { option in
Theme.renderView(option)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment