UITextView is a multiline text component of UIKit, inheriting from UIScrollView and supporting editing of attributed text with embedded links and images. According to Apple Developer documentation (2025), UITextView provides UITextViewDelegate with callbacks textViewDidChange, shouldInteractWithURL and shouldInteractWithTextAttachment for interaction handling. Unlike UITextField, UITextView supports scrolling, text styles via typingAttributes and dataDetectorTypes for auto-detection of links, addresses and dates. This is the main component for displaying content and entering multiline text in iOS applications.
Key Takeaways
UITextView is a UIKit component designed for displaying and editing multiline text. Unlike UILabel, UITextView supports text selection, editing, scrolling when content exceeds size, and attributed text. Being a subclass of UIScrollView, UITextView automatically includes vertical and horizontal scrolling when content overflows — this is configured via isScrollEnabled. The class supports editing through the isEditable property: set to false for read-only viewing mode. UITextView works with NSLayoutManager through the TextKit system: NSTextStorage (attributed text storage) → NSLayoutManager (line layout) → NSTextContainer (text area geometry). This architecture allows custom layouts with image wrapping, multi-column text and complex text layouts. UITextView is used for displaying articles, descriptions, logs, comments and any multiline texts in iOS.
The difference between UITextView and UITextField goes far beyond the number of lines. UITextField is a single-line UIControl with placeholder support, left/right views and clear button. UITextView is a multiline UIScrollView with a full TextKit system for attributed text. UITextField does not support NSAttributedString at the display level (only via attributedText with limitations), UITextView handles NSAttributedString fully with different fonts, paragraph styles and embedded images. UITextField has a built-in clear button (clearButtonMode), UITextView does not — a custom implementation is required. UITextField automatically resizes to content (intrinsicContentSize), UITextView requires manual height management via contentSize observation or Auto Layout constraints. UITextView supports dataDetectorTypes (auto-detection of links), UITextField supports it since iOS 16+. For multiline text input (comments, notes, messages) choose UITextView. For form fields (name, email, password) use UITextField.
| Feature | UITextView | UITextField |
|---|---|---|
| Lines | Multiline | Single-line |
| Placeholder | No (custom) | Built-in |
| Text Attributes | Full NSAttributedString | Limited |
| Data Detectors | Full support | iOS 16+ |
| Inline Images | Via NSTextAttachment | Not supported |
| Scrolling | Built-in (UIScrollView) | No |
| Left/Right View | No | Built-in |
NSAttributedString is a fundamental capability of UITextView that distinguishes it from UITextField and UILabel. An attributed string allows setting different fonts, colors, line spacing, alignment and links within a single piece of text. Properties: textView.attributedText = attributedString. Key NSAttributedString.Key attributes: font (.systemFont, .boldSystemFont, custom UIFont), foregroundColor, backgroundColor, paragraphStyle (NSMutableParagraphStyle with lineSpacing, alignment, lineBreakMode), link (URL for interactive links), underlineStyle, strikethroughStyle, shadow, baselineOffset, kern (letter spacing). To create an attributedString use init(string:, attributes:) or NSMutableAttributedString to add attributes to different ranges. UITextView automatically displays links (link attribute) as interactive — pressing them triggers the delegate method shouldInteractWithURL. To reset attributes for new input use typingAttributes.
import UIKit
let textView = UITextView()
textView.isEditable = false
textView.isScrollEnabled = true
textView.dataDetectorTypes = [.link, .phoneNumber]
let attributedString = NSMutableAttributedString(
string: "Visit our website or call +1-800-555-0199"
)
attributedString.addAttributes([
.font: UIFont.systemFont(ofSize: 16),
.foregroundColor: UIColor.label,
], range: NSRange(location: 0, length: 11))
let linkRange = (attributedString.string as NSString)
.range(of: "website")
attributedString.addAttributes([
.link: URL(string: "https://example.com")!,
.foregroundColor: UIColor.systemBlue,
], range: linkRange)
textView.attributedText = attributedString
UITextViewDelegate is a protocol that manages editing and interaction with text. Main methods: textViewDidBeginEditing(textView) — called when editing starts; textViewDidEndEditing — when editing ends; textViewDidChange — on every text change; textViewDidChangeSelection — when selection changes; textViewShouldInteractWithURL — when a link is tapped (returns Bool, allows transition); textViewShouldInteractWithTextAttachment — when an embedded image is tapped. To handle links inside UITextView override shouldInteractWithURL — in it you can: open URL in SFSafariViewController, show UIAlertController for confirmation, handle custom URL schemes. Important: if textView.isEditable = true, links become unavailable for single tap — double tap is required. For interactive links in editable textView use gesture recognizers.
SFSafariViewController is the standard way to open links in iOS applications. In the shouldInteractWithURL method create SFSafariViewController with the URL and present it modally or via navigationController. For universal links use UIApplication.shared.open(url, options) — but this exits the application. To prevent link opening return false in shouldInteractWithURL and handle the URL yourself.
NSTextContainer is part of the TextKit architecture, defining the geometric area where UITextView text is displayed. Each UITextView has a standard textContainer, configurable via: textView.textContainerInset — text insets from textView edges (UIEdgeInsets); textView.textContainer.lineFragmentPadding — horizontal padding inside each line; textView.textContainer.maximumNumberOfLines — line limit (0 = no limit); textView.textContainer.exclusionPaths — array of UIBezierPath for areas that text should wrap around (images, custom shapes). ExclusionPaths allow complex layouts: text wraps around a circular profile picture, icon or any arbitrary shape. For dynamic UITextView height observe contentSize via KVO or delegate and update constraints.
// Dynamic UITextView height via contentSize
func textViewDidChange(textView: UITextView) {
let fixedWidth = textView.frame.size.width
let newSize = textView.sizeThatFits(
CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude)
)
heightConstraint.constant = newSize.height
UIView.animate(withDuration: 0.1) {
self.view.layoutIfNeeded()
}
}
// ExclusionPath for image wrapping
let imageFrame = CGRect(x: 16, y: 100, width: 80, height: 80)
let exclusionPath = UIBezierPath(rect: imageFrame)
textView.textContainer.exclusionPaths = [exclusionPath]
dataDetectorTypes is a UITextView property that automatically recognizes and makes interactive certain data types in text: .link (URL, email), .phoneNumber (phone numbers), .address (addresses), .calendarEvent (dates and events), .flightNumber (flight numbers), .lookupSuggestion (search suggestions), .trackingNumber (tracking numbers), .money (amounts), .shipmentTrackingNumber, .all (all types). Data Detector works with NSDataDetector and URL, automatically highlighting detected data. To customize handling set dataDetectorTypes and override shouldInteractWithURL in the delegate. Important: dataDetectorTypes only works for URL and phoneNumber without additional setup — other types may require iOS 16+. Data Detector does not conflict with link attribute in NSAttributedString — both mechanisms work in parallel. For read-only UITextView with content (news, articles) set dataDetectorTypes = [.link, .phoneNumber].
Let's look at two practical examples of using UITextView in iOS applications. The first — a read-only text view with attributed text and data detector for displaying content. The second — an editable text view with lazy placeholder and dynamic height for a comment form. Both examples use UIKit best practices: delegate protocol, Auto Layout and TextKit.
Read-only mode is enabled by setting isEditable = false and isSelectable = true. This allows the user to select text and tap links, but not edit. Set dataDetectorTypes for auto-detection of links, backgroundColor = .clear for transparent background. For long content use attributedText with NSMutableParagraphStyle lineSpacing for readability. Add link attributes for key links inside the text.
import UIKit
class ArticleViewController: UIViewController {
let contentView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
contentView.isEditable = false
contentView.isSelectable = true
contentView.dataDetectorTypes = [.link, .phoneNumber]
contentView.backgroundColor = .clear
contentView.textContainerInset = UIEdgeInsets(
top: 8, left: 16,
bottom: 8, right: 16
)
contentView.delegate = self
loadArticleContent()
}
func loadArticleContent() {
guard let url = URL(string: "https://example.com/article")
else { return }
// Load content from URL and set attributedText
}
}
Placeholder for UITextView is not built-in — implement it via a UILabel over textView or via attributedText with isEmpty check. A simple approach: a UILabel with gray text that hides when editing begins. In textViewDidChange check for text presence and hide/show placeholder. For a more elegant solution use the typingAttributes property to set the input text color. Dynamic textView height is achieved by observing contentSize and updating constraints — this is a standard pattern for chats and comment forms.
Frequently Asked Questions
UITextView does not have a built-in placeholder. The standard solution: add a UILabel with gray text over the textView. In textViewDidBeginEditing hide the label, in textViewDidEndEditing show it if the text is empty. Alternatively: use attributedText with gray color as a placeholder and reset it when editing begins. For a ready-made solution use the KMPlaceholderTextView library or ReactorKit.
Set textContainerInset = .zero and lineFragmentPadding = 0: textView.textContainerInset = UIEdgeInsets.zero; textView.textContainer.lineFragmentPadding = 0. Additionally: textView.contentInset = .zero. This will completely remove insets from textView edges to the text. Note that lineFragmentPadding defaults to 5.
Check: isSelectable = true (required for interaction), dataDetectorTypes includes .link, the delegate does not return false in shouldInteractWithURL. If textView.isEditable = true, links are unavailable for single tap — double tap is required. For interactive links in editable textView add UITapGestureRecognizer on top of the textView and handle URLs manually.
Set isScrollEnabled = true and add a height constraint with a fixed value. Text exceeding the height will scroll. For optimal UX use maxHeight: observe contentSize and set the height constraint no higher than maxHeight. When maxHeight is exceeded enable scrollEnabled. This is a standard pattern for message and comment input fields.
Use NSTextAttachment: create an object, set the image and bounds, then create an NSAttributedString from the attachment and insert it into textView.attributedText. Example: let attachment = NSTextAttachment(); attachment.image = image; attachment.bounds = CGRect(x, y, width, height); let attributedString = NSAttributedString(attachment: attachment). The image will display inline in the text and scroll with it.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also