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