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