Created
March 5, 2025 10:13
-
-
Save Codelaby/90adf40b56663b1e308d54322d163a3d to your computer and use it in GitHub Desktop.
Apperance Picker, Theme switch for swiftUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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