在Swift中基于String计算UILabel的大小



我试图根据不同的字符串长度计算UILabel的高度。

func calculateContentHeight() -> CGFloat{
    var maxLabelSize: CGSize = CGSizeMake(frame.size.width - 48, CGFloat(9999))
    var contentNSString = contentText as NSString
    var expectedLabelSize = contentNSString.boundingRectWithSize(maxLabelSize, options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont.systemFontOfSize(16.0)], context: nil)
    print("(expectedLabelSize)")
    return expectedLabelSize.size.height
}

上面是我用来确定高度的当前函数,但它不起作用。我将非常感激我能得到的任何帮助。我更喜欢Swift的答案,而不是Objective c。

使用String的扩展

迅速

3

extension String {
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
    
        return ceil(boundingBox.height)
    }
    func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
        return ceil(boundingBox.width)
    }
}

NSAttributedString(有时非常有用)

extension NSAttributedString {
    func height(withConstrainedWidth width: CGFloat) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
    
        return ceil(boundingBox.height)
    }
    func width(withConstrainedHeight height: CGFloat) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
    
        return ceil(boundingBox.width)
    }
}

Swift 4 &5

只需更改extension String方法中attributes的值

[NSFontAttributeName: font]

[.font : font]

对于多行文本,此答案不能正常工作。您可以使用UILabel

构建不同的String扩展
extension String {
func height(constraintedWidth width: CGFloat, font: UIFont) -> CGFloat {
    let label =  UILabel(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))
    label.numberOfLines = 0
    label.text = self
    label.font = font
    label.sizeToFit()
    return label.frame.height
 }
}

UILabel获得固定宽度,. numberolines设置为0。通过添加文本并调用. sizetofit(),它会自动调整到正确的高度。

代码用Swift 3编写🔶🐦

这是一个对我有用的简单解决方案…类似于其他一些帖子,但它不需要sizeToFit

注意这是用Swift 5写的

let lbl = UILabel()
lbl.numberOfLines = 0
lbl.font = UIFont.systemFont(ofSize: 12) // make sure you set this correctly 
lbl.text = "My text that may or may not wrap lines..."
let width = 100.0 // the width of the view you are constraint to, keep in mind any applied margins here
 
let height = lbl.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel).height

处理换行等。不是最优雅的代码,但它完成了工作。

这是我在Swift 4.1和Xcode 9.4.1的答案

//This is your label
let proNameLbl = UILabel(frame: CGRect(x: 0, y: 20, width: 300, height: height))
proNameLbl.text = "This is your text"
proNameLbl.font = UIFont.systemFont(ofSize: 17)
proNameLbl.numberOfLines = 0
proNameLbl.lineBreakMode = .byWordWrapping
infoView.addSubview(proNameLbl)
//Function to calculate height for label based on text
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat {
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
    label.numberOfLines = 0
    label.lineBreakMode = NSLineBreakMode.byWordWrapping
    label.font = font
    label.text = text
    label.sizeToFit()
    return label.frame.height
}

现在调用这个函数

//Call this function
let height = heightForView(text: "This is your text", font: UIFont.systemFont(ofSize: 17), width: 300)
print(height)//Output : 41.0

Swift 5:

如果你有UILabel和某种方式boundingRect不适合你(我面临这个问题。它总是返回1行高),有一个扩展可以很容易地计算标签大小。

extension UILabel {
    func getSize(constrainedWidth: CGFloat) -> CGSize {
        return systemLayoutSizeFitting(CGSize(width: constrainedWidth, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    }
}

你可以这样使用:

let label = UILabel()
label.text = "My textnIsnAwesome"
let labelSize = label.getSize(constrainedWidth:200.0)

Works for me

extension String{
    func widthWithConstrainedHeight(_ height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
        return ceil(boundingBox.width)
    }
    func heightWithConstrainedWidth(_ width: CGFloat, font: UIFont) -> CGFloat? {
        let constraintRect = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
        return ceil(boundingBox.height)
    }
}

我发现接受的答案适用于固定宽度,但不适用于固定高度。对于固定的高度,它只会增加宽度以适应一行中的所有内容,除非文本中有换行符。

width函数多次调用height函数,但这是一个快速的计算,我没有注意到在utable的行中使用该函数的性能问题。

extension String {
    public func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font : font], context: nil)
        return ceil(boundingBox.height)
    }
    public func width(withConstrainedHeight height: CGFloat, font: UIFont, minimumTextWrapWidth:CGFloat) -> CGFloat {
        var textWidth:CGFloat = minimumTextWrapWidth
        let incrementWidth:CGFloat = minimumTextWrapWidth * 0.1
        var textHeight:CGFloat = self.height(withConstrainedWidth: textWidth, font: font)
        //Increase width by 10% of minimumTextWrapWidth until minimum width found that makes the text fit within the specified height
        while textHeight > height {
            textWidth += incrementWidth
            textHeight = self.height(withConstrainedWidth: textWidth, font: font)
        }
        return ceil(textWidth)
    }
}

在Swift 5:

label.textRect(forBounds: label.bounds, limitedToNumberOfLines: 1)

顺便说一句,limitedToNumberOfLines的值取决于你想要的标签文本行。

@IBOutlet weak var constraintTxtV: NSLayoutConstraint!
func TextViewDynamicallyIncreaseSize() {
    let contentSize = self.txtVDetails.sizeThatFits(self.txtVDetails.bounds.size)
    let higntcons = contentSize.height
    constraintTxtV.constant = higntcons
}

