NSView的viewDidMoveToSuperview未调用,如何设置NSTextContainer尺寸?
问题:NSView子类的viewDidMoveToSuperview未触发及NSTextContainer尺寸设置位置优化
我开发了一个基于Storyboard的App,在NSViewController中设置了NSView子类RMTextView。为使NSTextContainer宽度与视图宽度匹配,尝试在func viewDidMoveToSuperview()中设置,但该函数及func viewWillMove(toSuperview newSuperview: NSView?)均未被调用。请问这些函数何时不会触发?目前我可在func viewDidMoveToWindow()中设置textContainer尺寸,但有没有更合适的位置?
补充:RMTextView是Storyboard中ViewController场景的子视图(层级:ViewController->View->RMTextView)
相关代码:
class ViewController: NSViewController { @IBOutlet var textView: RMTextView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override var representedObject: Any? { didSet { // Update the view, if already loaded. } } } class RMTextView: NSView, CALayerDelegate { public var textLayoutManager: NSTextLayoutManager! public var textContentStorage: NSTextContentStorage! public var textContainer: NSTextContainer! internal var fragmentLayerMap: NSMapTable<NSTextLayoutFragment, CALayer>! override init(frame frameRect: NSRect) { super.init(frame: frameRect) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } func commonInit() { wantsLayer = true contentLayer = CALayer() layer?.addSublayer(contentLayer) fragmentLayerMap = .weakToWeakObjects() textContentStorage = NSTextContentStorage() textContainer = NSTextContainer(containerSize: NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) textLayoutManager = NSTextLayoutManager() textLayoutManager.textContainer = textContainer textContentStorage.addTextLayoutManager(textLayoutManager) textLayoutManager.textViewportLayoutController.delegate = self setSelectedRange(NSTextRange(location: textContentStorage.documentRange.location)) } override func viewDidMoveToSuperview() { // This is never called print("viewDidMoveToSuperview") } override func viewDidMoveToWindow() { // Is there a better place to set the textContainer size to match the view bounds if superview != nil { textContainer.size = CGSize(width: self.bounds.width, height: .greatestFiniteMagnitude) } } }
回答
一、viewDidMoveToSuperview/viewWillMove(toSuperview:)不触发的场景
- Storyboard/XIB加载视图时:这类场景下视图的层级是一次性构建完成的,视图在初始化阶段就已经被添加到父视图中,不存在"动态添加到父视图"的过程,因此这两个方法不会被触发。它们仅在代码中动态添加/移除视图到父视图时才会调用。
- 视图从未被添加到父视图:如果视图自创建后一直没有被添加到任何父视图层级中,这两个方法也不会触发。
二、设置NSTextContainer尺寸的更优方案
推荐以下几种更合适的位置,覆盖初始加载和后续尺寸变化的场景:
- 重写viewDidLayout()方法
这个方法会在视图的bounds发生变化时自动调用,既能处理初始加载时的尺寸设置,也能响应窗口调整、布局更新等后续变化,是最适配的方案:
override func viewDidLayout() { super.viewDidLayout() textContainer.size = CGSize(width: bounds.width, height: .greatestFiniteMagnitude) }
- KVO监听bounds变化
在commonInit中添加对bounds属性的监听,实时响应尺寸变化:
// 在commonInit方法内添加 addObserver(self, forKeyPath: "bounds", options: [.new], context: nil) // 实现监听回调 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "bounds" else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } textContainer.size = CGSize(width: bounds.width, height: .greatestFiniteMagnitude) } // 记得在deinit中移除监听 deinit { removeObserver(self, forKeyPath: "bounds") }
- 在ViewController的viewDidLayout中处理
如果RMTextView的尺寸依赖于ViewController的视图布局,也可以在ViewController的viewDidLayout方法中设置,但这种方式需要通过IBOutlet引用RMTextView,灵活性不如前两种:
override func viewDidLayout() { super.viewDidLayout() textView.textContainer.size = CGSize(width: textView.bounds.width, height: .greatestFiniteMagnitude) }
内容的提问来源于stack exchange,提问作者georgemp
相关产品推荐
相关产品推荐

