Adding Dropdown option to UITextField

Ajay Singh Thakur
6 min readDec 11, 2022

--

Requirement.

To add a dropdown option when clicking on the text field instead of typing manually, and selecting only from the given option.

dropdown view screenshot
End Result

Research

Adding drop-down options to the text field is one of the common UX practice around the mobile and web, the best and nearest option by searching and researching around I found these blogs.

After reading the first article you can implement your own dropdown, and if you don’t have time you can go for the second option.

I went with my own custom implementation, below are the details of the implementation.

Prerequisite

Get Started

Initial Setup

  1. Create Project.
    Let’s get started and create a new project in Xcode. Launch Xcode, then click “Create a new Xcode project” in the Welcome to Xcode window or choose File > New > Project, Select platform as iOS and template as App, after filling in all the fields select UserInterface as Storyboard.
  2. Adding Textfield.
    Now we are going to add textfield and setup its delegate.

Inside ViewController.swift create and initialize the textfield.

lazy var myTextField: UITextField = {
let textField = UITextField()
textField.font = UIFont.systemFont(ofSize: 17)
textField.borderStyle = .roundedRect
textField.layer.cornerRadius = 6
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.darkGray.cgColor
return textField
}()

Added constraint, set delegate and add it to the viewcontroller view.

private func configureConstraints() {

[myTextField].forEach { view in
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view)
}

// get safe area
let safeAreaGuide = self.view.safeAreaLayoutGuide

NSLayoutConstraint.activate([

myTextField.topAnchor.constraint(equalTo: safeAreaGuide.topAnchor, constant: 64),
myTextField.leadingAnchor.constraint(equalTo: safeAreaGuide.leadingAnchor, constant: 16),
myTextField.trailingAnchor.constraint(equalTo: safeAreaGuide.trailingAnchor, constant: -16),

])
}

calling the above function and setting delegate your ViewDidLoad will look like below.

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myTextField.delegate = self
self.configureConstraints()
}

after the above steps, you will get an error to set delegate like this Cannot assign a value of type 'ViewController' to type, add the extension to ViewController confirming to UITextFieldDelegate

extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return false
}

func textFieldDidEndEditing(_ textField: UITextField) {
}
}

Now your initial setup is all completed, you can go ahead and run your project you will see the textfield where you can type your input.

Dropdown View Setup

As you have textfield and other things in place, we have to now present the dropdown view instead of the keyboard and allow selection from it.

  • Add Dropdrop View with constraints.

The first step is to disable typing and keyboard popups, instead showing an empty view on editing start and hiding it when editing ends.

Adding view as property.

private let dropDownView = UIView()

make it hidden by default inside viewDidLoad and add some background color, i am adding black

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myTextField.delegate = self
dropDownView.isHidden = true
dropDownView.backgroundColor = UIColor.black
self.configureConstraints()
}

Modify the constraints and add the dropdownview to the controller, below is the modified configureConstraints method

private func configureConstraints() {

[myTextField, dropDownView].forEach { view in
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view)
}

// get safe area
let safeAreaGuide = self.view.safeAreaLayoutGuide

NSLayoutConstraint.activate([

myTextField.topAnchor.constraint(equalTo: safeAreaGuide.topAnchor, constant: 64),
myTextField.leadingAnchor.constraint(equalTo: safeAreaGuide.leadingAnchor, constant: 16),
myTextField.trailingAnchor.constraint(equalTo: safeAreaGuide.trailingAnchor, constant: -16),

dropDownView.topAnchor.constraint(equalTo: myTextField.bottomAnchor, constant: 1),
dropDownView.leadingAnchor.constraint(equalTo: myTextField.leadingAnchor),
dropDownView.trailingAnchor.constraint(equalTo: myTextField.trailingAnchor),

dropDownView.heightAnchor.constraint(equalToConstant: 200)

])

}
  • Disable text entry and hide unihde view.

Now modify the UITextFieldDelegate methods to show and hide the dropdown view

extension ViewController: UITextFieldDelegate {
// to hide the text editing and directly assigning the
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
print("prevent the text field from opening")
dropDownView.isHidden = false
return false
}

func textFieldDidEndEditing(_ textField: UITextField) {
dropDownView.isHidden = true
}
}

Run the project and click on textfield, you will see the black view appearing at the bottom of textfield.

Making Dropdown View

Now everything is setup the textfield and the empty view which show and hide on editing start-stop, we have to subclass view to host uitableview to show the list of options in the drop-down and add selection capability.

  1. Subclassing UIView as DropdownView

