0

I have been grappling with this since a while. There are APIs that give us bounds size for given attributes of NSAttributedString.

But there is no direct way to get string range that would fit within given bounds.

My requirement is to fit very long string across a few paged views (PDF is not an option, neither is scrolling). Hence I have to figure out string size for each view (same bounds).

Upon research I found that CTFramesetterSuggestFrameSizeWithConstraints and its friends in Core Text maybe of help. I tried the approach described here, but the resulting ranges have one ugly problem:

It ignores word breaks (a different problem unrelated to Core Text, but I would really like to see if there was some solution to that as well).

Basically I want paging of text across number of UITextView objects, but not getting the right attributed string splits.

NOTE: My NSAttributedString attributes are as follows:

let attributes: [NSAttributedString.Key : Any] = [.foregroundColor : textColor, .font : font, .paragraphStyle : titleParagraphStyle]

(titleParagraphStyle has lineBreakMode set to byWordWrapping)

extension UITextView
{
    func getStringSplits (fullString: String, attributes: [NSAttributedString.Key:Any]) -> [String]
    {
        let attributeString = NSAttributedString(string: fullString, attributes: attributes)
        let frameSetterRef = CTFramesetterCreateWithAttributedString(attributeString as CFAttributedString)

        var initFitRange:CFRange = CFRangeMake(0, 0)
        var finalRange:CFRange = CFRangeMake(0, fullString.count)

        var ranges: [Int] = []
        repeat
        {   
          CTFramesetterSuggestFrameSizeWithConstraints(frameSetterRef, initFitRange, attributes as CFDictionary, CGSize(width: bounds.size.width, height: bounds.size.height), &finalRange)
            initFitRange.location += finalRange.length
            ranges.append(finalRange.length)
        }
        while (finalRange.location < attributeString.string.count)

        var stringSplits: [String] = []

        var startIndex: String.Index = fullString.startIndex

        for n in ranges
        {
            let endIndex = fullString.index(startIndex, offsetBy: n, limitedBy: fullString.endIndex) ?? fullString.endIndex
            let theSubString = fullString[startIndex..<endIndex]
            stringSplits.append(String(theSubString))
            startIndex = endIndex
        }

        return stringSplits
    }
}
Nirav Bhatt
  • 6,940
  • 5
  • 45
  • 89

1 Answers1

1

My requirement is to fit very long string across a few paged views (PDF is not an option, neither is scrolling). Hence I have to figure out string size for each view (same bounds).

No, you don't have to figure that out. The text kit stack will do it for you.

In fact, UITextView will flow long text from one text view to another automatically. It's just a matter of configuring the text kit stack — one layout manager with multiple text containers.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • +1 thank you. Have you known any example links for it? Your answer seems to suggest my approach has been some overkill. What am I missing? – Nirav Bhatt Jul 08 '19 at 08:02
  • 1
    https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch10p539textKitStacks/ch23p813textKitShapes/ViewController.swift Case 1 – matt Jul 08 '19 at 11:19
  • Thanks for the link. My problem would actually involve arbitrary number of textviews, depending upon string length. Is there a way I could create as many as I want, based on text range, sharing same text storage as you describe in your link? – Nirav Bhatt Jul 08 '19 at 11:26
  • I don’t know what would prevent it. It sounds like you’re writing a book reading app, like Apple Books or Marvin. Surely this is a well solved problem – matt Jul 08 '19 at 11:34
  • Your assumption is correct. The problem is, I can't seem to connect textview creation with content i.e. look at my code, it parses through text range to give me strings array that would fit in each page. Just for trying's sake, I followed your example of 2 textviews but I see blank 2nd text view, despite they both share same text storage. – Nirav Bhatt Jul 08 '19 at 11:56