Skip to content

Instantly share code, notes, and snippets.

@diamantidis
Created June 21, 2020 12:59
Show Gist options
  • Save diamantidis/061d101853f6400f76780345614b2c90 to your computer and use it in GitHub Desktop.
Save diamantidis/061d101853f6400f76780345614b2c90 to your computer and use it in GitHub Desktop.
A SwiftUI field with a UIPickerView as a keyboard
import SwiftUI
struct ContentView: View {
@State var selectedIndex: Int? = nil
let options: [String] = ["Option1", "Option2"]
var body: some View {
PickerField("Select an option", data: self.options, selectionIndex: self.$selectedIndex)
}
}
import SwiftUI
struct PickerField: UIViewRepresentable {
// MARK: - Public properties
@Binding var selectionIndex: Int?
// MARK: - Initializers
init<S>(_ title: S, data: [String], selectionIndex: Binding<Int?>) where S: StringProtocol {
self.placeholder = String(title)
self.data = data
self._selectionIndex = selectionIndex
textField = PickerTextField(data: data, selectionIndex: selectionIndex)
}
// MARK: - Public methods
func makeUIView(context: UIViewRepresentableContext<PickerField>) -> UITextField {
textField.placeholder = placeholder
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<PickerField>) {
if let index = selectionIndex {
uiView.text = data[index]
} else {
uiView.text = ""
}
}
// MARK: - Private properties
private var placeholder: String
private var data: [String]
private let textField: PickerTextField
}
import SwiftUI
class PickerTextField: UITextField {
// MARK: - Public properties
var data: [String]
@Binding var selectionIndex: Int?
// MARK: - Initializers
init(data: [String], selectionIndex: Binding<Int?>) {
self.data = data
self._selectionIndex = selectionIndex
super.init(frame: .zero)
self.inputView = pickerView
self.tintColor = .clear
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Private properties
private lazy var pickerView: UIPickerView = {
let pickerView = UIPickerView()
pickerView.delegate = self
pickerView.dataSource = self
return pickerView
}()
// MARK: - Private methods
@objc
private func donePressed() {
self.selectionIndex = self.pickerView.selectedRow(inComponent: 0)
self.endEditing(true)
}
}
// MARK: - UIPickerViewDataSource & UIPickerViewDelegate extension
extension PickerTextField: UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.data.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return self.data[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.selectionIndex = row
}
}
@aaronlab
Copy link

aaronlab commented Dec 1, 2020

Hi guys, here's some thips.

You can add a done button with a tool bar using the code below in PickerTextField Class

init(data: [String], selectionIndex: Binding<Int?>) {
    let toolBar = UIToolbar()
    toolBar.barStyle = UIBarStyle.default
    toolBar.isTranslucent = true
    toolBar.sizeToFit()
        
    let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(self.donePressed))
    let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)

    toolBar.setItems([spaceButton, doneButton], animated: false)
    toolBar.isUserInteractionEnabled = true

    self.inputAccessoryView = toolBar
}

@objc private func donePressed() {
    self.resignFirstResponder()
}

Also, you can make users can't copy and paste or drag the content inside of the text field with the code below
Just copy and paste inside of PickerTextField class.

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        false
}
    
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
    []
}

override func caretRect(for position: UITextPosition) -> CGRect {
    .zero
}

Good luck!

Thanks for your tips, @diamantidis

@michael94ellis
Copy link

override func caretRect(for position: UITextPosition) -> CGRect {
.null
}

This will truly make it unselectable! Relevant StackOverflow post: https://stackoverflow.com/questions/32846678/how-to-disable-the-selection-of-a-uitextfield/62754240#62754240

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment