Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS15 - SwiftUI WheelPicker scrollable outside frame and clipped area destructing other interfaces

Tags:

swiftui

ios15

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)
    }
}
like image 205
user14341201 Avatar asked Sep 09 '21 17:09

user14341201


2 Answers

In NumberPicker try adding compositingGroup just before clipped(...) as:

.compositingGroup()
.clipped(antialiased: true)
like image 186
workingdog support Ukraine Avatar answered Oct 23 '22 13:10

workingdog support Ukraine


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]
        }
    }
}
like image 20
Steve M Avatar answered Oct 23 '22 13:10

Steve M