iOS UITextView and UITextViewDelegate

Filed Under: iOS

In this tutorial, we’ll be discussing the UITextView element and implement the various forms of it in our application.

iOS UITextView

Unlike its name, a UITextView is not just a text view. You can edit it, type in it, scroll it.
A UITextView is a multiline text region. It has a built-in UIScrollView.

By default a UITextView is editable. To disabled editing you need to set the property isEditable as false.

To create it programmatically you need to create a rectangle with a width and height specified:


let uiTextView = UITextView()
uiTextView.frame = CGRect(x: 0, y: 0, width: 200, height: 150)
UITextView vs UITextField
UITextView is for multi-line input whereas UITextField by default is for a single line only.

UITextView does not provide a placeholder/hint text by default.

UITextViewDelegate

The UITextViewDelegate protocol defines a set of optional methods that gets triggered when the text is being edited. All the protocol methods are optional.

Following are some of the methods present in the UITextViewDelegate.

textViewDidBeginEditing/textViewWillBeginEditing
textViewDidEndEditing/textViewWillEndEditing
textViewDidChange – Gets called when the text is editted.
textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) – This returns a boolean which asks if the text should be replaced or not.

An interesting use case would be to set the character limit on the UITextView:


func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText newText: String) -> Bool {
        return textView.text.count + (newText.count - range.length) <= 140
    }

This sets the character limit to 140. Just like twitter!

Enough talk. Lets code!

UITextView Implementation

Create a new XCode project and let's get started:

In the Main.storyboard drag the UITextView onto the ViewController and do the following instructions:

ios uitextview uitextviewdelegate storyboard

  1. Set auto-layout constraints on the UITextView. We've set it to the bottom of the screen with a fixed height.
  2. Link the UITextView to the ViewController.swift file using the IBOutlet via Assistant Editor.
  3. .

On running the application on the simulator and device we get:

ios uitextview below keyboard output

That's WEIRD!

The keyboard pops up over the UITextView.
On clicking return, the keyboard is getting dismissed.

We need to fix these.

Add the following code in your ViewController.swift file:


import UIKit

class ViewController: UIViewController, UITextViewDelegate {

    @IBOutlet weak var bottomTextView: UITextView!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        bottomTextView.delegate = self
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateTextView(notification:)), name: Notification.Name.UIKeyboardWillChangeFrame, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateTextView(notification:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
    
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
        textView.backgroundColor = UIColor.lightGray
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        textView.backgroundColor = UIColor.white
    }
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if text == "\n" {
            textView.resignFirstResponder()
            return false
        }
        return true
    }
    
    @objc func updateTextView(notification: Notification)
    {
        if let userInfo = notification.userInfo
        {
            let keyboardFrameScreenCoordinates = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
            
            let keyboardFrame = self.view.convert(keyboardFrameScreenCoordinates, to: view.window)
            
            if notification.name == Notification.Name.UIKeyboardWillHide{
                view.frame.origin.y = 0
            }
            else{ 
                view.frame.origin.y = -keyboardFrame.height
            }
        }
    }
    

}

In the above code, we've implemented the UITextViewDelegate Protocol, implemented keyboard dismiss logic and moved the UITextView above the keyboard.

  1. To set it on the UITextView we do: bottomTextView.delegate = self
  2. We've implemented three functions of the UITextViewDelegate - textViewDidBeginEditing, textViewDidEndEditing, textView(shouldChangeTextIn:). We change the background color of the UITextView when editting begins and ends.
    resignFirstResponder() is used to dismiss the Keyboard. To do so on a UITextView we do:

    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if text == "\n" {
                textView.resignFirstResponder()
                return false
            }
            return true
        }
    
  3. In the viewDidLoad, we've added two Notification observers which detect changes in Keyboard and trigger the function updateTextView.
  4. Inside the updateTextView we change the position of the UITextView depending on the notification name.
  5. To move the UITextView above the keyboard, we calculate the keyboard height programmatically and shift the whole view upwards by that height.

The output when the application is now run is:

ios uitextview above keyboard output

Next, we'll look into Auto-Sizing UITextViews

Auto-Size UITextView

UITextViews size can be changed depending on the content size of it.
Let's create another ViewController in our storyboard and connect it via a Button segue.

