Skip to content

Instantly share code, notes, and snippets.

@diamantidis
Created June 21, 2020 12:59
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • 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
}
}
@bokiosdev
Copy link

How to dismiss this view?

@diamantidis
Copy link
Author

diamantidis commented Jul 6, 2020

How to dismiss this view?

Hey!!

One option is to use self.resignFirstResponder() when the user picks an option.

Another alternative is to add a UIToolbar, as I described in this post and on the actions of the ToolbarItem call the self.resignFirstResponder(), like so:

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

@daihovey
Copy link

daihovey commented Sep 4, 2020

@diamantidis Thanks for this great example! Have you been able to get the picker to be the same height as the keyboard? If you open the picker, then a textfield, then the picker I see a black gap above the picker
Screen Shot 2020-09-04 at 3 46 09 pm

@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