Complete Guide to Implementing Placeholder Text in UITextView with Swift

Nov 20, 2025 · Programming · 14 views · 7.8

Keywords: Swift | UITextView | Placeholder | iOS Development | Text Input

Abstract: This article provides a comprehensive exploration of implementing placeholder functionality for UITextView in iOS development. By analyzing core methods of the UITextViewDelegate protocol, it presents two main implementation strategies: a simple color-based approach and an advanced real-time text monitoring solution. The discussion covers cursor control, view lifecycle management, performance optimization, and comparative analysis of different scenarios.

Introduction

In iOS application development, UITextView is widely used as a multi-line text input control, but unlike UITextField, it does not natively provide a placeholder property. This imposes additional implementation burden on developers. Based on the Swift language, this article deeply analyzes how to programmatically add placeholder functionality to UITextView, ensuring consistent user experience.

Fundamental Concepts and Implementation Principles

The core functionality of a placeholder is to display hint text when the text view is empty and automatically hide it when the user starts typing. Implementing this requires understanding key methods of the UITextViewDelegate protocol:

protocol UITextViewDelegate : NSObjectProtocol {
    optional func textViewDidBeginEditing(_ textView: UITextView)
    optional func textViewDidEndEditing(_ textView: UITextView)
    optional func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
    optional func textViewDidChangeSelection(_ textView: UITextView)
}

These methods correspond to key events such as the start and end of text editing, content changes, and selection range changes, providing the necessary event response mechanism for placeholder implementation.

Solution #1: Color-Based Toggle on Edit State

This solution is suitable for scenarios where the placeholder should disappear immediately when editing begins. Implementation steps are as follows:

First, initialize the text view and set placeholder styles in the view controller:

class ViewController: UIViewController {
    @IBOutlet weak var textView: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
        textView.text = "Enter text here..."
        textView.textColor = UIColor.lightGray
    }
}

Next, implement the textViewDidBeginEditing method to clear the placeholder when the user starts editing:

extension ViewController: UITextViewDelegate {
    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.textColor == UIColor.lightGray {
            textView.text = nil
            textView.textColor = UIColor.black
        }
    }
}

Finally, check if the content is empty when editing ends to decide whether to restore the placeholder:

func textViewDidEndEditing(_ textView: UITextView) {
    if textView.text.isEmpty {
        textView.text = "Enter text here..."
        textView.textColor = UIColor.lightGray
    }
}

Solution #2: Advanced Implementation with Real-Time Text Monitoring

This solution provides finer control, ensuring the placeholder is always visible when the text view is empty, even during editing.

Initial setup requires more complex configuration:

override func viewDidLoad() {
    super.viewDidLoad()
    textView.delegate = self
    textView.text = "Enter text here..."
    textView.textColor = UIColor.lightGray
    
    // Optional: Automatically gain focus and set cursor position
    textView.becomeFirstResponder()
    textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}

The core lies in the implementation of the shouldChangeTextIn method, which is called before each text change:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    let currentText = textView.text
    let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)
    
    if updatedText.isEmpty {
        // Text will be empty, restore placeholder
        textView.text = "Enter text here..."
        textView.textColor = UIColor.lightGray
        textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
    } else if textView.textColor == UIColor.lightGray && !text.isEmpty {
        // Currently showing placeholder and there is new input
        textView.textColor = UIColor.black
        textView.text = text
    } else {
        return true
    }
    
    return false
}

To prevent users from moving the cursor while the placeholder is displayed, selection change monitoring is also needed:

func textViewDidChangeSelection(_ textView: UITextView) {
    if self.view.window != nil {
        if textView.textColor == UIColor.lightGray {
            textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
        }
    }
}

Alternative Solution: Floating Label Implementation

In addition to modifying text content, placeholder effects can also be achieved by overlaying a UILabel:

class NotesViewController: UIViewController {
    @IBOutlet var textView: UITextView!
    var placeholderLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
        
        placeholderLabel = UILabel()
        placeholderLabel.text = "Enter text here..."
        placeholderLabel.font = .italicSystemFont(ofSize: (textView.font?.pointSize)!)
        placeholderLabel.sizeToFit()
        textView.addSubview(placeholderLabel)
        placeholderLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
        placeholderLabel.textColor = .tertiaryLabel
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
}

extension NotesViewController: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        placeholderLabel?.isHidden = !textView.text.isEmpty
    }
}

Performance Optimization and Best Practices

In actual development, the following optimization points should be considered:

Memory Management: Ensure proper resource release when the view is destroyed, especially when using the UILabel solution:

deinit {
    placeholderLabel?.removeFromSuperview()
    placeholderLabel = nil
}

Text Length Limits: Combine placeholder implementation with character limit functionality:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    let currentText = textView.text
    let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)
    
    // Limit maximum characters to 200
    return updatedText.count <= 200
}

Multilingual Support: Placeholder text should support localization:

textView.text = NSLocalizedString("placeholder_text", comment: "Placeholder text")

Solution Comparison and Selection Recommendations

Each of the three solutions has its advantages and disadvantages:

Selection should be based on specific requirements: if only basic placeholder functionality is needed, Solution #1 is recommended; if finer control is required, consider Solution #2; if pursuing the best visual experience, the Floating Label solution is a better choice.

Conclusion

By deeply analyzing the UITextViewDelegate protocol and Swift language features, this article provides complete placeholder implementation solutions. Developers can choose the most suitable implementation method based on specific needs to ensure optimal user experience for their applications. As the Swift language continues to evolve, these implementation solutions can be further optimized and extended.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.