检查标签文本高度,它正在工作

let labelTextSize = ((labelDescription.text)! as NSString).boundingRect(
                with: CGSize(width: labelDescription.frame.width, height: .greatestFiniteMagnitude),
                options: .usesLineFragmentOrigin,
                attributes: [.font: labelDescription.font],
                context: nil).size
            if labelTextSize.height > labelDescription.bounds.height {
                viewMoreOrLess.hide(byHeight: false)
                viewLess.hide(byHeight: false)
            }
            else {
                viewMoreOrLess.hide(byHeight: true)
                viewLess.hide(byHeight: true)
            }

这个解决方案将有助于在运行时计算高度和宽度。

    let messageText = "Your Text String"
    let size = CGSize.init(width: 250, height: 1000)
    let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
    let estimateFrame = NSString(string: messageText).boundingRect(with:  size, options: options, attributes: [NSAttributedString.Key.font: UIFont(name: "HelveticaNeue", size: 17)!], context: nil)

这里你可以计算出你的字符串的估计高度,并把它传递给UILabel帧。

estimateFrame.Width
estimateFrame.Height 

我无法让@KaanDedeoglu的解决方案在Swift 5中为多行标签和文本视图工作-无论出于什么原因-所以我只是写了一个"手工"解决方案,保持与@KaanDedeoglu的答案相同的函数签名,对于那些感兴趣的人来说。

宽度
extension String {
    func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
        
        var wordBoxes = [CGSize]()
        var calculatedHeight = CGFloat.zero
        var calculatedWidth = CGFloat.zero
        
        for word in self.wordsWithWordSeparators() {
            
            let box = word.boundingRect(with: CGSize.zero, attributes: [.font: font], context: nil)
            let boxSize = CGSize(width: box.width, height: box.height)
            wordBoxes.append(boxSize)
            calculatedHeight += boxSize.height
            calculatedWidth = calculatedWidth < boxSize.width ? boxSize.width : calculatedWidth
        }
        
        while calculatedHeight > height && wordBoxes.count > 1 {
            
            var bestLineToRelocate = wordBoxes.count - 1
            
            for i in 1..<wordBoxes.count {
                
                let bestPotentialWidth = wordBoxes[bestLineToRelocate - 1].width + wordBoxes[bestLineToRelocate].width
                let thisPotentialWidth = wordBoxes[i - 1].width + wordBoxes[i].width
                
                if bestPotentialWidth > thisPotentialWidth {
                    bestLineToRelocate = i
                }
            }
            
            calculatedHeight -= wordBoxes[bestLineToRelocate].height
            wordBoxes[bestLineToRelocate - 1].width += wordBoxes[bestLineToRelocate].width
            wordBoxes.remove(at: bestLineToRelocate)
            calculatedWidth = max(wordBoxes[bestLineToRelocate - 1].width, calculatedWidth)
        }
        
        return ceil(calculatedWidth)
    }
}

extension String {
    
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        
        var wordBoxes = [CGSize]()
        var calculatedHeight = CGFloat.zero
        var currentLine = 0
        
        for word in self.wordsWithWordSeparators() {
            
            let box = word.boundingRect(with: CGSize.zero, attributes: [.font: font], context: nil)
            let boxSize = CGSize(width: box.width, height: box.height)
            
            if wordBoxes.isEmpty == true {
                wordBoxes.append(boxSize)
            }
            else if wordBoxes[currentLine].width + boxSize.width > width {
                wordBoxes.append(boxSize)
                currentLine += 1
            }
            else {
                wordBoxes[currentLine].width += boxSize.width
                wordBoxes[currentLine].height = max(wordBoxes[currentLine].height, boxSize.height)
            }
        }
        
        for wordBox in wordBoxes {
            calculatedHeight += wordBox.height
        }
        
        return calculatedHeight
    }
}

使用的帮助器方法

extension String {
    
    // Should work with any language supported by Apple
    func wordsWithWordSeparators () -> [String] {
        
        let range = self.startIndex..<self.endIndex
        var words = [String]()
        
        self.enumerateSubstrings(in: range, options: .byWords) { (substr, substrRange, enclosingRange, stop) in
            let wordWithWordSeparators = String(self[enclosingRange])
            words.append(wordWithWordSeparators)
        }
        
        return words
    }
}

注意:这些高度和宽度计算假设给定的标签或文本视图在执行换行时不会拆分或连字符。如果您的情况不是这样,那么您应该只需要将单词替换为字符。此外,如果您处于对运行时敏感的环境中,可能需要考虑限制这些函数调用或缓存结果,因为根据字符串包含的单词数量,它们可能会有点昂贵。

我创建了以下扩展来根据宽度计算字符串的高度。

   extension String {
    func heightWithConstrainedWidth(width: CGFloat, font: UIFont) -> CGFloat {
        let textView = UITextView()
        textView.text = self
        textView.font = font
        var newFrame = textView.frame
        newFrame.size = CGSize(width: width, height: .greatestFiniteMagnitude)
        textView.frame = newFrame
        return textView.contentSize.height
    }
extension String {
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
        
        return ceil(boundingBox.height)
    }
    
    func width(withConstraintedHeight height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
        
        return ceil(boundingBox.width)
    }
}

最新更新