带有布局锚点的编程 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()
}
}
很多关于以编程方式实现滚动视图的 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()
}
}