Skip to content

Instantly share code, notes, and snippets.

@Codelaby
Created February 26, 2025 11:14
Show Gist options
  • Select an option

  • Save Codelaby/242615b9b38382a1778ea05cbd44fbd1 to your computer and use it in GitHub Desktop.

Select an option

Save Codelaby/242615b9b38382a1778ea05cbd44fbd1 to your computer and use it in GitHub Desktop.
NumerciHPicker
//
// 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