I have a simple Swift code for a macOS app:
// ViewController.swift
import Cocoa
let string = (1...30).map { "line \($0)" }.joined(separator: "\n")
class ViewController: NSViewController {
private func setupTextView() {
let scrollView = NSTextView.scrollableTextView()
guard let textView = scrollView.documentView as? NSTextView else {
return
}
textView.string = string
/**
* If I comment the following line, it won't scroll.
*/
textView.layoutManager?.allowsNonContiguousLayout = true
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
view.topAnchor.constraint(equalTo: scrollView.topAnchor),
view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
/**
* If I execute self.setupTextView() directly without DispatchQueue.main.async, it won't scroll.
*/
DispatchQueue.main.async {
self.setupTextView()
}
}
}
When the app is starts, the text view automatically scrolls to the bottom, contrary to my intent for it to remain unscrolled so that the first line is visible.
Interestingly, if I commented out textView.layoutManager?.allowsNonContiguousLayout = true, or moved self.setupTextView() out of the async block, it worked as expect.
Just wanted to understand what is the root cause of the scrolling, and what's the best practice to avoid it (given I need to set it up async and allowsNonContiguousLayout is required)?
scrollView.frame.sizeis(width = 0, height = 0). The number of lines and scroll position can't be calculated correctly. When the scroll view is resized later, the scroll position remains incorrect.I think the Non Contiguous Layout calculates the number of lines differently.
The async block adds the scroll view when its superview is visible. I think the frame sizes, number of lines and scroll position are calculated in a different order.
Solution: Set the frame size of the scroll view to a realistic value. For example in this case
scrollView.frame = view.boundsor
scrollView.frame.size = NSSize(width: 100, height: 100)