带有布局锚点的编程 NSScrollView - 如何修复滚动?

Programmatic NSScrollView with layout anchors - how to fix the scrolling?

很多关于以编程方式实现滚动视图的 SO 帖子,但我还没有找到针对这种情况的解决方案...在这里我将子视图添加到文档视图(所有内容都使用布局锚点),这除了滚动之外的所有工作。

我认为这里的问题是 AppKit 将示例中的约束解释为没有任何内容可滚动,但我不确定为什么...

import Cocoa

class ViewController: NSViewController {

    var scrollView = NSScrollView()
    var contentView = NSClipView()
    var documentView = ParentView()
    var generateButton = NSButton()
    
    
    @objc func generate(_ sender: NSObject) {
         
        let child = ChildView()
        child.translatesAutoresizingMaskIntoConstraints = false
        documentView.addSubview(child)
        documentView.setupChildViewLayout(sv: child)
    }
    
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        view.addSubview(generateButton)
        view.addSubview(scrollView)
        setupLayout()
    }
    
    
    func setupLayout() {
        
        scrollView.contentView = contentView
        scrollView.documentView = documentView
        
        scrollView.borderType = .lineBorder
        scrollView.hasHorizontalScroller = true
        scrollView.hasVerticalScroller = true
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        contentView.translatesAutoresizingMaskIntoConstraints = false
        documentView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addConstraints([
            scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            scrollView.trailingAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -150),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
            scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100)
        ])

        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            scrollView.trailingAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -150),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
            scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100)
        ])

        scrollView.addConstraints([
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
        ])

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
        ])
        
        scrollView.addConstraints([
            documentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            documentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
            documentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            documentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
        ])

        NSLayoutConstraint.activate([
            documentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            documentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
            documentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            documentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
        ])
        
        // Setup anchor constraints for the button
        setupGenerateButton()
        generateButton.translatesAutoresizingMaskIntoConstraints = false
        
        view.addConstraints([
            generateButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            generateButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
            generateButton.heightAnchor.constraint(equalToConstant: 30),
            generateButton.widthAnchor.constraint(equalToConstant: 200)
        ])
        
        NSLayoutConstraint.activate([
            generateButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            generateButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
            generateButton.heightAnchor.constraint(equalToConstant: 30),
            generateButton.widthAnchor.constraint(equalToConstant: 200)
        ])
    }
    
    func setupGenerateButton() {
        generateButton.attributedTitle = NSMutableAttributedString(string: "GenerateChildView", attributes: [NSAttributedString.Key.strokeColor: (NSColor.white), NSAttributedString.Key.font: NSFont.systemFont(ofSize: (NSFont.systemFontSize))])
        generateButton.wantsLayer = true
        generateButton.bezelColor = NSColor(red: 0.2, green: 0.2, blue: 0.6, alpha: 1.0)
        generateButton.action  = #selector(self.generate(_:))
    }
    
}


class ParentView: NSView {
    
    override func draw(_ dirtyRect: NSRect) {
        
        super.draw(dirtyRect)
    }
    
    func setupChildViewLayout(sv: ChildView) {
        
        if (self.subviews.count < 2) {
        
            self.addConstraints([
                sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
                sv.topAnchor.constraint(equalTo: self.topAnchor)
            ])
            
            NSLayoutConstraint.activate([
                sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
                sv.topAnchor.constraint(equalTo: self.topAnchor)
            ])
        }
        
        else {
            
            let c = self.subviews.count - 2
            let lastView = self.subviews[c]
            
            self.addConstraints([
                sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
                sv.topAnchor.constraint(equalTo: lastView.bottomAnchor)
            ])
            
            NSLayoutConstraint.activate([
                sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
                sv.topAnchor.constraint(equalTo: lastView.bottomAnchor)
            ])
        }
    }
}


class ChildView: NSView {
    
    override var intrinsicContentSize: NSSize {
        
        return CGSize(width: 650, height: 200)
    }
    
    override func draw(_ dirtyRect: NSRect) {
        
        super.draw(dirtyRect)
        
        NSColor.gray.set()
        self.bounds.frame()
        
    }
}

您仍在将内容视图和文档视图固定到滚动视图。只在滚动视图的外部和文档视图的内部添加约束。不要在内容视图、文档视图和滚动视图之间添加约束。在父视图及其子视图之间添加约束,以便子视图适合父视图。

class ViewController: NSViewController {

    var scrollView = NSScrollView()
    var documentView = ParentView()
    var generateButton = NSButton()
    
    @objc func generate(_ sender: NSObject) {
        let child = ChildView()
        child.translatesAutoresizingMaskIntoConstraints = false
        documentView.addSubview(child)
        documentView.setupChildViewLayout(sv: child)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(generateButton)
        view.addSubview(scrollView)
        setupLayout()
    }
    
    func setupLayout() {
        scrollView.documentView = documentView
        
        scrollView.borderType = .lineBorder
        scrollView.hasHorizontalScroller = true
        scrollView.hasVerticalScroller = true
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        documentView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addConstraints([
            scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -150),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
            scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100)
        ])
        
        // Setup anchor constraints for the button
        setupGenerateButton()
        generateButton.translatesAutoresizingMaskIntoConstraints = false
        
        view.addConstraints([
            generateButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            generateButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
            generateButton.heightAnchor.constraint(equalToConstant: 30),
            generateButton.widthAnchor.constraint(equalToConstant: 200)
        ])
    }
    
    func setupGenerateButton() {
        generateButton.attributedTitle = NSMutableAttributedString(string: "GenerateChildView", attributes: [NSAttributedString.Key.strokeColor: (NSColor.white), NSAttributedString.Key.font: NSFont.systemFont(ofSize: (NSFont.systemFontSize))])
        generateButton.wantsLayer = true
        generateButton.bezelColor = NSColor(red: 0.2, green: 0.2, blue: 0.6, alpha: 1.0)
        generateButton.action  = #selector(self.generate(_:))
    }
    
}


class ParentView: NSView {
    
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
    }
    
    func setupChildViewLayout(sv: ChildView) {
        
        if (self.subviews.count == 1) {
            self.addConstraints([
                sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
                sv.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor),
                sv.topAnchor.constraint(equalTo: self.topAnchor),
                sv.bottomAnchor.constraint(equalTo: self.bottomAnchor)
            ])
        }
        
        else {
            
            let c = self.subviews.count - 2
            let lastView = self.subviews[c]
            
            self.addConstraints([
                sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
                sv.topAnchor.constraint(equalTo: lastView.bottomAnchor)
            ])

            let bottomConstraints = self.constraints.filter {
                [=10=].firstAttribute == NSLayoutConstraint.Attribute.bottom
            }
            self.removeConstraints(bottomConstraints)
            self.addConstraints([
                sv.bottomAnchor.constraint(equalTo: self.bottomAnchor)
            ])
        }
    }
}


class ChildView: NSView {
    
    override var intrinsicContentSize: NSSize {
        
        return CGSize(width: 650, height: 200)
    }
    
    override func draw(_ dirtyRect: NSRect) {
        
        super.draw(dirtyRect)
        
        NSColor.gray.set()
        self.bounds.frame()
        
    }
}