0

我有一个UITextView包含一个NSAttributedString. 我想调整文本视图的大小,以便在给定固定宽度的情况下显示整个字符串而不滚动。

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

let computedSize = attributedString.boundingRect(with: CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude),
                                                   options: .usesLineFragmentOrigin,
                                                   context: nil)

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

4

2 回答 2

0

经过几次尝试,我发现NSAttributedString我设置byTruncatingTaillineBreakMode 值是 for NSParagraphStyle(这是我们在应用程序中使用的默认值)。

为了实现所需的行为,我必须将其更改为byWordWrapping or byCharWrapping

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

请注意,当使用byTruncatingTaila 上的值UILabel(其中 numberOfLines值为 0)设置属性字符串时,该字符串“自动”调整为多行,这在计算boundingRect.

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

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

由于高度是基于边界的,所以当边界改变时应该重新计算。这可以在boundskeypath 上使用 KVO 来实现,当这种变化时布局无效。

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

在我的情况下intrinsicContentSize,我的自定义无效,UITextView因为这是我根据计算的字符串高度调整它的方式。

2.使用NSTextContainer宽度

使用textContainer.width(而不是bounds.width)作为用于方法调用的固定宽度,boundingRect因为它会考虑任何textContainerInset值(尽管默认值为 0)leftright

textContainerInsets3.为字符串高度添加垂直值

在计算NSAttributedString高度之后,我们应该添加textContainerInsets.toptextContainerInsets.bottom计算正确的UITextField高度(它们的默认值是 8.0...)

override var intrinsicContentSize: CGSize {
    let computedHeight = attributedText.boundingHeight(forFixedWidth: textContainer.size.width)
    return CGSize(width: bounds.width,
                          height: computedHeight + textContainerInset.top + textContainerInset.bottom)
}

4.删除lineFragmentPadding

将 0 设置为的值,lineFragmentPadding或者,如果您想拥有它,请记住在计算NSAttributedString高度之前将其值从“固定宽度”中删除

textView.textContainer.lineFragmentPadding = 0

5. 应用于ceil计算高度

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

于 2021-04-07T14:00:00.983 回答
0

一种可能的方法是子类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
}
于 2021-04-09T08:28:18.967 回答