UITextField Drop Down List In Swift

Datetime:2016-08-23 04:14:17          Topic: Swift           Share

This post provides a working example of a UITextField Drop Down List that allows a user to type in a value, or select from a list. It is somewhat similar to a feature you see in apps such as Uber, and also Kijiji (for Canadians like myself). If you want to jump quickly to running it, grab the demo app here in GitHub and give it a try.

My original implementation was needed for an Objective-C app, but the demo app for this post is a Swift version. Based on the required behavior, I implemented a drop down list using a UITableView , versus using a UIPickerView . The requirements I had were somewhat specific in terms of behavior and integration with another component. I didn’t find something off the shelf that met the requirements, so I went with the solution I’ll describe. It should be easy to tailor for your own needs if you are looking for something similar.

Here is a screenshot of what it looks like when the user has clicked on the UITextField . The list is open, and the user has the option to type a value, or select one from the list.

Here are the feature highlights for this screen above:

  • Displays 4 possible values in the list when the user touches the text field.
  • User can type in the field or select a value.
  • When the user starts typing, the list is closed.
  • At any time during typing, the user can touch the text field again to open the list, and replace the value in the text field.
  • The user can select a value from the list, and then type more text after that value.
  • The list must hide when clicking outside it (of course along with the keyboard).

Here is the complete code for the UIViewController in the project.

import UIKit
 
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
 
    // The sample values
    var values = ["123 Main Street", "789 King Street", "456 Queen Street", "99 Apple Street"]
    let cellReuseIdentifier = "cell"
    
    @IBOutletweak var heightConstraint: NSLayoutConstraint!
    
    // Using simple subclass to prevent the copy/paste menu
    // This is optional, and a given app may want a standard UITextField
    @IBOutletweak var textField: NoCopyPasteUITextField!
    @IBOutletweak var tableView: UITableView!
    
    // If user changes text, hide the tableView
    @IBActionfunc textFieldChanged(sender: AnyObject) {
        tableView.hidden = true
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
 
        tableView.delegate = self
        tableView.dataSource = self
        textField.delegate = self
        
        tableView.hidden = true
        
        // Manage tableView visibility via TouchDown in textField
        textField.addTarget(self, action: #selector(textFieldActive), forControlEvents: UIControlEvents.TouchDown)
    }
    
    override func viewDidLayoutSubviews()
    {
        // Assumption is we're supporting a small maximum number of entries
        // so will set height constraint to content size
        // Alternatively can set to another size, such as using row heights and setting frame
        heightConstraint.constant = tableView.contentSize.height
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    // Manage keyboard and tableView visibility
    override func touchesBegan(touches: Set<UITouch>, withEventevent: UIEvent?)
    {
        guardlet touch:UITouch = touches.first else
        {
            return;
        }
        if touch.view != tableView
        {
            textField.endEditing(true)
            tableView.hidden = true
        }
    }
    
    // Toggle the tableView visibility when click on textField
    func textFieldActive() {
        tableView.hidden = !tableView.hidden
    }
    
    // MARK: UITextFieldDelegate
    func textFieldDidEndEditing(textField: UITextField) {
        // TODO: Your app can do something when textField finishes editing
        print("The textField ended editing. Do something based on app requirements.")
    }
    
    func textFieldShouldReturn(textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
 
    // MARK: UITableViewDataSource
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSectionsection: Int) -> Int {
        return values.count;
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPathindexPath: NSIndexPath) -> UITableViewCell {
        let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell!
        // Set text from the data model
        cell.textLabel?.text = values[indexPath.row]
        cell.textLabel?.font = textField.font
        return cell
    }
    
    // MARK: UITableViewDelegate
    func tableView(tableView: UITableView, didSelectRowAtIndexPathindexPath: NSIndexPath) {
        // Row selected, so set textField to relevant value, hide tableView
        // endEditing can trigger some other action according to requirements
        textField.text = values[indexPath.row]
        tableView.hidden = true
        textField.endEditing(true)
    }
 
    func tableView(tableView: UITableView, heightForHeaderInSectionsection: Int) -> CGFloat {
        return 0.0
    }
}

I’ve provided comments in the code so will just summarize the main aspects and dispense with a large writeup.

The controller implements the UITableViewDataSource , UITableViewDelegate , and UITextFieldDelegate protocols for the UITableView and UITextField .

The UITableView is shown or hidden using the delegate methods appropriately. Since the requirement in my case was a limit of 5 items in the list, retrieved dynamically, the list height is sized based on the  tableView.contentSize.height , using a height constraint defined in the Storyboard and accessed in code. If constraints being accessed from code is new for you, I’ve described it in this previous post .

In the demo app that accompanies this post, I’ve shown just 4 items in the sample data. The UITableView is constrained to be the same width as the UITextField , which you can see by looking at the constraints in the Storyboard.

If you want the result of your editing and selecting in the UITextField to trigger some other action in your app, you do this in the  textFieldDidEndEditing method as also mentioned in the code comments.

I wanted to prevent the copy/paste/select menu from appearing, so to do that I implemented a subclass of UITextField . This involves overriding just one method, and the complete code for that is as follows.

import UIKit
 
class NoCopyPasteUITextField:UITextField {
 
    override func canPerformAction(action: Selector, withSendersender: AnyObject?) -> Bool {
        if action == #selector(NSObject.paste(_:)) || action == #selector(NSObject.copy(_:)) || action == #selector(NSObject.select(_:)) || action == #selector(NSObject.selectAll(_:)){
            return false
        }
        
        return super.canPerformAction(action, withSender: sender)
    }
}

In your particular case, you might want just a regular UITextField with the ability to copy/paste.

I used size classes in the demo to set a bigger font for Regular/Regular, so you can see that definition in the Storyboard.

This demo app should be fairly self-explanatory with the comments. It isn’t implemented as a custom control, but it was easy and quick, and I’ll probably use this technique again in the future. Although it is always nice to find a third party control, hopefully this post illustrates an easy way to achieve a drop down list by implementing a few delegate protocols with minimal code.

The completed project is here in GitHub .





About List