SwiftUI选择器的可点击区域重叠。

5

我正在尝试创建一个带有三个相邻的 Picker 视图的页面,如下所示:

enter image description here

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

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)
        }
    }
}

在预览画布和模拟器中测试 CustomPickerChoicePage 时,它们工作得非常好,但当我尝试在我的物理设备上使用它们(iPhone 8 和 iPhone 13,均运行 iOS 15.1)时,可点击区域会重叠。我尝试了这篇文章这篇文章以及其他许多解决方案,但似乎没有任何解决方法适用于我。


1
在iOS 15.1中没有办法让本地选择器正常工作,请参考我在此问题中提出的UIViewRepresentable解决方案想法:https://dev59.com/MFEG5IYBdhLWcg3wRIid#69842277 - Steve M
这个回答解决了你的问题吗?https://stackoverflow.com/a/65631488/12299030 - Asperi
@Asperi 我刚刚尝试使用.contentShape(Rectangle()),但它没有起作用。我已经回答了原始帖子,并提供了一个来自Steve M的调整后的解决方案。 - Teddy Bersentes
3个回答

12

在我的15.4版本中添加此扩展功能可正常工作。

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

请看此处


4

我有一个iOS 15+的解决方法。

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

然而这也会压缩其中的文本,为了修复这个问题,仅对ForEach中的文本应用.scaleEffect(x:2)。

  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)
}

Screenshot of result, where the touchable area of the picker is smaller in width


很棒的想法。唯一的问题是,它会破坏选择区域的圆角。但对我来说没问题,因为我无论如何都会剪辑它们。 - heyfrank
它对我有效。简单而有效!谢谢。然而,似乎只适用于同一hstack中的选择器。有没有办法解决同一vstack中选择器重叠的问题? - test1229

4
我通过修改 Steve M 的解决方案来解决这个问题,所以所有的功劳都归于他。
他使用了一个 UIViewRepresentable,但在他的实现中,它用于三个不同的选择。我稍微调整了他的实现,只用于从给定的选择器中选择一个值。
我从 BasePicker 开始,它作为 UIViewRepresentableBasePicker.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]
        }
    }
}

我随即在 SwiftUI 的 View 中使用了 BasePicker 可表示元素,这是通过 CustomPicker 实现的。 我这样做是为了更轻松地保留以前原始代码中的样式/结构。

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)
        }
    }
}

1
这是我唯一的解决方案,可以避免多个选择器之间重叠手势的问题。谢谢! - Spettacolo83
谢谢您提供这个出色的答案。有人知道如何使其更通用,以防每个选择器具有不同的类型。我正在使用枚举来支持我的选择器,并尝试进行转换。 - Prasanth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接