找出字符串中有多少个字符可以放在一行中

时间:2017-09-20 16:30:19

标签: ios arrays swift split lines

我必须根据每行可以容纳的字符数将字符串拆分为数组。数组中的每个对象只需要一行文本。我能够计算字符串中的行数,但我无法弄清楚如何在一行中找出最大字符数。

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

3 个答案:

答案 0 :(得分:0)

由于某些字体的字符宽度可变,因此无法使用每种字体连续获取一定数量的字符。你可以做的是尝试将字符串分解为行大小的块:

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

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

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

另一种方法,因为您需要更高级的布局,可以使用NSLayoutManagerNSTextContainer

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提供连字符和其他行为。

答案 1 :(得分:0)

迅速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 :(得分:-2)

我不确定这是否是最有效的方式,但我得到了它的工作:

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
    }