SwiftUI Picker 重叠的可点击区域

Clickable area of SwiftUI Picker overlapping

我目前正在尝试在 HStack 中创建一个包含三个相邻 Picker 视图的页面,如下所示:

我制作了一个 CustomPicker 视图,其中我将框架限制为 90 x 240,然后使用 .compositingGroup().clipped() 使每个选择器的可选区域不重叠。

CustomPicker.swift

import SwiftUI

struct CustomPicker: View {
    @Binding var selection: Int
    let pickerColor: Color
    
    var numbers: some View {
        ForEach(0...100, id: \.self) { num in
            Text("\(num)")
                .bold()
        }
    }
    
    var stroke: some View {
        RoundedRectangle(cornerRadius: 16)
            .stroke(lineWidth: 2)
    }
    
    var backgroundColor: some View {
        pickerColor
            .opacity(0.25)
    }
    
    var body: some View {
        Picker("Numbers", selection: $selection) {
            numbers
        }
        .frame(width: 90, height: 240)
        .compositingGroup()
        .clipped()
        .pickerStyle(.wheel)
        .overlay(stroke)
        .background(backgroundColor)
        .cornerRadius(16)
    }
}

ChoicePage.swift

struct ChoicePage: View {
    @State var choiceA: Int = 0
    @State var choiceB: Int = 0
    @State var choiceC: Int = 0
    
    var body: some View {
        HStack(spacing: 18) {
            CustomPicker(selection: $choiceA, pickerColor: .red)
            CustomPicker(selection: $choiceB, pickerColor: .green)
            CustomPicker(selection: $choiceC, pickerColor: .blue)
        }
    }
}

在预览 canvas 和模拟器中测试 CustomPickerChoicePage 时,它运行得非常好,但是当我尝试在我的物理设备上使用它时 (iPhone 8 和 iPhone 13,都在 iOS 15.1) 可点击区域重叠。我尝试了 and this post 以及许多其他人的解决方案,但似乎没有任何效果。

我通过修改 Steve M 的解决方案解决了这个问题,所以所有的功劳都归于他。

他使用 UIViewRepresentable,但在他的实现中,它用于一个内部的三个不同 select 离子。我稍微调整了他的实现,仅用于给定选择器中 select 的一个值。

我从 BasePicker 开始,它充当 UIViewRepresentable:

BasePicker.swift

struct BasePicker: UIViewRepresentable {
    var selection: Binding<Int>
    let data: [Int]
    
    init(selecting: Binding<Int>, data: [Int]) {
        self.selection = selecting
        self.data = data
    }
    
    func makeCoordinator() -> BasePicker.Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: UIViewRepresentableContext<BasePicker>) -> 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<BasePicker>) {
        guard let row = data.firstIndex(of: selection.wrappedValue) else { return }
        view.selectRow(row, inComponent: 0, animated: false)
    }
    
    class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
        var parent: BasePicker
        
        init(_ pickerView: BasePicker) {
            parent = pickerView
        }
        
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return 1
        }
        
        func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
            return 90
        }
        
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return parent.data.count
        }
        
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            return parent.data[row].formatted()
        }
        
        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            parent.selection.wrappedValue = parent.data[row]
        }
    }
}

然后我在 CustomPicker 中使用 BasePicker Representable,这是一个 SwiftUI View。我这样做是为了更容易地在原始代码中保留我之前的 styling/structure。

CustomPicker.swift

struct CustomPicker: View {
    @Binding var selection: Int
    let pickerColor: Color

    let numbers: [Int] = Array(stride(from: 0, through: 100, by: 1))
    
    var stroke: some View {
        RoundedRectangle(cornerRadius: 16)
            .stroke(lineWidth: 2)
    }
    
    var backgroundColor: some View {
        pickerColor
            .opacity(0.25)
    }
    
    var body: some View {
        BasePicker(selecting: $selection, data: numbers)
            .frame(width: 90, height: 240)
            .overlay(stroke)
            .background(backgroundColor)
            .cornerRadius(16)
    }
}

然后我只需要稍微改变一下 ChoicePage 就可以解决了。另外,请注意,我将 numbers 数组移动到我的 CustomPicker 视图中,但您可以对其进行调整,以便您可以根据需要从 ChoicePage 传递它。

ChoicePage.swift

struct ChoicePage: View {
    @State var choiceA: Int = 0
    @State var choiceB: Int = 0
    @State var choiceC: Int = 0
    
    var body: some View {
        HStack(spacing: 18) {
            CustomPicker(selection: $choiceA, pickerColor: .red)
            CustomPicker(selection: $choiceB, pickerColor: .green)
            CustomPicker(selection: $choiceC, pickerColor: .blue)
        }
    }
}

在 15.4 中添加此扩展对我有用

extension UIPickerView {   
   open override var intrinsicContentSize: CGSize {     
      return CGSize(width: UIView.noIntrinsicMetric, height: super.intrinsicContentSize.height)} 
}

发现于 https://developer.apple.com/forums/thread/687986?answerId=706782022#706782022

我有 iOS 15+ 的解决方法。

使用 .scaleEffect(x: 0.5) 将内联选择器的可触摸区域减半。

然而,这也会挤压其中的文本,要解决此问题,请仅将 .scaleEffect(x: 2) 应用于 ForEach 中的文本。

  var body: some View {
      Picker(selection: $number, label: Text(""), content: {
            ForEach(0..<21) {value in
            Text("\(value)").tag(number)
                .scaleEffect(x: 3)
            }
        }
    )
    .pickerStyle(InlinePickerStyle())
    .scaleEffect(x: 0.333)
}