You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 05:04:53