设置排除路径后UITextView的contentSize未随文本换行更新
UITextView设置exclusionPaths后无法自动增高的解决方案
这个确实是UITextView的非预期但符合系统设计逻辑的行为——当你给textContainer添加exclusionPaths实现文本绕排后,系统并不会自动更新contentSize。对于禁用滚动的TextView来说,这就意味着它无法根据绕排后的实际文本高度自动调整自身尺寸,最终导致文本被截断。
为什么contentSize没有变化?
contentSize的计算是基于文本在无排版限制(比如排除路径)下的默认排版结果,而exclusionPaths属于NSTextContainer的高级排版规则,系统并不会把它纳入contentSize的计算逻辑里,所以你看到设置前后contentSize.height没有变化是正常的。
解决方法:手动计算绕排后的文本高度
要让TextView自动适配绕排后的文本高度,我们需要借助NSLayoutManager来计算文本实际占据的排版高度,然后手动更新TextView的尺寸约束。
1. 编写计算文本高度的工具方法
可以给UITextView写一个扩展,方便复用计算逻辑:
extension UITextView { func calculateExclusionPathHeight() -> CGFloat { guard let layoutManager = layoutManager, let textContainer = textContainer else { return contentSize.height } // 确保当前的排除路径已生效 textContainer.exclusionPaths = self.textContainer.exclusionPaths // 获取文本对应的glyph范围 let glyphRange = layoutManager.glyphRange(for: textContainer) // 计算该范围对应的实际排版矩形 let textBoundingRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) // 加上contentInset的上下边距,得到最终需要的高度 return textBoundingRect.height + contentInset.top + contentInset.bottom } }
2. 动态更新TextView的高度约束
假设你给TextView添加了一个高度约束的IBOutlet(比如textViewHeightConstraint),在设置完排除路径后,调用上面的方法计算高度并更新约束:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let imageBezierPath = UIBezierPath(rect: imageRect) overviewTextView.textContainer.exclusionPaths = [imageBezierPath] // 计算绕排后的实际高度 let actualHeight = overviewTextView.calculateExclusionPathHeight() // 更新高度约束 textViewHeightConstraint.constant = actualHeight // 触发视图重新布局,让StackView自动调整下方元素位置 view.layoutIfNeeded() }
3. 适配StackView的布局
确保你的UIStackView设置正确:
axis设为.vertical(垂直方向)distribution设为.fill或者.fillProportionally,这样TextView高度变化时,StackView会自动重新排列子视图- 不要给TextView设置固定高度(除了我们动态更新的那个约束)
针对切换排除路径场景的补充
在你提到的Playground示例里,切换排除路径的按钮点击事件中,同样需要调用计算方法并更新高度约束:
@IBAction func toggleExclusionPath(_ sender: UIButton) { let imageBezierPath = UIBezierPath(rect: imageRect) overviewTextView.textContainer.exclusionPaths = overviewTextView.textContainer.exclusionPaths.isEmpty ? [imageBezierPath] : [] let actualHeight = overviewTextView.calculateExclusionPathHeight() textViewHeightConstraint.constant = actualHeight view.layoutIfNeeded() }
这样就能保证切换排除路径时,TextView会根据新的排版结果自动调整高度,文本也不会被截断了。
内容的提问来源于stack exchange,提问作者Dov
相关产品推荐
相关产品推荐