ios uitextview auto sizing storyboard

  1. Add the Button to the current ViewController and set the constraints.
  2. Create another ViewController Scene and drag the Button using Ctrl+ Click to create a segue. Create a new Swift File SecondViewController.swift and set the name in the right pane

In the SecondViewController.swift, we'll add the UITextView programmatically and set the constraints through Swift Code as well.

In the below code, we've:

  • Added Placeholders on the UITextView
  • Auto-increased the height of the UITextView when more content is typed.
  • Set font size on the UITextView
  • 
    import UIKit
    
    class SecondViewController: UIViewController, UITextViewDelegate {
        let topTextView = UITextView()
        override func viewDidLoad() {
            super.viewDidLoad()
       
            addAnotherTextView()
            
        }
        
        func addAnotherTextView()  {
            
            topTextView.delegate = self
            topTextView.text = "Enter your notes here"
            topTextView.frame = CGRect(x: 0, y: 0, width: 200, height: 150)
            topTextView.font = .systemFont(ofSize: 20)
            
            view.addSubview(topTextView)
            topTextView.translatesAutoresizingMaskIntoConstraints = false
            [
                topTextView.topAnchor.constraint(equalTo: view!.safeAreaLayoutGuide.topAnchor),
                topTextView.leadingAnchor.constraint(equalTo: view!.leadingAnchor),
                topTextView.trailingAnchor.constraint(equalTo: view!.trailingAnchor),
                topTextView.heightAnchor.constraint(equalToConstant: 40)
                ].forEach{
                    $0.isActive = true
                    
            }
        }
        
        
        
        func textViewDidBeginEditing(_ textView: UITextView) {
            textView.backgroundColor = UIColor.lightGray
            
            if (textView.text == "Enter your notes here")
            {
                textView.text = ""
                textView.textColor = .black
            }
            
        }
        
        func textViewDidEndEditing(_ textView: UITextView) {
            textView.backgroundColor = UIColor.white
            
            if (textView.text == "")
            {
                textView.text = "Enter your notes here"
                textView.textColor = .lightGray
            }
        }
        
        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if text == "\n" {
                textView.resignFirstResponder()
                return false
            }
            
            return true
            
        }
        
        func textViewDidChange(_ textView: UITextView) {
            let size = CGSize(width: view.frame.width, height: .infinity)
            let approxSize = textView.sizeThatFits(size)
            
            textView.constraints.forEach{(constraint) in
                
                if constraint.firstAttribute == .height
                {
                    constraint.constant = approxSize.height
                }
            }
        }    
    }
    
    

    textViewDidChange is a part of the UITextViewDelegate protocol. This gets triggered whenever text is added.
    Each time we calculate the size of the text and increase the height constraint of the UITextView.

    The output of the application in action is:

    ios auto sizing uitextview output

    As you can see the height of the UITextView is increased. But the UITextView keeps pushing the top line above.

    This is because of the ScrollBars. We can disable them by adding the following line inside the addAnotherTextView() function:

    
    topTextView.isScrollEnabled = false
    

    ios uitextview auto size scroll disabled

    Now the top line doesn't keep going out of the screen.

    Let's look at another use case.

    Auto Sizing the UITextView till a specific height

    We can size the UITextView such that it can expand only to a specific height.

    Change your textViewDidChange function to:

    
    func textViewDidChange(_ textView: UITextView)
        {
            if topTextView.contentSize.height >= 120.0
            {
                topTextView.isScrollEnabled = true
            }
            else
            {
                let size = CGSize(width: view.frame.width, height: .infinity)
                let approxSize = textView.sizeThatFits(size)
                
                textView.constraints.forEach {(constraint) in
                    
                            if constraint.firstAttribute == .height{
                                    constraint.constant = approxSize.height
                                }
                            }
                topTextView.isScrollEnabled = false
            }
        }
    
    

    The logic is pretty simple:

    Every time you increase the height, first check if the max height is reached or not.

    If it is, don't increase the height anymore and just enable the scrolling.
    If the max height isn't reached, keep the scrollbars disabled.

    The output of the application in action is:

    ios uitextview upper limit height

    This brings an end to this tutorial on UITextView. We implement some practical scenarios that might help you in your iOS Development by using UITextViewDelegate and also Notification Observers.

    You can download the project from the link below:

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    close
    Generic selectors
    Exact matches only
    Search in title
    Search in content
    Search in posts
    Search in pages