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

Ios 如何计算swift中多行文字的最佳标签宽度,ios,swift,string,uilabel,Ios,Swift,String,Uilabel,我想创建一个方法来计算多行标签的最佳宽度,以便在固定高度的水平行中附加多个标签 只有一行文字没有问题: let textAttributes: [String : Any] = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.title2)] let maximalWidth: CGFloat = text!.boundingRect( with: CGSize(width: CG

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

只有一行文字没有问题:

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
据我所知,这里没有选项表明,我有几行。当我们用固定宽度计算文本的高度时,这种方法在另一个方向上效果很好。但我有相反的目标

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

我在这里使用了两个字符串扩展名来创建单词列表并查找所有最长的单词:

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个简短的单词,如:

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

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

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)
}
导入UIKit
导入PlaygroundSupport
//:仅用于启动游乐场时间线预览的视图
让hostView=UIView(帧:CGRect(x:0,y:0,宽度:320,高度:480))
hostView.backgroundColor=.lightGray
PlaygroundPage.current.liveView=hostView
//马克:-分机
扩展字符串{
var wordList:[字符串]{
返回数组(集合(组件(分隔符:。标点符号)。联接(分隔符:)。组件(分隔符:))))。筛选器{$0.characters.count>0}
}
}
扩展字符串{
var longestWord:String{
如果让max=self.wordList.max(by:{$1.characters.count>$0.characters.count}){
返回最大值
}else{return”“}
}
}
//马克:马修
func createLabelWithOptimalLabelWidth(
请求高度:CGFloat,
constantElementsWidth:CGFloat,
AcceptableWidthForExtofoneline:CGFloat,//当我们不希望文本收缩时
文本:字符串,
属性:[字符串:任意]
)->UILabel{
let label=UILabel(帧:.0)
label.attributedText=NSAttributedString(字符串:文本,属性:属性)
设maximalLabelWidth=label.intrinsicContentSize.width
如果最大带宽<可接受的Extofoneline宽度{
label.frame=CGRect(原点:CGPoint.zero,大小:CGSize(宽度:maximalLabelWidth,高度:requestedHeight))
返回标签//我们可以使用此宽度
}
//最小宽度,根据最长单词计算
让maxWordsCharacterCount=label.text!.longestWord.characters.count
让allLongWords:[String]=label.text!.wordList.filter{$0.characters.count==maxWordsCharacterCount}
变量大小:[CGFloat]=[]
allLongWords.forEach{size.append($0.size(attributes:attributes.width)}
设最小宽度=(size.max()!+constantElementsWidth)
//高度计算
var flexibleWidth=最大带宽
var flexibleHeight=CGFloat()
var optimizewidth=CGFloat()
var optimizeheight=CGFloat()
同时(灵活高度=最小宽度){
最佳宽度=可弯曲宽度
最佳高度=灵活高度
柔性宽度-=1
flexibleHeight=label.AttributeText!.boundingRect(
带:CGSize(宽度:flexibleWidth,高度:CGFloat.greatestfinitemagnity),
选项:[NSStringDrawingOptions.usesLineFragmentOrigin],
上下文:nil).size.height
打印(“宽度:\(flexibleWidth)”)
打印(“高度:\(灵活高度)”)
打印(“\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
}
打印(“最终宽度:\(最佳宽度)”)
打印(“最终高度:\(最佳高度)”)
label.frame=CGRect(原点:CGPoint.zero,大小:CGSize(宽度:optimalWidth+constantElementsWidth,高度:requestedHeight))
退货标签
}
//标记:-输入
让文本:字符串?=“确定公平价格”//nil//“选择适当的付款方式”//“完成订单”//“出售汽车”//“检查付款方式”
让font=UIFont.preferredFont(forTextStyle:uifontextstyle.callout)
设paragraphStyle=NSMutableParagraphStyle()
paragraphStyle.lineBreakMode=.byWordWrapping
paragraphStyle.allowsDefaultTighteningForTruncation=true
let属性:[字符串:任意]=[
NSFontAttributeName:font,
NSParagraphStyleAttributeName:段落样式,
NSBaselineOffsetAttribute名称:0
]
如果文本!=零{
让label=createLabelWithOptimalLabelWidth(请求的高度:70,常量元素宽度:0,可接受的外部轮廓线宽度:120,文本:text!,属性:attributes)
标签、框架、宽度
标签、框架、高度
label.backgroundColor=.white
label.lineBreakMode=.byWordWrapping
label.numberOfLines=3
hostView.addSubview(标签)
}

您应该使用自动布局约束,根据标签所包含的内容根据需要调整标签的大小。在故事板中很容易做到这一点,但如果需要,也可以通过编程实现。我会做一些关于自动布局的研究:我不明白自动布局在这种情况下有什么帮助。我的意思是,如果我知道宽度,更改标签大小不会有问题。但是我不能计算这个宽度。
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)
}