I have an NSView class which takes care of a Custom View created in the nib file.
Now I want to add an NSScrollView to the custom view, but I need to do it programmatically and not using Interface Builder (Embed Into Scroll View).
I have found this code:
NSView *windowContentView = [mainWindow contentView];
NSRect windowContentBounds = [windowContentView bounds];
scrollView = [[NSScrollView alloc] init];
[scrollView setBorderType:NSNoBorder];
[scrollView setHasVerticalScroller:YES];
[scrollView setBounds: windowContentBounds];
[windowContentView addSubview:scrollView];
Assuming I declare as IBOutlets the variables 'mainWindow' and 'scrollView' above, how would I go about connecting them to the proper components in Interface Builder? Does it make any sense to do it this way?
Or is there a better way to add a scroll view programmatically?
P.S. I cannot connect them in the usual way because I cannot create an NSObject Object from Interface Builder, or use the File Owner..
I had difficulty creating NSScrollView
with AutoLayout
programmatically but finally got it to work. This is a Swift
version.
// Initial scrollview
let scrollView = NSScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.borderType = .noBorder
scrollView.backgroundColor = NSColor.gray
scrollView.hasVerticalScroller = true
window.contentView?.addSubview(scrollView)
window.contentView?.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: [], metrics: nil, views: ["scrollView": scrollView]))
window.contentView?.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView]|", options: [], metrics: nil, views: ["scrollView": scrollView]))
// Initial clip view
let clipView = NSClipView()
clipView.translatesAutoresizingMaskIntoConstraints = false
scrollView.contentView = clipView
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .left, relatedBy: .equal, toItem: scrollView, attribute: .left, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .right, relatedBy: .equal, toItem: scrollView, attribute: .right, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 0))
// Initial document view
let documentView = NSView()
documentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.documentView = documentView
clipView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .left, relatedBy: .equal, toItem: documentView, attribute: .left, multiplier: 1.0, constant: 0))
clipView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .top, relatedBy: .equal, toItem: documentView, attribute: .top, multiplier: 1.0, constant: 0))
clipView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .right, relatedBy: .equal, toItem: documentView, attribute: .right, multiplier: 1.0, constant: 0))
// Subview1
let view1 = NSView()
view1.translatesAutoresizingMaskIntoConstraints = false
view1.wantsLayer = true
view1.layer?.backgroundColor = NSColor.red.cgColor
documentView.addSubview(view1)
documentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view1]|", options: [], metrics: nil, views: ["view1": view1]))
// Subview2
let view2 = NSView()
view2.translatesAutoresizingMaskIntoConstraints = false
view2.wantsLayer = true
view2.layer?.backgroundColor = NSColor.green.cgColor
documentView.addSubview(view2)
documentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view2]|", options: [], metrics: nil, views: ["view2": view2]))
// Subview3
let view3 = NSView()
view3.translatesAutoresizingMaskIntoConstraints = false
view3.wantsLayer = true
view3.layer?.backgroundColor = NSColor.blue.cgColor
documentView.addSubview(view3)
documentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view3]|", options: [], metrics: nil, views: ["view3": view3]))
// Vertical autolayout
documentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view1(==100)][view2(==200)][view3(==300)]", options: [], metrics: nil, views: ["view1": view1, "view2": view2, "view3": view3]))
documentView.addConstraint(NSLayoutConstraint(item: documentView, attribute: .bottom, relatedBy: .equal, toItem: view3, attribute: .bottom, multiplier: 1.0, constant: 0))
Brian's answer is correct, here 's how to create NSStackView
inside NSScrollView
in Swift 4.2
See https://github.com/onmyway133/blog/issues/173
You might need to flip NSClipView
final class FlippedClipView: NSClipView {
override var isFlipped: Bool {
return true
}
}
private func setup() {
setupScrollView()
setupStackView()
}
private func setupScrollView() {
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.hasVerticalScroller = true
scrollView.drawsBackground = false
NSLayoutConstraint.activate([
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -30),
scrollView.heightAnchor.constraint(equalToConstant: 400)
])
let clipView = FlippedClipView()
clipView.drawsBackground = false
scrollView.contentView = clipView
clipView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
clipView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
clipView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
clipView.topAnchor.constraint(equalTo: scrollView.topAnchor),
clipView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
]
scrollView.documentView = stackView
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.leftAnchor.constraint(equalTo: clipView.leftAnchor),
stackView.topAnchor.constraint(equalTo: clipView.topAnchor),
stackView.rightAnchor.constraint(equalTo: clipView.rightAnchor),
// NOTE: No need for bottomAnchor
])
}
private func setupStackView() {
stackView.orientation = .vertical
stackView.edgeInsets = NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
NSLayoutConstraint.activate([
myRowView.heightAnchor.constraint(equalToConstant: 40)
])
myRowView.onPress = { [weak self] in
self?.doSomething()
}
stackView.addArrangedSubview(myRowView)
}
This code fragment should demonstrate how to create an NSScrollView programmatically and use it to display any view, whether from a nib or from code. In the case of a nib generated view, you simply need to load the nib file to your custom view prior, and have an outlet to your custom view (outletToCustomViewLoadedFromNib) made to File's Owner.
NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:[[mainWindow contentView] frame]];
// configure the scroll view
[scrollView setBorderType:NSNoBorder];
[scrollView setHasVerticalScroller:YES];
// embed your custom view in the scroll view
[scrollView setDocumentView:outletToCustomViewLoadedFromNib];
// set the scroll view as the content view of your window
[mainWindow setContentView:scrollView];
Apple has a guide on the subject, which I won't link to as it requires Apple Developer Connection access and their links frequently break. It is titled "Creating and Configuring a Scroll View" and can currently be found by searching for its title using Google.
Here is an example of NSStackView
progrmatically added to NSScrollView
. Any view can be added using the following solution but I am taking NSStackView as an example
I am using SnapKit to keep the auto layouting code concise, however, you can use anchors to add constraints without any 3rd party dependency
// Create NSScrollView and add it as a subview in the desired location
let scrollView = NSScrollView()
scrollView.borderType = .noBorder
scrollView.verticalScrollElasticity = .none
addSubview(scrollView)
scrollView.snp.makeConstraints { $0.edges.equalToSuperview() } //match edges to superview
// Assign an instance of NSClipView to `contentView` property of `NSScrollView`
let clipView = NSClipView()
scrollView.contentView = clipView
clipView.snp.makeConstraints { $0.edges.equalTo(scrollView) }
// Assign whatever view you want to put inside scroll view to the `documentView` property.
// Also note I have added just 3 constraints; top bottom and left. That way stackview can freely expand on the right
scrollView.documentView = stackView
stackView.snp.makeConstraints { $0.top.bottom.left.equalTo(clipView) }
Note that I am not using translatesAutoresizingMaskIntoConstraints
after adding a subview because SnapKit handles it internally but if you are adding constraints using anchors then ensure translatesAutoresizingMaskIntoConstraints
is set to false for all the subviews that are added programatically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With