iosswiftswiftuiuikituipickerview

How do I hide (or recolor) the default selection highlight in a UIPickerView that I embed in SwiftUI?


What I’m trying to do I’m wrapping a custom UIKit UIPickerView in SwiftUI because I need:

custom row height (56 pt)

SwiftUI data binding

Everything works, except I still see the system‑gray selection indicator / loupe that UIPickerView draws in the middle. I’d like to remove it entirely or change its color so only my own blue rounded rectangle is visible.

// ------------------------- SwiftUI wrapper -------------------------
struct WheelPickerDemoUIKit: View {
    @State private var selected: DefaultTool = .basic      // enum cases below
    
    var body: some View {
        VStack(spacing: 30) {
            DefaultToolPickerRepresentable(selection: $selected,
                                           rowHeight: 56)
                .frame(height: UIScreen.main.bounds.height * 0.7)

            Text("Selected →  \(selected.rawValue)")
        }
        .padding()
    }
}

// ------------------------- UIKit picker view -------------------------
final class DefaultToolPickerView: UIView, UIPickerViewDataSource, UIPickerViewDelegate {
   
    let picker = UIPickerView()
    private let rowHeight: CGFloat
    
    var selectedTool: DefaultTool {
        DefaultTool.allCases[picker.selectedRow(inComponent: 0)]
    }
    var onSelection: ((DefaultTool) -> Void)?
    
    init(rowHeight: CGFloat = 56) {
        self.rowHeight = rowHeight
        super.init(frame: .zero)
        commonInit()
    }
    required init?(coder: NSCoder) { fatalError() }
    
    private func commonInit() {
        picker.dataSource = self
        picker.delegate   = self
        picker.backgroundColor = .clear
        picker.translatesAutoresizingMaskIntoConstraints = false
        addSubview(picker)
        
        NSLayoutConstraint.activate([
            picker.leadingAnchor.constraint(equalTo: leadingAnchor),
            picker.trailingAnchor.constraint(equalTo: trailingAnchor),
            picker.topAnchor.constraint(equalTo: topAnchor),
            picker.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
    
    // MARK: UIPickerViewDataSource / Delegate
    func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent _: Int) -> Int {
        DefaultTool.allCases.count
    }
    
    func pickerView(_ pickerView: UIPickerView,
                    viewForRow row: Int,
                    forComponent _: Int,
                    reusing view: UIView?) -> UIView {
        let label = (view as? UILabel) ?? UILabel()
        label.text          = DefaultTool.allCases[row].rawValue
        label.font          = .systemFont(ofSize: 24, weight: .medium)
        label.textAlignment = .center
        label.textColor     = .black
        return label
    }
    
    func pickerView(_: UIPickerView, rowHeightForComponent _: Int) -> CGFloat {
        rowHeight
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow _: Int, inComponent _: Int) {
        pickerView.reloadAllComponents()
        onSelection?(selectedTool)
    }
}

// ------------------------- SwiftUI <-> UIKit bridge -------------------------
struct DefaultToolPickerRepresentable: UIViewRepresentable {
    @Binding var selection: DefaultTool
    var rowHeight: CGFloat = 56
    
    func makeUIView(context: Context) -> DefaultToolPickerView {
        let view = DefaultToolPickerView(rowHeight: rowHeight)
        view.onSelection = { selection = $0 }
        return view
    }
    
    func updateUIView(_ uiView: DefaultToolPickerView, context: Context) {
        if selection != uiView.selectedTool,
           let idx = DefaultTool.allCases.firstIndex(of: selection) {
            uiView.picker.selectRow(idx, inComponent: 0, animated: true)
            uiView.picker.reloadAllComponents()
        }
    }
}

// ------------------------- Demo enum -------------------------
enum DefaultTool: String, CaseIterable, Identifiable {
    var id: Self { self }
    case basic = "Basic Calc", emi = "EMI Calc", sip = "SIP Calc"
    case percent = "Percentage Calc", gst = "GST Calc", bmi = "BMI Calc"
}

What I already tried

picker.subviews
    .filter { $0.frame.height < 2 }
    .forEach { $0.isHidden = true }

Attempted KVC

picker.setValue(UIColor.clear, forKey: "selectionIndicator")

Question Is there any supported way to hide or recolor the default selection indicator in UIPickerView, when I embed it in SwiftUI? Or is the only safe approach to overlay my own view and accept that the gray band is still rendered underneath?

Image :

enter image description here


Solution

  • picker.subviews[1].backgroundColor = UIColor.clear

    This is how I remove color of selected item but do it after UIPickerView is loaded perfectly, otherwise it's going to crash