【问题标题】:Figure out size of UILabel based on String in Swift在 Swift 中根据 String 计算 UILabel 的大小
【发布时间】:2015-08-07 15:33:41
【问题描述】:

我正在尝试根据不同的字符串长度计算 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 中的答案。

【问题讨论】:

标签: ios iphone string swift3 uilabel


【解决方案1】:

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)
    }
}

斯威夫特 4 & 5

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

来自

[NSFontAttributeName: font]

[.font : font]

【讨论】:

  • @CodyWeaver 检查 widthWithConstrainedHeight 方法的编辑。
  • @KaanDedeoglu 这将如何与动态高度字符串一起使用,例如当您使用“numberOfLines”= 0(可能是 UILabel 特定的,不确定)或 lineBreakMode ByWordWrapping 时。我的猜测是将其添加到 [NSFontAttributeName: font, NSLineBreakMode: .ByWordWrapping] 这样的属性中,但它不起作用
  • 我想我已经找到答案了。我需要使用NSParagraphStyleAttributeName : style,其中样式为 NSMutableParagraphStyle
  • 我需要写 'self.boundingRect' 而不是 'boundingRect' 否则会出现编译错误。
  • 我在sizeForItemAtIndexPathUICollectionView 中使用此答案时发现,它似乎覆盖了insetForSectionAt 的返回值
【解决方案2】:

对于多行文本,此答案无法正常工作。 您可以使用 UILabel 构建不同的字符串扩展

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 获得固定宽度,.numberOfLines 设置为 0。通过添加文本并调用 .sizeToFit(),它会自动调整到正确的高度。

代码是用 Swift 3 编写的??

【讨论】:

  • sizeToFit 但是由于绘图的多次传递而引入了一百万个性能问题。手动计算大小在资源上要便宜得多
  • 这个方案应该设置UILabel的UIFont来保证高度正确。
  • 非常适合我 - 甚至可以考虑空字符串(与接受的答案不同)。使用自动高度单元格计算 tableView 的高度非常方便!
  • 我发现接受的答案适用于固定宽度,但不适用于固定高度。对于固定高度,它只会增加宽度以适应一行中的所有内容,除非文本中有换行符。这是我的替代答案:My Answer
  • 我发布了一个类似的解决方案——无需致电sizeToFit
【解决方案3】:
@IBOutlet weak var constraintTxtV: NSLayoutConstraint!
func TextViewDynamicallyIncreaseSize() {
    let contentSize = self.txtVDetails.sizeThatFits(self.txtVDetails.bounds.size)
    let higntcons = contentSize.height
    constraintTxtV.constant = higntcons
}

【讨论】:

  • 你的答案不仅应该包含代码,还应该包含关于代码的解释。详情请见How to Answer
  • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
  • 这个答案不完整。它指的是类型未知的重要变量,这违背了目的
【解决方案4】:
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)
    }

}

【讨论】:

    【解决方案5】:

    这是我在 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
    

    【讨论】:

      【解决方案6】:

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

      width函数多次调用height函数,但计算速度很快,在UITable的行中使用该函数我没有注意到性能问题。

      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)
          }
      }
      

      【讨论】:

      • 什么是minimumTextWrapWidth:CGFloat
      • 它只是函数中计算的种子值。如果您希望宽度较大,则选择较小的 minimumTextWrapWidth 将导致 while 循环进行额外的迭代。所以最小宽度越大越好,但如果大于实际需要的宽度,则始终是返回的宽度。
      【解决方案7】:

      检查标签文本高度,它正在处理它

      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)
      
                  }
      

      【讨论】:

        【解决方案8】:

        这是一个对我有用的简单解决方案...类似于发布的其他一些解决方案,但它不包括调用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
        

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

        【讨论】:

        • 很好的答案!谢谢
        【解决方案9】:

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

            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 
        

        【讨论】:

          【解决方案10】:

          斯威夫特 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 text\nIs\nAwesome"
          let labelSize = label.getSize(constrainedWidth:200.0)
          

          为我工作

          【讨论】:

            【解决方案11】:

            在 Swift 5 中:

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

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

            【讨论】:

              【解决方案12】:

              我无法让@KaanDedeoglu 的解决方案在 Swift 5 中为 multiline 标签和文本视图工作 - 无论出于何种原因 - 所以我最终编写了一个保持不变的“手动”解决方案@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
                  }
              }
              

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

              【讨论】:

              • 不错的解决方案,但不考虑 \n 强制换行
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-10-08
              • 1970-01-01
              • 2018-09-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多