UITextView是UIKit的多行文本组件,继承自UIScrollView,支持编辑带有嵌入链接和图像的属性文本。根据Apple Developer(2025)文档,UITextView提供UITextViewDelegate,包含textViewDidChange、shouldInteractWithURL和shouldInteractWithTextAttachment回调以处理交互。与UITextField不同,UITextView支持滚动、通过typingAttributes设置文本样式以及dataDetectorTypes自动检测链接、地址和日期。这是iOS应用中显示内容和输入多行文本的主要组件。
要点
UITextView是UIKit组件,用于显示和编辑多行文本。与UILabel不同,UITextView支持文本选择、编辑、超出尺寸时滚动以及属性文本。作为UIScrollView的子类,UITextView在内容超出时自动启用垂直和水平滚动——这通过isScrollEnabled配置。该类通过isEditable属性支持编辑:设置为false以进入只读模式。UITextView通过TextKit系统与NSLayoutManager协作:NSTextStorage(存储属性文本)→ NSLayoutManager(行布局)→ NSTextContainer(文本区域几何形状)。这种架构允许创建图像周围文本环绕的自定义布局、多列文本和复杂的文本布局。UITextView用于在iOS中显示文章、描述、日志、评论和任何多行文本。
UITextView和UITextField之间的差异远不止行数。UITextField是单行UIControl,支持占位符、left/right视图和清除按钮。UITextView是多行UIScrollView,拥有完整的TextKit系统用于属性文本。UITextField在显示层面不支持NSAttributedString(只能通过attributedText且有限制),UITextView完全处理NSAttributedString,支持不同字体、段落样式和嵌入图像。UITextField内置清除按钮(clearButtonMode),UITextView没有——需要自定义实现。UITextField自动根据内容调整大小(intrinsicContentSize),UITextView需要通过contentSize观察或Auto Layout约束手动管理高度。UITextView支持dataDetectorTypes(自动检测链接),UITextField从iOS 16+开始支持。多行文本输入(评论、笔记、消息)请选择UITextView。表单字段(姓名、电子邮件、密码)请使用UITextField。
| 功能 | UITextView | UITextField |
|---|---|---|
| 行 | 多行 | 单行 |
| 占位符 | 无(自定义) | 内置 |
| 文本属性 | 完整NSAttributedString | 有限 |
| Data Detectors | 完全支持 | iOS 16+ |
| 内联图像 | 通过NSTextAttachment | 不支持 |
| 滚动 | 内置(UIScrollView) | 无 |
| 左/右视图 | 无 | 内置 |
NSAttributedString是UITextView的基础功能,使其区别于UITextField和UILabel。属性字符串允许在同一文本中设置不同字体、颜色、行间距、对齐方式和链接。属性:textView.attributedText = attributedString。NSAttributedString.Key的关键属性:font(.systemFont,.boldSystemFont,自定义UIFont),foregroundColor,backgroundColor,paragraphStyle(带lineSpacing、alignment、lineBreakMode的NSMutableParagraphStyle),link(交互式链接的URL),underlineStyle,strikethroughStyle,shadow,baselineOffset,kern(字符间距)。使用init(string:,attributes:)创建attributedString,或使用NSMutableAttributedString向不同范围添加属性。UITextView自动将链接(link attribute)显示为交互式——点击时将调用委托方法shouldInteractWithURL。使用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是管理编辑和文本交互的协议。主要方法:textViewDidBeginEditing(textView) — 编辑开始时调用;textViewDidEndEditing — 结束时;textViewDidChange — 文本每次变化时;textViewDidChangeSelection — 选择变化时;textViewShouldInteractWithURL — 点击链接时(返回Bool,是否允许导航);textViewShouldInteractWithTextAttachment — 点击嵌入图像时。要处理UITextView内的链接,重写shouldInteractWithURL——在其中可以:在SFSafariViewController中打开URL,显示UIAlertController进行确认,处理自定义URL方案。重要:如果textView.isEditable = true,链接在双击前无法点击——编辑时交互链接请使用手势识别器。
SFSafariViewController是在iOS应用中打开链接的标准方式。在shouldInteractWithURL方法中创建带有URL的SFSafariViewController,并通过模态或navigationController呈现。对于通用链接,使用UIApplication.shared.open(url, options)——但这会离开应用。要禁止打开链接,在shouldInteractWithURL中返回false并自行处理URL。
NSTextContainer— TextKit架构的一部分,定义了UITextView文本的几何显示区域。每个UITextView都有标准textContainer,可通过以下方式配置:textView.textContainerInset — 文本到textView边界的边距(UIEdgeInsets);textView.textContainer.lineFragmentPadding — 每行内的水平边距;textView.textContainer.maximumNumberOfLines — 行数限制(0 = 无限制);textView.textContainer.exclusionPaths — UIBezierPath数组,用于文本应绕开的区域(图像、自定义形状)。ExclusionPaths允许创建复杂布局:文本围绕圆形个人照片、图标或任意轮廓流动。对于UITextView的动态大小,通过KVO或委托观察contentSize并更新约束。
// 通过contentSize实现动态UITextView高度
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
let imageFrame = CGRect(x: 16, y: 100, width: 80, height: 80)
let exclusionPath = UIBezierPath(rect: imageFrame)
textView.textContainer.exclusionPaths = [exclusionPath]
dataDetectorTypes— UITextView的属性,自动识别并使文本中的特定数据类型可交互:.link(URL,电子邮件),.phoneNumber(电话号码),.address(地址),.calendarEvent(日期和事件),.flightNumber(航班号),.lookupSuggestion(搜索建议),.trackingNumber(跟踪号),.money(金额),.shipmentTrackingNumber,.all(所有类型)。Data Detector与NSDataDetector和URL配合工作,自动高亮检测到的数据。要自定义处理,设置dataDetectorTypes并在委托中重写shouldInteractWithURL。重要:dataDetectorTypes无需额外配置仅适用于URL和phoneNumber——其他类型可能需要iOS 16+。Data Detector与NSAttributedString中的link attribute不冲突——两种机制并行工作。对于只读UITextView(新闻、文章),设置dataDetectorTypes = [.link, .phoneNumber]。
让我们看看在iOS应用中使用UITextView的两个实际示例。第一个——带有属性文本和data detector的只读文本视图,用于显示内容。第二个——带有惰性占位符和动态高度的可编辑文本视图,用于评论表单。两个示例均使用UIKit最佳实践:委托协议、Auto Layout和TextKit。
只读模式通过设置isEditable = false和isSelectable = true启用。这允许用户选择文本和点击链接,但不能编辑。为自动检测链接设置dataDetectorTypes,为透明背景设置backgroundColor = .clear。显示长内容时,使用带有NSMutableParagraphStyle lineSpacing的attributedText以提高可读性。为文本中的关键链接添加link attributes。
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 }
// 从URL加载内容并设置attributedText
}
}
占位符对于UITextView并非内置——通过textView上方的UILabel或通过attributedText配合isEmpty检查实现。简单方法:带有灰色文本的UILabel,编辑开始时隐藏。在textViewDidChange中检查文本是否存在并隐藏/显示占位符。更优雅的解决方案,使用typingAttributes属性设置输入文本的颜色。文本视图的动态高度通过观察contentSize和更新约束实现——这是聊天和评论表单的标准模式。
常见问题
UITextView没有内置占位符。标准解决方案:在textView上方添加带灰色文本的UILabel。在textViewDidBeginEditing中隐藏标签,在textViewDidEndEditing中如果文本为空则显示。替代方案:使用attributedText以灰色作为占位符,并在开始编辑时重置。现成解决方案请使用KMPlaceholderTextView或ReactorKit库。
设置textContainerInset = .zero和lineFragmentPadding = 0:textView.textContainerInset = UIEdgeInsets.zero; textView.textContainer.lineFragmentPadding = 0。另外:textView.contentInset = .zero。这将完全移除从textView边缘到文本的边距。请注意lineFragmentPadding默认值为5。
检查:isSelectable = true(交互必需),dataDetectorTypes包含.link,委托在shouldInteractWithURL中未返回false。如果textView.isEditable = true,链接无法通过单次点击——需要双击。在可编辑文本视图中使用交互链接时,请在textView上方添加UITapGestureRecognizer并手动处理URL。
设置isScrollEnabled = true并添加固定值的height约束。超出高度的文本将可滚动。为获得最佳用户体验,使用maxHeight:观察contentSize并将height约束设置不超过maxHeight。超出maxHeight时启用scrollEnabled。这是消息和评论输入字段的标准模式。
使用NSTextAttachment:创建对象,设置image和bounds,然后从attachment创建NSAttributedString并插入到textView.attributedText。示例:let attachment = NSTextAttachment(); attachment.image = image; attachment.bounds = CGRect(x, y, width, height); let attributedString = NSAttributedString(attachment: attachment)。图像将内嵌显示在文本中并随文本一起滚动。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。