I want to know how to prevent a user from edition a UITextField but not the user interaction. What I want to do here is when the user taps on the text field a UIPickerView pops up from the bottom and the user can select an item from the picker view and display it on the text field. But I don't want the user to be able to edit the text field. I want to do this for the class below.
import UIKit
typealias PickerTextFieldDisplayNameHandler = ((Any) -> String)
typealias PickerTextFieldItemSelectionHandler = ((Int, Any) -> Void)
class PickerTextField: UITextField {
private let pickerView = UIPickerView(frame: .zero)
private var lastSelectedRow: Int?
public var pickerData: [Any] = []
public var displayNameHandler: PickerTextFieldDisplayNameHandler?
public var itemSelectionHandler: PickerTextFieldItemSelectionHandler?
override init(frame: CGRect) {
super.init(frame: frame)
self.configureView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.configureView()
}
override func caretRect(for position: UITextPosition) -> CGRect {
return .zero
}
private func configureView() {
self.pickerView.delegate = self
self.pickerView.dataSource = self
self.inputView = pickerView
let toolbar = UIToolbar()
toolbar.barStyle = .default
toolbar.sizeToFit()
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(doneButtonTapped))
toolbar.setItems([spaceButton, doneButton], animated: false)
self.inputAccessoryView = toolbar
}
private func updateText() {
if self.lastSelectedRow == nil {
self.lastSelectedRow = 0
}
if self.lastSelectedRow! > self.pickerData.count {
return
}
let data = self.pickerData[self.lastSelectedRow!]
self.text = self.displayNameHandler?(data)
}
@objc func doneButtonTapped() {
self.updateText()
self.resignFirstResponder()
}
}
extension PickerTextField: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let data = self.pickerData[row]
return self.displayNameHandler?(data)
}
}
extension PickerTextField: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.pickerData.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.lastSelectedRow = row
self.updateText()
let data = self.pickerData[row]
self.itemSelectionHandler?(row, data)
}
}
From my understanding you want to achieve the following behavior:
I would use the delegate methods of UITextField
and UIPickerView
.
There is a method in UIPickerViewDelegate
that lets you know which row was selected by the user:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
textField.text = "Hello \(row)"
}
Here you would set the text to the textfield which solves (1).
And also you can use the method in UITextFieldDelegate
that allows you to prevent user input into the textfield.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return false
}
This prevents the user from typing anything into the textfield. This solves (2). However, the cursor will still be showing which might confuse the user. He might think he can enter something into the textfield from the cursor blinking when he actually can't.
And of course you have to make the inputView
of textfield to your custom picker view like so:
textField.inputView = myPickerView
I hope this answers your question :)