如何实现类似苹果笔记的UITextView多行标题功能?
实现思路:仿苹果笔记的UITextView标题行为
核心逻辑
以第一个换行符为分界点,将换行前的内容作为标题(应用大字号粗体样式),换行后的内容作为正文(应用普通样式)。通过监听文本变化动态调整样式,同时处理回车事件确保首次回车后光标自动定位到正文区域。
代码实现细节
绑定UITextView代理
让你的ViewController遵守UITextViewDelegate协议,并给目标UITextView设置delegate = self。文本变化时的样式区分
在textViewDidChange方法中分割标题与正文,分别应用不同样式:
func textViewDidChange(_ textView: UITextView) { guard let fullText = textView.text, !fullText.isEmpty else { // 空文本时可添加占位符逻辑 return } let attributedText = NSMutableAttributedString(string: fullText) // 标题样式配置 let titleAttrs: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 24, weight: .bold), .foregroundColor: UIColor.label ] // 正文字体配置 let bodyAttrs: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor.label ] if let newlineRange = fullText.range(of: "\n") { let titleLength = fullText.distance(from: fullText.startIndex, to: newlineRange.lowerBound) // 为标题区域设置样式 attributedText.addAttributes(titleAttrs, range: NSRange(location: 0, length: titleLength)) // 为正文区域设置样式(跳过换行符) let bodyStart = titleLength + 1 let bodyLength = fullText.count - bodyStart if bodyLength > 0 { attributedText.addAttributes(bodyAttrs, range: NSRange(location: bodyStart, length: bodyLength)) } } else { // 无换行时,全部内容作为标题 attributedText.addAttributes(titleAttrs, range: NSRange(location: 0, length: fullText.count)) } // 避免重复赋值影响性能,仅当样式或文本变化时更新 if textView.attributedText?.string != attributedText.string || textView.attributedText?.attributes(at: 0, effectiveRange: nil) != titleAttrs { textView.attributedText = attributedText } }
- 回车事件的光标处理
在shouldChangeTextIn方法中处理首次回车后的光标定位:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard text == "\n" else { return true } let fullText = textView.text ?? "" // 首次回车时,光标跳到正文开头 if fullText.range(of: "\n") == nil { DispatchQueue.main.async { let cursorOffset = fullText.count + 1 if let newPosition = textView.position(from: textView.beginningOfDocument, offset: cursorOffset) { textView.selectedTextRange = textView.textRange(from: newPosition, to: newPosition) } } } return true }
额外优化点
- 占位符定制:重写UITextView的
draw(_ rect: CGRect)方法,为标题和正文区域分别设置不同的提示文本(比如标题区显示"输入标题",正文区显示"输入内容")。 - 性能优化:缓存标题与正文的范围,仅当范围发生变化时再更新富文本样式,减少不必要的计算。
- 编辑体验优化:当用户删除所有换行符时,自动将正文内容合并到标题区域并恢复标题样式。
替代库推荐
如果不想自定义实现,可以尝试以下库:
- RichEditorView:支持富文本编辑,可通过配置实现标题与正文的样式区分,需额外适配换行触发规则。
- MarkdownTextView:基于Markdown语法,可调整默认规则,将无#标记的首段识别为标题。
内容的提问来源于stack exchange,提问作者Bugrym
相关产品推荐
相关产品推荐

