extension String {
static let blanks: [Character] = [" ", "\n", "\t"]
func splitToFit (size: CGSize, using font: UIFont) -> [String] {
var output: [String] = []
var (fitted, remaining) = fit(self, in: size, using: font)
var lastCount = fitted.count
output.append(fitted)
while remaining != "" {
(fitted, remaining) = fit(remaining,
in: size,
using: font,
lastCount: lastCount)
lastCount = fitted.count
output.append(fitted)
}
//Trim white spaces if needed
//return output.map { $0.trimmingCharacters(in: .whitespacesAndNewlines)}
return output
}
private func fit(
_ str: String,
in size: CGSize,
using font: UIFont,
lastCount: Int = 0) -> (String, String) {
if !str.isTruncated(in: size, using: font) {
return (str, "")
}
var low = 0
var high = str.count - 1
let lastValidIndex = high
var substr = ""
let step = lastCount/10 //Could be adjusted
if lastCount != 0 {
high = min(lastCount, lastValidIndex)
while !str[0..<high].isTruncated(in: size, using: font) {
low = high
high = min(high + step, lastValidIndex)
}
}
while low < high - 1 {
let mid = (low + (high - low)/2
//Or more efficiently
//let mid = Int((UInt(low) + UInt(high)) >> 1)
//Have a look here https://ai.googleblog.com/2006/06/extra-extra-read-all-about-it-nearly.html
substr = str[0..<mid]
let substrTruncated = substr.isTruncated(in: size, using: font)
if substrTruncated {
high = mid
} else {
low = mid
}
}
substr = str[0..<low]
while !String.blanks.contains(substr.last!) {
substr.removeLast()
low -= 1
}
let remains = str[low..<str.count]
return (substr, remains)
}
}