如何计算swift中多行文本的最佳标签宽度

时间:2017-07-15 23:06:42

标签: ios swift string uilabel

我想创建一种方法来计算多行标签的最佳宽度,以便在固定高度的水平行中附加多个标签。

使用一行文字没有问题:

let textAttributes: [String : Any] = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.title2)]

let maximalWidth: CGFloat = text!.boundingRect(
        with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: height),
        options: [NSStringDrawingOptions.usesLineFragmentOrigin],
        attributes: textAttributes,
        context: nil).size.width

据我所知,这里没有选项,我有几行。当我们计算具有固定宽度的文本的高度时,此方法在其他方向上工作良好。但我有相反的目标。

作为一种变体,我可以根据最长的单词创建一个标签(更准确地说,基于最宽的单词,因为我们可以有几个具有相同字符数但具有不同渲染宽度的单词):

    var sizeToReturn = CGSize()

    let maxWordsCharacterCount = text?.maxWord.characters.count
    let allLongWords: [String] = text!.wordList.filter {$0.characters.count == maxWordsCharacterCount}
    var sizes: [CGFloat] = []
    allLongWords.forEach {sizes.append($0.size(attributes: attributes).width)}
    let minimalWidth = (sizes.max()! + constantElementsWidth)

我在这里使用了两个String扩展来创建单词列表并找到所有最长的:

extension String {
    var wordList: [String] {
    return Array(Set(components(separatedBy: .punctuationCharacters).joined(separator: "").components(separatedBy: " "))).filter {$0.characters.count > 0}
    }
}

extension String {
    var maxWord: String {
        if let max = self.wordList.max(by: {$1.characters.count > $0.characters.count}) {
        return max
    } else {return ""}
}

}

这不是一个糟糕的选择,但如果我们的文字不能用三行编写并且最后有几个短文和一个长字,那么它看起来很难看。确定宽度的这个长词将被截断。更多的是,它看起来不太好,有3个简短的单词,如:

嗯,我有最小宽度,我有最大宽度。也许,我可以 从最大值到最小值,并在标签开始被截断时捕获。 所以我觉得可以有一个优雅的解决方案,但我被卡住了。

1 个答案:

答案 0 :(得分:0)

万岁,我找到了可能的解决方案之一。您可以在操场上使用以下代码:

import UIKit
import PlaygroundSupport

//: Just a view to launch playground timeline preview
let hostView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
hostView.backgroundColor = .lightGray
PlaygroundPage.current.liveView = hostView

// MARK: - Extensions
extension String {
    var wordList: [String] {
        return Array(Set(components(separatedBy: .punctuationCharacters).joined(separator: "").components(separatedBy: " "))).filter {$0.characters.count > 0}
    }
}

extension String {
    var longestWord: String {
        if let max = self.wordList.max(by: {$1.characters.count > $0.characters.count}) {
            return max
        } else {return ""}
    }
}

// MARK: - Mathod

func createLabelWithOptimalLabelWidth (
                    requestedHeight: CGFloat,
              constantElementsWidth: CGFloat,
    acceptableWidthForTextOfOneLine: CGFloat, //When we don't want the text to be shrinked
                               text: String,
                         attributes: [String:Any]
    ) -> UILabel {

    let label = UILabel(frame: .zero)

    label.attributedText = NSAttributedString(string: text, attributes: attributes)

    let maximalLabelWidth = label.intrinsicContentSize.width

    if maximalLabelWidth < acceptableWidthForTextOfOneLine {

        label.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: maximalLabelWidth, height: requestedHeight))
        return label // We can go with this width
    }

    // Minimal width, calculated based on the longest word

    let maxWordsCharacterCount = label.text!.longestWord.characters.count
    let allLongWords: [String] = label.text!.wordList.filter {$0.characters.count == maxWordsCharacterCount}
    var sizes: [CGFloat] = []
    allLongWords.forEach {sizes.append($0.size(attributes: attributes).width)}
    let minimalWidth = (sizes.max()! + constantElementsWidth)


    // Height calculation
    var flexibleWidth = maximalLabelWidth
    var flexibleHeight = CGFloat()

    var optimalWidth = CGFloat()
    var optimalHeight = CGFloat()

    while (flexibleHeight <= requestedHeight && flexibleWidth >= minimalWidth) {

        optimalWidth = flexibleWidth
        optimalHeight = flexibleHeight

        flexibleWidth -= 1

        flexibleHeight = label.attributedText!.boundingRect(
        with: CGSize(width: flexibleWidth, height: CGFloat.greatestFiniteMagnitude),
        options: [NSStringDrawingOptions.usesLineFragmentOrigin],
        context: nil).size.height

        print("Width: \(flexibleWidth)")
        print("Height: \(flexibleHeight)")
        print("_______________________")
    }

    print("Final Width: \(optimalWidth)")
    print("Final Height: \(optimalHeight)")

    label.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: optimalWidth+constantElementsWidth, height: requestedHeight))

    return label
}

// MARK: - Inputs

let text: String? = "Determine the fair price"//nil//"Select the appropriate payment method"//"Finalize the order" //"Sell the car"//"Check the payment method"
let font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.callout)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.allowsDefaultTighteningForTruncation = true


let attributes: [String:Any] = [
    NSFontAttributeName: font,
    NSParagraphStyleAttributeName: paragraphStyle,
    NSBaselineOffsetAttributeName: 0
]

if text != nil {
    let label = createLabelWithOptimalLabelWidth(requestedHeight: 70, constantElementsWidth: 0, acceptableWidthForTextOfOneLine: 120, text: text!, attributes: attributes)

    label.frame.width
    label.frame.height

    label.backgroundColor = .white
    label.lineBreakMode = .byWordWrapping
    label.numberOfLines = 3

    hostView.addSubview(label)
}