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 和模拟器中测试 CustomPicker
和 ChoicePage
时,它运行得非常好,但是当我尝试在我的物理设备上使用它时 (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)
}
我目前正在尝试在 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 和模拟器中测试 CustomPicker
和 ChoicePage
时,它运行得非常好,但是当我尝试在我的物理设备上使用它时 (iPhone 8 和 iPhone 13,都在 iOS 15.1) 可点击区域重叠。我尝试了
我通过修改 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)
}