Now lets create a subclass of UIView to host uitableview to show the options.

class DropdownView: UIView {
let tableView = UITableView.init(frame: CGRect.zero, style: UITableView.Style.plain)

// writing custom init
required init() {
super.init(frame: .zero)
configureUI()
}

@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func configureUI() {

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.delegate = self
tableView.dataSource = self

tableView.backgroundColor = tableBackground

tableView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(tableView)
// get safe area
let safeAreaGuide = safeAreaLayoutGuide

NSLayoutConstraint.activate([

tableView.topAnchor.constraint(equalTo: safeAreaGuide.topAnchor),
tableView.bottomAnchor.constraint(equalTo: safeAreaGuide.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: safeAreaGuide.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: safeAreaGuide.trailingAnchor),

])
}
}

extension DropdownView: UITableViewDelegate, UITableViewDataSource {func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = "\(indexpath.row)"
cell.textLabel?.textColor = .black
return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//TODO: Add
}
}

Now again run the project you will see the

Adding datasource and delegate.

We have tableview setup inside DropdownView now we have to pass our datasrouce and allow selection for it. For selection we need to use delegate or closure to pass data backward, we are going to use protocol and delegate for it.

For DataSource add property inside DropdownView

class DropdownView: UIView {
private var dataSource: [String]?
}

For data passing create a protocol to pass data backward, write it above DropdownView class inside the same file.

protocol DropDownDelegate: AnyObject {
func onSelected(_ value: String)
}

add delegate variable inside DropdownView

class DropdownView: UIView {
weak var delegate: REDropDownDelegate?
}

Modify the init of DropdownView to inject both datasource and delegate confirm class

class DropdownView: UIView {    // DI
required init(delegate: REDropDownDelegate, dataSource: [String]) {
super.init(frame: .zero)
self.delegate = delegate
self.dataSource = dataSource
configureUI()
}
}

Finally modify the UITableViewDelegate and UITableViewDataSource methods to show data source passed and pass data back on click

extension DropdownView: UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource?.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if let dataSource = dataSource {
cell.textLabel?.text = dataSource[indexPath.row]
}
return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let dataSource = dataSource {
let value = dataSource[indexPath.row]
delegate?.onSelected(value)
}
}
}

Completion.

Everything is setup so, in ViewController change the normal view to DropdownView and your final code should look like this.

class ViewController: UIViewController {

lazy var myTextField: UITextField = {
let textField = UITextField()
textField.font = UIFont.systemFont(ofSize: 17)
textField.borderStyle = .roundedRect
textField.layer.cornerRadius = 6
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.darkGray.cgColor

return textField
}()

private var dropDownView: DropdownView!

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myTextField.delegate = selfdropDownView = DropdownView.init(delegate: self, dataSource: ["abc", "def", "ghi", "klm", "nop"])
dropDownView.isHidden = true
dropDownView.backgroundColor = .black
self.configureConstraints()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

}

private func configureConstraints() {

[myTextField, dropDownView].forEach { view in
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view)
}

// get safe area
let safeAreaGuide = self.view.safeAreaLayoutGuide

NSLayoutConstraint.activate([

myTextField.topAnchor.constraint(equalTo: safeAreaGuide.topAnchor, constant: 64),
myTextField.leadingAnchor.constraint(equalTo: safeAreaGuide.leadingAnchor, constant: 16),
myTextField.trailingAnchor.constraint(equalTo: safeAreaGuide.trailingAnchor, constant: -16),

dropDownView.topAnchor.constraint(equalTo: myTextField.bottomAnchor, constant: 1),
dropDownView.leadingAnchor.constraint(equalTo: myTextField.leadingAnchor),
dropDownView.trailingAnchor.constraint(equalTo: myTextField.trailingAnchor),

dropDownView.heightAnchor.constraint(equalToConstant: 200)

])

}

}
extension ViewController: REDropDownDelegate {
func onSelected(_ value: String) {
self.myTextField.text = value
self.dropDownView.isHidden = true
}
}
extension ViewController: UITextFieldDelegate {
// to hide the text editing and directly assigning the
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
print("prevent the text field from opening")
dropDownView.isHidden = false
return false
}

func textFieldDidEndEditing(_ textField: UITextField) {
dropDownView.isHidden = true
}
}

please find the completed project link: https://github.com/ajaysinghthakur/Dropdown-TextField

--

--

Ajay Singh Thakur
Ajay Singh Thakur

Written by Ajay Singh Thakur

SDE with expertise in iOS, also knows android and azure data engineer

Responses (1)