SwiftUI中如何让Wheel Picker选中的中间行支持点击?
实现可点击中间行的SwiftUI滚轮选择器
核心思路
SwiftUI原生WheelPickerStyle的手势逻辑被封装,无法直接精准控制中间行的点击事件。我们可以通过UIViewRepresentable封装UIKit的UIPickerView,既能保留原生滚轮的拖拽交互,又能自定义中间选中行的点击响应。
完整实现代码
1. 封装可点击的UIPickerView组件
import SwiftUI struct ClickableWheelPicker: UIViewRepresentable { @Binding var selectedValue: Int let range: ClosedRange<Int> var onMiddleRowTap: () -> Void func makeUIView(context: Context) -> UIPickerView { let picker = UIPickerView() picker.dataSource = context.coordinator picker.delegate = context.coordinator picker.selectRow(selectedValue - range.lowerBound, inComponent: 0, animated: false) // 添加点击手势识别器 let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(_:))) tapGesture.cancelsTouchesInView = false // 不干扰拖拽手势 picker.addGestureRecognizer(tapGesture) return picker } func updateUIView(_ uiView: UIPickerView, context: Context) { let row = selectedValue - range.lowerBound if uiView.selectedRow(inComponent: 0) != row { uiView.selectRow(row, inComponent: 0, animated: true) } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { let parent: ClickableWheelPicker init(parent: ClickableWheelPicker) { self.parent = parent } // MARK: - UIPickerViewDataSource func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { parent.range.count } // MARK: - UIPickerViewDelegate func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { String(parent.range.lowerBound + row) } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { parent.selectedValue = parent.range.lowerBound + row } // MARK: - 处理点击事件 @objc func handleTap(_ gesture: UITapGestureRecognizer) { guard let pickerView = gesture.view as? UIPickerView else { return } let selectedRow = pickerView.selectedRow(inComponent: 0) // 计算中间选中行的frame let rowRect = pickerView.rowRect(forRow: selectedRow) let tapLocation = gesture.location(in: pickerView) // 判断点击位置是否在选中行范围内 if rowRect.contains(tapLocation) { parent.onMiddleRowTap() } } } }
2. 在SwiftUI视图中使用并处理键盘输入
struct ContentView: View { @State private var value = 0 @State private var showNumberInput = false var body: some View { VStack(spacing: 20) { ClickableWheelPicker( selectedValue: $value, range: 0...59, onMiddleRowTap: { showNumberInput = true } ) .frame(height: 200) // 数字输入弹窗 if showNumberInput { VStack(spacing: 16) { TextField("输入数字", value: $value, format: .number) .keyboardType(.numberPad) .textFieldStyle(.roundedBorder) .padding() Button("确认") { // 限制输入范围在0-59之间 value = max(0, min(value, 59)) showNumberInput = false } } .background(Color.white) .cornerRadius(12) .padding() .shadow(radius: 10) } } .padding() } }
关键细节说明
- 手势共存:设置
tapGesture.cancelsTouchesInView = false,确保点击手势不会阻断UIPickerView的原生拖拽手势,两者可以同时工作。 - 精准点击判断:通过
rowRect(forRow:)获取选中行的区域,对比点击坐标,只在点击中间选中行时触发输入弹窗。 - 范围限制:在确认输入时强制将
value限制在0-59之间,避免超出滚轮的可选范围。
内容的提问来源于stack exchange,提问作者user3932000
相关产品推荐
相关产品推荐

