异步加载且开启allowsNonContiguousLayout时NSTextView自动滚到底部的原因及解决方法咨询
异步加载且开启allowsNonContiguousLayout时NSTextView自动滚到底部的原因及解决方法咨询
嗨,我来帮你拆解这个问题的根源和对应的解决办法~
一、问题根源分析
这个现象的核心是**allowsNonContiguousLayout的特性 + 异步执行布局代码**共同作用的结果:
allowsNonContiguousLayout是NSTextView布局管理器的一个性能优化开关,开启后它允许文本视图只渲染可见区域的内容,而非一次性渲染全部文本。但这个特性会改变布局更新的触发逻辑。- 当你在
DispatchQueue.main.async里执行setupTextView时,此时视图控制器的viewDidLoad已经完成,视图的初始布局状态已经稳定。这时再添加文本视图、设置内容并开启非连续布局,布局管理器会在异步任务完成后触发一次额外的布局校正,这个过程中系统默认会将滚动位置定位到内容的末尾(仿佛要让用户看到最新加载的内容)。
而另外两种正常的情况也能印证这个逻辑:
- 去掉
async直接同步执行:此时视图还在初始化流程中,布局尚未稳定,设置文本后滚动位置会默认停留在顶部,不会触发后续的布局校正滚动。 - 关闭
allowsNonContiguousLayout:布局管理器会采用传统的连续布局逻辑,即使异步执行,也不会触发自动滚动到底部的额外行为。
二、最佳解决方法(满足异步+开启非连续布局的需求)
既然你需要保留异步执行和allowsNonContiguousLayout的设置,最可靠的方式是在设置完文本内容后,手动强制将滚动位置重置到顶部。具体修改你的setupTextView方法即可:
private func setupTextView() { let scrollView = NSTextView.scrollableTextView() guard let textView = scrollView.documentView as? NSTextView else { return } textView.string = string // 关键:手动滚动到文本的起始位置 textView.scrollRangeToVisible(NSMakeRange(0, 0)) 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), ]) }
如果上面的代码偶尔还是有问题(比如布局尚未完全生效导致滚动不生效),可以把滚动操作再包一层异步延迟,确保布局已经完成:
textView.string = string textView.layoutManager?.allowsNonContiguousLayout = true // 延迟一小段时间再执行滚动,确保布局更新完成 DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { textView.scrollRangeToVisible(NSMakeRange(0, 0)) }
这样就能确保文本视图始终停留在第一行的位置,同时保留你需要的异步设置和性能优化特性。
备注:内容来源于stack exchange,提问作者luin
相关产品推荐
相关产品推荐

