Printing NSTextStorage over multiple UITextView produces weird results

I would like to print a NSTextStorage on multiple pages and add annotations to the side margins corresponding to certain text ranges. For example, for all occurrences of # at the start of a line, the side margin should show an automatically increasing number.

My idea was to create a NSLayoutManager and dynamically add NSTextContainer instances to it until all text is laid out. The layoutManager would then allow me to get the bounding rectangle of the interesting text ranges so that I can draw the corresponding numbers at the same height inside the side margin. This approach works well on macOS, but I'm having some issues on iOS.

When running the code below in an iPad Simulator, I would expect that the print preview shows 3 pages, the first with the numbers 0-1, the second with the numbers 2-3, and the last one with the number 4. Instead the first page shows the number 4, the second one the numbers 2-4, and the last one the numbers 0-4. It's as if the pages are inverted, and each page shows the text starting at the correct location but always ending at the end of the complete text (and not the range assigned to the relative textContainer).

I've created FB17026419.

class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
let printController = UIPrintInteractionController.shared
let printPageRenderer = PrintPageRenderer()
printPageRenderer.pageSize = CGSize(width: 100, height: 100)
printPageRenderer.textStorage = NSTextStorage(string: (0..<5).map({ "\($0)" }).joined(separator: "\n"), attributes: [.font: UIFont.systemFont(ofSize: 30)])
printController.printPageRenderer = printPageRenderer
printController.present(animated: true) { _, _, error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
class PrintPageRenderer: UIPrintPageRenderer, NSLayoutManagerDelegate {
var pageSize: CGSize!
var textStorage: NSTextStorage!
private let layoutManager = NSLayoutManager()
private var textViews = [UITextView]()
override var numberOfPages: Int {
if !Thread.isMainThread {
return DispatchQueue.main.sync { [self] in
numberOfPages
}
}
printFormatters = nil
layoutManager.delegate = self
textStorage.addLayoutManager(layoutManager)
if textStorage.length > 0 {
let glyphRange = layoutManager.glyphRange(forCharacterRange: NSRange(location: textStorage.length - 1, length: 0), actualCharacterRange: nil)
layoutManager.textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil)
}
var page = 0
for textView in textViews {
let printFormatter = textView.viewPrintFormatter()
addPrintFormatter(printFormatter, startingAtPageAt: page)
page += printFormatter.pageCount
}
return page
}
func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) {
if textContainer == nil {
addPage()
}
}
private func addPage() {
let textContainer = NSTextContainer(size: pageSize)
layoutManager.addTextContainer(textContainer)
let textView = UITextView(frame: CGRect(origin: .zero, size: pageSize), textContainer: textContainer)
textViews.append(textView)
}
}
Printing NSTextStorage over multiple UITextView produces weird results
 
 
Q