调整 UITextView 以适应多行 NSAttributedString

问题描述

我有一个包含 override fun onActivityResult(requestCode: Int,resultCode: Int,data: Intent?) { super.onActivityResult(requestCode,resultCode,data) if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { val bitmap=BitmapFactory.decodeFile(currentPhotoPath) imageView?.setimageBitmap(bitmap) } } UITextView。我想调整文本视图的大小,以便在给定宽度的情况下显示整个字符串而无需滚动。

NSAttributedString一个方法可以计算给定大小的边界矩形

NSAttributedString

但不幸的是它似乎不起作用,因为它总是返回单行的高度。

解决方法

多次尝试后,我发现我设置的 NSAttributedStringbyTruncatingTail 作为 lineBreakModeNSParagraphStyle 值(这是我们在应用程序中使用的默认值).

要实现所需的行为,我必须将其更改为 byWordWrappingbyCharWrapping

let paragraphStyle = NSMutableParagraphStyle()
// When setting "byTruncatingTail" it returns a single line height
// paragraphStyle.lineBreakMode = .byTruncatingTail
paragraphStyle.lineBreakMode = .byWordWrapping
let stringAttributes: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Avenir-Book",size: 16.0)!,.paragraphStyle: paragraphStyle]
let attributedString = NSAttributedString(string: string,attributes: stringAttributes)
 
let computedSize = attributedString.boundingRect(with: CGSize(width: 200,height: CGFloat.greatestFiniteMagnitude),options: .usesLineFragmentOrigin,context: nil)
computedSize.height

请注意,在 byTruncatingTail 上设置具有 UILabel 值的属性字符串(其中 numberOfLines 值为 0)时,该字符串会“自动”调整为多行大小,这不会t 在计算 boundingRect 时发生。

在计算用于 NSAttributedString 内部的 UITextView 高度时,还有其他因素需要牢记(这些因素中的每一个都可能导致字符串未完全包含在文本视图中):>

1.边界改变时重新计算高度

因为高度是基于边界的,所以当边界改变时应该重新计算。这可以通过在 bounds 键路径上使用 KVO 来实现,在此更改时布局无效。

observe(\.bounds) { (_,_) in
    invalidateIntrinsicContentSize()
    layoutIfNeeded()
}

就我而言,我使自定义 intrinsicContentSizeUITextView 无效,因为这是我根据计算出的字符串高度调整它的大小的方式。

2.使用 NSTextContainer 宽度

使用 textContainer.width(而不是 bounds.width)作为用于 boundingRect 方法调用的固定宽度,因为它保留了任何 textContainerInset 值(尽管 {{1 }} 和 left 默认值为 0)

3.将垂直 right 值添加到字符串高度

计算 textContainerInsets 高度后,我们应该添加 NSAttributedStringtextContainerInsets.top 以计算正确的 textContainerInsets.bottom 高度(它们的默认值为 8.0...)

UITextField

4.移除 lineFragmentPadding

将 0 设置为 override var intrinsicContentSize: CGSize { let computedHeight = attributedText.boundingHeight(forFixedWidth: textContainer.size.width) return CGSize(width: bounds.width,height: computedHeight + textContainerInset.top + textContainerInset.bottom) } 的值,或者,如果您想要它,请记住在计算 lineFragmentPadding 高度之前将其值从“固定宽度”中删除

NSAttributedString

5.将 textView.textContainer.lineFragmentPadding = 0 应用于计算高度

ceil 返回的高度值可以是小数,如果我们按原样使用它可能会导致最后一行不显示。将其传递给 boundingRect 函数以获取上整数值,以避免向下舍入。

,

一种可能的方法是将 UITextView 子类化以在其 contentSize 确实发生变化时通知您(~文本的大小)。

class MyExpandableTextView: UITextView {

    var onDidChangeContentSize: ((CGSize) -> Void)?
    override var contentSize: CGSize {
        didSet {
            onDidChangeContentSize?(contentSize)
        }
    }
}

在 “父查看”:

@IBOulet var expandableTextView: MyExpandableTextView! //Do not forget to set the class in the Xib/Storyboard
// or
var expandableTextView = MyExpandableTextView()

并应用效果:

expandableTextView. onDidChangeContentSize = { [weak self] newSize in 
   // if you have a NSLayoutConstraint on the height:
   // self?.myExpandableTextViewHeightConstraint.constant = newSize.height
   // else if you play with "frames"
   // self?.expandableTextView.frame.height = newSize.height
}