Created
February 26, 2025 11:14
-
-
Save Codelaby/242615b9b38382a1778ea05cbd44fbd1 to your computer and use it in GitHub Desktop.
NumerciHPicker
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
| // | |
| // NumerciHPicker.swift | |
| // FieldsPlayground | |
| // | |
| // Created by Codelaby on 25/2/25. | |
| // | |
| import SwiftUI | |
| struct NumerciHPicker<SelectionValue, Content>: View where SelectionValue: Hashable & Sendable, Content: View { | |
| @State private var scrollPosition: ScrollPosition = .init(idType: SelectionValue.self) | |
| private var items: [SelectionValue] | |
| private var content: (SelectionValue) -> Content | |
| @Binding private var selection: SelectionValue | |
| private let config: Config | |
| init( | |
| items: [SelectionValue], | |
| selection: Binding<SelectionValue>, | |
| config: Config = Config(), | |
| @ViewBuilder content: @escaping (SelectionValue) -> Content | |
| ) { | |
| self.items = items | |
| self.content = content | |
| self.config = config | |
| _selection = selection | |
| } | |
| struct Config { | |
| var numberOfDisplays: CGFloat = 1 | |
| var spacing: CGFloat = 0 | |
| var itemSize: CGSize = .init(width: 48, height: 48) | |
| } | |
| var body: some View { | |
| GeometryReader { proxy in | |
| let size = proxy.size | |
| ScrollView(.horizontal, showsIndicators: false) { | |
| LazyHStack(spacing: config.spacing) { | |
| ForEach(items, id: \.self) { item in | |
| content(item) | |
| .frame(width: config.itemSize.width, height: config.itemSize.height) | |
| .border(.blue) | |
| } | |
| } | |
| .scrollTargetLayout() | |
| } | |
| //.scrollTargetBehavior(.viewAligned) | |
| .scrollTargetBehavior(.snap(step: config.itemSize.width + config.spacing)) | |
| .disableBounces() | |
| .contentMargins(.horizontal, max((size.width - config.itemSize.width) / 2, 0)) | |
| .scrollPosition($scrollPosition) | |
| .onAppear { | |
| scrollPosition.scrollTo(id: selection) | |
| } | |
| .onChange(of: scrollPosition) { oldValue, newValue in | |
| Task { @MainActor in | |
| if let newSelection = newValue.viewID(type: SelectionValue.self) { | |
| selection = newSelection | |
| } | |
| } | |
| } | |
| } | |
| .overlay { | |
| Rectangle() | |
| .fill(.secondary.quinary) | |
| .frame(width: config.itemSize.width, height: config.itemSize.height) | |
| } | |
| .mask { | |
| let count = Int(config.numberOfDisplays) | |
| LinearGradient( | |
| colors: [.clear] + Array(repeating: .black, count: count) + [.clear], | |
| startPoint: .leading, | |
| endPoint: .trailing | |
| ) | |
| } | |
| } | |
| } | |
| // MARK: Playground | |
| struct NumericHPickerDemo: View { | |
| @AppStorage("pickerSelection") private var selection: Int = 0 // Store selection in UserDefaults | |
| let items = (-20..<21).map(\.self) | |
| var body: some View { | |
| VStack { | |
| Text("\(selection)") | |
| NumerciHPicker(items: items, selection: $selection) { item in | |
| let isSelected = item == selection | |
| Text("\(item)") | |
| .foregroundStyle(item == 0 ? .red : isSelected ? .blue : .primary) | |
| } | |
| .frame(height: 48) | |
| .border(.red) | |
| } | |
| } | |
| } | |
| #Preview { | |
| NumericHPickerDemo() | |
| } | |
| // MARK: Int | |
| #Preview("Int") { | |
| @Previewable @State var selection: Int = 0 | |
| let items = (-20..<21).map(\.self) | |
| //let items = stride(from: 0.0, to: 20.0, by: 1.0).map { $0 } // For double | |
| SampleTitleView(title: "Base Horizontal Scroll Picker in SwiftUI", summary: "Auto size + Customization + bindable current index") | |
| Spacer() | |
| Text("\(selection)") | |
| //Text(String(format: "%.1f", selection)) // For double | |
| NumerciHPicker(items: items, selection: $selection) { item in | |
| let isSelected = item == selection | |
| Text("\(item)") | |
| //Text(String(format: "%.1f", item)) // For double | |
| .foregroundStyle(isSelected ? .red : .primary) | |
| } | |
| .frame(height: 48) | |
| .border(.red) | |
| Spacer() | |
| CreditsView() | |
| } | |
| // MARK: Double | |
| #Preview("Double") { | |
| @Previewable @State var selection: Double = 0.0 | |
| let items = stride(from: 0.0, to: 20.0, by: 1.0).map { $0 } // For double | |
| SampleTitleView(title: "Base Horizontal Scroll Picker in SwiftUI", summary: "Auto size + Customization + bindable current index") | |
| Spacer() | |
| Text(String(format: "%.1f", selection)) // For double | |
| NumerciHPicker(items: items, selection: $selection) { item in | |
| let isSelected = item == selection | |
| Text(String(format: "%.1f", item)) // For double | |
| .foregroundStyle(isSelected ? .red : .primary) | |
| } | |
| .frame(height: 48) | |
| .border(.red) | |
| Spacer() | |
| CreditsView() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment