【问题标题】:Find out how many characters in a string can fit in one row找出一个字符串中有多少个字符可以放在一行中
【发布时间】:2017-09-20 16:30:18
【问题描述】:

我必须根据每行可以容纳多少个字符将字符串拆分为一个数组。数组中的每个对象只需一行文本。我能够计算字符串中的行数,但我不知道如何找出一行中的最大字符数。

func lineCount(forText text: String) -> Int {
        let font = UIFont.systemFont(ofSize: 24.0)
        let width: Int = Int(self.tableView.frame.size.width)
        let rect: CGRect = text.boundingRect(with: CGSize(width: CGFloat(width), height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
        return Int(ceil(rect.size.height / font.lineHeight))
    }

【问题讨论】:

  • 只是好奇。根据您将使用的字符类型,这不会有所不同吗?说 10 i 的宽度小于 10 A 的宽度... iiiiiiiiii : AAAAAAAAAA
  • 是的。我想知道一行中有多少个特定字符串的字符。本质上,我必须根据每行可以获得的最大值将字符串拆分为一个数组。因此,如果第一个喜欢是 AAAAA ......那么它会不太适合然后 iiii。我需要能够计算出来
  • 我相信您想拆分 String 以便它适合某个渲染长度,对吗?您可能想在问题中澄清这一点。
  • 我将每一行放入一个 tableviewcell 中,因此需要应用标准的自动换行规则。本质上,我是从一个适合其内容高度的表格单元格开始,将内容拆分为仅包含一行文本的单个表格单元格。字体不能缩小,所以我需要将其拆分以适应

标签: ios arrays swift split lines


【解决方案1】:

Swift 5 更新对我有用的答案。

  1. 已添加lineBreakMode
extension String {    
    func splittingLinesThatFitIn(width: CGFloat, font: UIFont) -> [String] {
        
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineBreakMode = .byWordWrapping
        
        // set up styled text for the container
        let storage = NSTextStorage(string: self, attributes: [
            NSAttributedString.Key.font: font,
            NSAttributedString.Key.paragraphStyle: paragraphStyle
        ])
        
        // add a layout manage for the storage
        let layout = NSLayoutManager()
        storage.addLayoutManager(layout)
        
        // Set up the size of the container
        // width is what we care about, height is maximum
        let container = NSTextContainer(size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
        
        // add the container to the layout
        layout.addTextContainer(container)
        
        var lines = [String]()
        
        // generate the layout and add each line to the array
        layout.enumerateLineFragments(forGlyphRange: NSMakeRange(0, storage.length)) { _, _, _, range, _ in
            lines.append(storage.attributedSubstring(from: range).string)
        }
        
        debugPrint(lines)
        
        return lines
    }
}

【讨论】:

    【解决方案2】:

    由于某些字体具有可变宽度的字符,因此您无法在每种字体中获得固定数量的字符。您可以做的是尝试将字符串分成行大小的块:

    extension String {
      func split(width: CGFloat, font: UIFont) -> [String] {
        guard !self.isEmpty else { return [String]() }
    
        var lines = [String]()
    
        // set up range of the split
        var splitStart = self.startIndex
        var splitEnd = self.startIndex
    
        repeat {
          // advance the end range for the split
          splitEnd = self.index(after: splitStart)
    
          // initial split to test
          var line = String(characters[splitStart..<splitEnd])
    
          // while we're before the end test the rendered width
          while splitEnd < self.endIndex &&
            line.size(attributes: [NSFontAttributeName: font]).width < width {
              // add one more character
              splitEnd = self.index(after: splitEnd)
              line = String(characters[splitStart..<splitEnd])
          }
    
          // add split to array and set up next split
          lines.append(line)
          splitStart = splitEnd
        } while splitEnd < self.endIndex // don't go past the end of the string
    
    
        // add remainder of string to array
        lines.append(String(characters[splitStart..<self.endIndex]))
        return lines
      }
    }
    

    这可以通过预先计算整个字符串的宽度,除以行数,从每行的平均宽度开始,然后尝试更多或更少的字符,直到它适合。但是,这确实使代码更加复杂。

    如果您想确保单词不会被拆分,那么您可以保存每个单词的开头位置,当您到达行尾时,将拆分结束放在单词之前,将单词带到下一次分裂。当然,您还需要考虑太长而无法拆分的单词、连字词等等。

    另一种方法是使用NSLayoutManager 和NSTextContainer,因为您需要更高级的布局:

    let text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Non est ista, inquam, Piso, magna dissensio. Minime vero istorum quidem, inquit. Graecum enim hunc versum nostis omnes-: Suavis laborum est praeteritorum memoria. Negat enim summo bono afferre incrementum diem. Quasi ego id curem, quid ille aiat aut neget. Semper enim ex eo, quod maximas partes continet latissimeque funditur, tota res appellatur. Duo Reges: constructio interrete."
    
    let font = UIFont.systemFont(ofSize: 24.0)
    
    // set up styled text for the container
    let storage = NSTextStorage(string: text, attributes: [NSFontAttributeName: font])
    
    // add a layout manage for the storage
    let layout = NSLayoutManager()
    storage.addLayoutManager(layout)
    
    // Set up the size of the container
    // width is what we care about, height is maximum
    let width:CGFloat = 500
    let container = NSTextContainer(size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
    
    // add the container to the layout
    layout.addTextContainer(container)
    
    var lines = [String]()
    
    // generate the layout and add each line to the array
    layout.enumerateLineFragments(forGlyphRange: NSMakeRange(0, storage.length)) {
      lines.append(storage.attributedSubstring(from: $0.3).string)
    }
    
    lines.forEach { print($0) }
    

    结果:

    Lorem ipsum dolor sit amet, consectetur 
    adipiscing elit. Non est ista, inquam, Piso, 
    magna dissensio. Minime vero istorum 
    quidem, inquit. Graecum enim hunc versum 
    nostis omnes-: Suavis laborum est 
    praeteritorum memoria. Negat enim summo 
    bono afferre incrementum diem. Quasi ego id 
    curem, quid ille aiat aut neget. Semper enim 
    ex eo, quod maximas partes continet 
    latissimeque funditur, tota res appellatur. Duo 
    Reges: constructio interrete.
    

    如果您愿意,您还可以通过NSLayoutManager 提供断字和其他行为。

    【讨论】:

    • 这不考虑将单词放到下一行,而不是拆分单词
    • @user1079052 当然不是,这只是一个例子。必须添加这些行为。您的问题没有说明您需要它们。
    • 这是我的错误,因为我没有说明我需要应用自动换行规则。
    • 这仍然有效。我将添加一个更新的答案以使用 swift5
    【解决方案3】:

    我不确定这是否是最有效的方法,但我可以使用它:

    func getLinesArrayOfString(forText text: String) ->NSArray {
            let font = UIFont.systemFont(ofSize: 24.0)
            let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
            label.numberOfLines = 0
            label.text = text as String
            label.font = font
            label.sizeToFit()
            var linesArray: [Any] = []
    
    
            let rect: CGRect = label.frame
    
    
            let attStr = NSMutableAttributedString(string: text)
            attStr.addAttribute((NSAttributedStringKey(rawValue: kCTFontAttributeName as String)), value: font, range: NSRange(location: 0, length: attStr.length))
            let frameSetter: CTFramesetter = CTFramesetterCreateWithAttributedString(attStr)
            let path: CGMutablePath = CGMutablePath()
            path.addRect(CGRect(x: 0, y: 0, width: rect.size.width, height: 100000), transform: .identity)
            let frame: CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
            let lines = CTFrameGetLines(frame) as? [Any]
    
            for line: Any in lines! {
                let lineRef = line
                let lineRange: CFRange = CTLineGetStringRange(lineRef as! CTLine)
                let range = NSRange(location: lineRange.location, length: lineRange.length)
                let lineString: String = (text as NSString).substring(with: range)
    
    
    
                CFAttributedStringSetAttribute(attStr, lineRange, kCTKernAttributeName, font)
                CFAttributedStringSetAttribute(attStr, lineRange, kCTKernAttributeName, font)
                linesArray.append(lineString)
            }
            return linesArray as NSArray
        }
    

    【讨论】:

    • 这里有很多不必要的代码,这是一种非常迂回的方法。如果您查看我回答问题的方式,您会发现一种更直接的处理方式。
    猜你喜欢
    • 2021-10-28
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多