I have two WheelPickers contained inside a HStack for 'hour' and 'min'. Each Picker is set within a frame(width: 50, height: 30) and additionally clipped.
In iOS14, it behaved as expected and I could scrolled the 'hour' picker to change the hour and 'minute' picker to change the mins.
HOWEVER in iOS15, the 'minute' wheelpicker is extended beyond the frame width of 50 and overlapped into the 'hour' picker; if I scroll on the 'hour' picker, the 'mins' value changes (instead of 'hour' value), if I scroll on 'minute' picker, it changes the 'mins' as expected. If I touch on the far left outside the 'hour' picker, then the 'hour' value changes.
Anyone has the same issue and any workaround for this issue?
I came across a workaround to add 'mask(rectangle()' and tried it, but it did not work on iOS15.
@State private var hour: Int = 0
@State private var minute: Int = 0
var body: some View {
VStack {
HStack (alignment: .center, spacing: 3) {
NumberPicker("", selection: $hour
, startValue: 0
, endValue: 23
, pickerSize: CGSize(width: 50, height: 30)
)
Text("hr")
NumberPicker("", selection: $minute
, startValue: 0
, endValue: 59
, pickerSize: CGSize(width: 50, height: 30)
)
Text("min")
} // HStack
} // VStack
}
}
struct NumberPicker: View {
let startValue: Int
let endValue: Int
let pickerSize: CGSize
let title: String
@Binding var selection: Int
@State var value: Int = 0
init(_ title: String = ""
, selection: Binding<Int>
, startValue: Int = 0
, endValue: Int
, pickerSize: CGSize = CGSize(width: 50, height: 30)
) {
self._selection = selection
self.title = title
self.startValue = startValue
self.endValue = (endValue + 1)
self.pickerSize = pickerSize
self._value = State(initialValue: selection.wrappedValue)
}
var body: some View {
Picker(title, selection: $value) {
ForEach(startValue..<endValue, id: \.self) { currentValue in
Text("\(currentValue)")
.tag(currentValue)
}
}
.pickerStyle(WheelPickerStyle())
.fixedSize(horizontal: true, vertical: true)
.frame(width: pickerSize.width, height: pickerSize.height)
.clipped(antialiased: true)
}
}
In NumberPicker
try adding compositingGroup
just before clipped(...)
as:
.compositingGroup()
.clipped(antialiased: true)
In iOS 15.1 there doesn't seem to be any workaround for this, so I rolled my own using UIViewRepresentable
. The example is made for my use with Double
but you can easily adapt it for yours.
As well as being able to have multiple components in a single wheel picker, you can also stack them horizontally in an HStack
without problem:
Usage
import SwiftUI
struct ContentView: View {
@State private var selections1: [Double] = [5, 10]
private let data1: [[Double]] = [
Array(stride(from: 0, through: 60, by: 1)),
Array(stride(from: 0, through: 60, by: 1))
]
var body: some View {
VStack {
HStack{
MultiWheelPicker(selections: self.$selections1, data: data1)
.frame(width: 150)
MultiWheelPicker(selections: self.$selections1, data: data1)
.frame(width: 150)
}
}
}
}
View
struct MultiWheelPicker: UIViewRepresentable {
var selections: Binding<[Double]>
let data: [[Double]]
func makeCoordinator() -> MultiWheelPicker.Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<MultiWheelPicker>) -> UIPickerView {
let picker = UIPickerView()
picker.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
picker.dataSource = context.coordinator
picker.delegate = context.coordinator
return picker
}
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<MultiWheelPicker>) {
for comp in selections.indices {
if let row = data[comp].firstIndex(of: selections.wrappedValue[comp]) {
view.selectRow(row, inComponent: comp, animated: false)
}
}
}
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
var parent: MultiWheelPicker
init(_ pickerView: MultiWheelPicker) {
parent = pickerView
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return parent.data.count
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return parent.data[component].count
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 48
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(format: "%02.0f", parent.data[component][row])
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
parent.selections.wrappedValue[component] = parent.data[component][row]
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With