NSLayoutConstraints crashing on ios7 but not on ios8 - ios7

I've setup in a UIViewController some layout constraints which works fine on ios8. But as soon as I run it on ios7 i've got the following error :
*** Assertion failure in -[UIView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8803
Here is my code :
class DatacenterIndicatorViewController: UIViewController {
let sideMargins:Float = 12.0
var dataCenterPollingLabel:UILabel = UILabel()
var dataCenterAlarmLabel:UILabel = UILabel()
//MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(dataCenterPollingLabel)
self.view.addSubview(dataCenterAlarmLabel)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.reloadData()
}
func reloadData() {
self.setupAlarmLabel()
self.setupPollingLabel()
self.generateConstraints()
}
func setupPollingLabel() {
// some graphic setup
}
func setupAlarmLabel() {
// some graphic setup
}
func generateConstraints() {
self.dataCenterPollingLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
self.dataCenterAlarmLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addConstraint(NSLayoutConstraint(item: dataCenterPollingLabel, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0))
self.view.addConstraint(NSLayoutConstraint(item: dataCenterAlarmLabel, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0))
self.view.addConstraint(NSLayoutConstraint(item: dataCenterAlarmLabel, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: dataCenterPollingLabel, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0.0))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(NSString(format:"H:|-==%f-[dataCenterPollingLabel]-==%f-[dataCenterAlarmLabel]-==%f-|", sideMargins, sideMargins, sideMargins), options: NSLayoutFormatOptions.allZeros, metrics: nil, views: ["dataCenterPollingLabel": dataCenterPollingLabel, "dataCenterAlarmLabel": dataCenterAlarmLabel]))
}
}
What is wrong in my code ? I can even know where to look for some errors, everything looks fine to me.

I faced the same issue in iOS 7 version. Calling self.view.layoutIfNeeded() function instead of super.viewDidLayoutSubviews() function at the end of the viewDidLayoutSubviews method solved the issue.
Code snippet
override func viewDidLayoutSubviews() {
// Your statements
self.reloadData()
//Write the below statement at the end of the function
self.view.layoutIfNeeded()
}

Related

Unable to add button on top right inside UITextView programmatically in swift

the textview is dynamic, which contains a button on top right corner. it works for textfield. not showing button in textview..
Awaiting....
class CommonTextView: UITextView {
private let microphoneButton = UIButton(type: .System)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initTextView()
}
func initTextView() -> Void {
self.layer.cornerRadius = 7
microphoneButton.translatesAutoresizingMaskIntoConstraints = false
//microphoneButton.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.3)
microphoneButton.setImage(UIImage(named: "mic"), forState: .Normal)
microphoneButton.tintColor = UIColor.darkGrayColor()
microphoneButton.addTarget(self, action: #selector(self.microphonePressed), forControlEvents: .TouchUpInside)
self.addSubview(microphoneButton)
let trailingConstraint = NSLayoutConstraint(item: microphoneButton, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: microphoneButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0)
let widthConstraint = NSLayoutConstraint(item: microphoneButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
let heightConstraint = NSLayoutConstraint(item: microphoneButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
self.addConstraints([trailingConstraint, topConstraint, widthConstraint, heightConstraint])
}
It's too complicated to deal with NSLayoutConstraint. Instead, try this simple Anchor layout code. Replace everything under self.addSubView to these simple five lines of code
let margins = view.layoutMarginsGuide
microphoneButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: 0).isActive = true
microphoneButton.topAnchor.constraint(equalTo: margins.topAnchor, constant: 100).isActive = true
microphoneButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
microphoneButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
See for the proper Constraints rightAnchor to textView not working give it self.view.rightAnchor
Black Part is textView and Orange part is Button
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(textView)
textView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
textView.heightAnchor.constraint(equalToConstant: 200).isActive = true
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textView.addSubview(button)
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.topAnchor.constraint(equalTo: textView.topAnchor).isActive = true
**button.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true**
view.bringSubview(toFront: button)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
let textView: UITextView = {
let label = UITextView()
// label.text = "Jeevan Chandra Tiwari"
label.backgroundColor = .black
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let button: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = .orange
button.setTitle("Click Me", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
}

iOS constraint animation did not happen immediately

Basically, I use two difference constraints to let a UIView to slide to and from (Left to Right or Right to Left)
However the in-flight animation should stop right there, and immediately begin to animate to the opposite direction, since I have manipulated the constraints and called layoutIfNeeded:.
Strangely, the new animation needs some time to take effect. The old animation will stop slowly. It's like the UIView has some "Inertia" and cannot stop immediately.
Why is this? Can this be addressed in a easy way? I've tried
"UIViewAnimationOptions.BeginFromCurrentState" - not working.
Talk is cheap, show you the code:
Please Just open a "Xcode iOS single view project" and paste all of the code to "ViewController.swift." You do not have change anything else.
You can pull down the green view in the simulator multiple times and check this animation issue.
//
// ViewController.swift
// testItem
//
// Created by 陈成 on 15/8/11.
// Copyright © 2015年 陈成. All rights reserved.
//
import UIKit
class ccControl: UIControl {
var imageView: UIImageView!
var firstPoint: CGPoint!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blueColor()
imageView = UIImageView()
imageView.frame = CGRectMake(10, -363, 60, 438)
imageView.backgroundColor = UIColor.greenColor()
self.addSubview(imageView)
}
override func intrinsicContentSize() -> CGSize {
return CGSizeMake(80, 175)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
print("beginTrackingWithTouch")
let touchPoint = touch.locationInView(self)
if CGRectContainsPoint(imageView.frame, touchPoint) {
firstPoint = touchPoint
return true
}
return false
}
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
let touchPoint = touch.locationInView(self)
var dy = touchPoint.y - firstPoint.y
dy = max(dy, 0)
dy = min(dy, self.bounds.size.height - 75)
self.imageView.frame = CGRectMake(10, dy + 75 - 438, 60, 438)
if dy > 75 {
self.sendActionsForControlEvents(UIControlEvents.ValueChanged)
UIView.animateWithDuration(1.0, animations: { self.imageView.frame = CGRectMake(10, 75 - 438, 60, 438) }, completion: nil)
return false
}
return true
}
}
class ViewController: UIViewController {
var status = "a"
var hid: NSLayoutConstraint!
var sho: NSLayoutConstraint!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let cd = ccControl()
cd.translatesAutoresizingMaskIntoConstraints = false
cd.addTarget(self, action: "vc", forControlEvents: UIControlEvents.ValueChanged)
self.view.addSubview(cd)
let hiddenView = UIView()
hiddenView.backgroundColor = UIColor.purpleColor()
hiddenView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(hiddenView)
// add constraints
self.view.addConstraint(NSLayoutConstraint(item: cd, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: topLayoutGuide, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0))
hid = NSLayoutConstraint(item: hiddenView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
sho = NSLayoutConstraint(item: hiddenView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
self.view.addConstraint(hid)
self.view.addConstraint(NSLayoutConstraint(item: hiddenView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 100))
self.view.addConstraint(NSLayoutConstraint(item: hiddenView, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: hiddenView, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0))
self.view.layoutSubviews()
}
func vc() {
switch status {
case "a":
status = "b"
self.view.removeConstraint(hid)
self.view.addConstraint(sho)
case "b":
status = "a"
self.view.removeConstraint(sho)
self.view.addConstraint(hid)
default:
break
}
self.view.setNeedsLayout()
// UIView.animateWithDuration(10, delay: 0, options: [UIViewAnimationOptions.BeginFromCurrentState, UIViewAnimationOptions.AllowAnimatedContent, UIViewAnimationOptions.AllowUserInteraction], animations: {
// for i in self.view.subviews {
// i.layer.removeAllAnimations()
// }
//
// self.view.layoutIfNeeded()
// }, completion: nil)
UIView.animateWithDuration(10.0) {
self.view.layoutIfNeeded()
}
}
}
layoutIfNeeded forces the receiver to layout its subviews immediately if required.
You might need to call setNeedsLayout. Try this:
self.view.setNeedsLayout()
UIView.animateWithDuration(20.0) {
self.view.layoutIfNeeded()
}
As far as documentation goes, you need to call layoutIfNeeded before you start animating constraints. It's gonna then finish redrawing content from animation before if need be and then your view should be ready to animate right away.
view.layoutIfNeeded()
// make some constraint changes
UIView.animateWithDuration(20.0) {
self.view.layoutIfNeeded()
}

Translating NSConstraints From Objective-C to Swift

I'm looking to turn my objective-c based universal application into a Swift project. I've been able to swap most of the code so far, but I'm having trouble translating the following:
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self.contentView
attribute:NSLayoutAttributeLeading
relatedBy:0
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0];
[self.view addConstraint:leftConstraint];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self.contentView
attribute:NSLayoutAttributeTrailing
relatedBy:0
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0];
[self.view addConstraint:rightConstraint];
I also have to change this code as well:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return NO;
}
Is there a method similar to this in Swift?
Any help would be greatly appreciated. Thanks in advance to all who reply. :)
let leftConstranint = NSLayoutConstraint(item: self.contentView, attribute: .Leading, relatedBy: .Equal, toItem: self.view, attribute: .Left, multiplier: 1.0, constant: 0)
self.view.addConstraint(leftConstranint)
let rightConstraint = NSLayoutConstraint(item: self.contentView, attribute: .Trailing, relatedBy: .Equal, toItem: self.view, attribute: .Right, multiplier: 1.0, constant: 0)
self.view.addConstraint(rightConstraint)
.Leading, .Left, .Trailing and .Right are Enums from NSLayoutAttribute.
Also the function from UIGestureRecognizerDelegate:
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
With Cartography, you can do the above Autolayout in the following:
layout(self.contentView, self.view) { (contentView, view) -> () in
contentView.leading == view.left
contentView.trailing == view.right
}
With Snap, you can do the above Autolayout in the following:
self.contentView.snp_makeConstraints { maker in
maker.leading.equalTo(self.view.snp_left)
maker.trailing.equalTo(self.view.snp_right)
}
Cartography is more a mathematical way to make your constraints in code. In other way, Snap is more like an English language way to describe your constraints in words.
Unlike Visual Language Format (coding your constraints in plain string), both Cartography and Snap can take advantage of code-completion in XCode.
Using AutoLayout Visual Format Language is generally considered preferrable:
var viewsDictionary = [ "contentView" : self.contentView,
"view" : self.view ]
var viewsConstraint =
NSLayoutConstraint.constraintsWithViaualFormat(
"[contentView][view]",
options: 0,
metrics: nil,
views: viewsDictionary)
view.addConstraint(viewsConstraint)
But you can use the other form as well:
var leftConstraint =
NSLayoutConstraint(item: contentView,
attribute: .Leading,
relatedBy: .Equal,
toItem: view,
attribute: .Left,
multiplier: 1.0,
constant: 0)
view.addConstraint(leftConstraint)
var rightConstraint =
NSLayoutConstraint(item: contentView,
attribute: .Trailing,
relatedBy: .Equal,
toItem: view,
attribute: .Right,
multiplier: 1.0,
constant: 0)
view.addConstraint(rightConstraint)
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}

Autolayout constraints and child view controller

I have two view controllers, parent and child.
So in the viewDidLoad method I do the following:
ChildViewController* childViewController = [[ChildViewController alloc] init];
[self addChildViewController:childViewController];
// ChildViewController sets his own constraints in viewDidLoad
[self.view addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
//
// setup constraints to expand childViewController.view to
// fill the size of parent view controller
//
So basically what happens is that updateViewConstraints is called on ChildViewController before parent controller constraints apply, so in fact self.view.frame == CGRectZero, exactly the same as I specified in custom loadView method in ChildViewController.
translatesAutoresizingMaskIntoConstraints all set to NO for all views.
What's the proper way to setup constraints in this case so ChildViewController updates his constraints after parent?
Current log from both controllers is pretty frustrating, I do not understand how updateViewConstraints can be called before viewWillLayoutSubviews:
App[47933:c07] ChildViewController::updateViewConstraints. RECT: {{0, 0}, {0, 0}}
App[47933:c07] ParentViewController::updateViewConstraints
App[47933:c07] ChildViewController:viewWillLayoutSubviews. RECT: {{0, 0}, {984, 454}}
App[47933:c07] ChildViewController:viewDidLayoutSubviews. RECT: {{0, 0}, {984, 454}}
You can add constraints right after invoke of addSubview: , don't forget to set translatesAutoresizingMaskIntoConstraints to false
Here is a code snippet of adding and hiding child view controller with constraints (inspired by apple guide)
Display
private func display(contentController content : UIViewController)
{
self.addChildViewController(content)
content.view.translatesAutoresizingMaskIntoConstraints = false
self.containerView.addSubview(content.view)
content.didMove(toParentViewController: self)
containerView.addConstraints([
NSLayoutConstraint(item: content.view, attribute: .top, relatedBy: .equal, toItem: containerView, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: content.view, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: content.view, attribute: .leading, relatedBy: .equal, toItem: containerView, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: content.view, attribute: .trailing, relatedBy: .equal, toItem: containerView, attribute: .trailing, multiplier: 1, constant: 0)
])
}
Hide
private func hide(contentController content : UIViewController)
{
content.willMove(toParentViewController: nil)
content.view.removeFromSuperview()
content.removeFromParentViewController()
}
I have a similar situation where I add a child controller to a visible controller (a popup).
I define the child view controller in interface builder. It's viewDidLoad method just calls setTranslatesAutoresizingMaskIntoConstraints:NO
Then I define this method on the child controller which takes a UIViewController parameter, which is the parent.
This method adds itself to the given parent view controller and defines its own constraints:
- (void) addPopupToController:(UIViewController *)parent {
UIView *view = [self view];
[self willMoveToParentViewController:parent];
[parent addChildViewController:self];
[parent.view addSubview:view];
[self didMoveToParentViewController:parent];
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[view]-0-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(view)];
[parent.view addConstraints:horizontalConstraints];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[view]-0-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(view)];
[parent.view addConstraints:verticalConstraints];
}
then inside the parent UIViewController (the one which is already displayed), when I want to display my child popup view controller it calls:
PopUpNotificationViewController *popup = [[self storyboard] instantiateViewControllerWithIdentifier:#"NotificationPopup"];
[popup addPopupToController:self];
You can define whatever constraints you want on the child controller's view when adding it to the parent controller's view.
I think the layoutIfNeeded method will only work if you have previously called setNeedsLayout. If you use setNeedsLayout instead, the system will know to update at an appropriate time. Try changing that in your code.
When you add constraints to a view, that view should automatically layout its subviews again to account for the new constraints. There should be no need to call setNeedsLayout unless something has changed since you have added the constraints. Are you adding the constraints to a view and if so: are you adding them to the right view?
One thing you can try is to subclass UIView so that you see a log message whenever the ChildViewController.view or ParentViewController.view performs a fresh layout:
-(void) layoutSubviews {
[super layoutSubviews];
NSLog(#"layoutSubviews was called on the following view: %#", [view description]);
}
Maybe that will reveal something about when your views are (or aren't) layout out their subviews.
According to the following link, I moved constraints creation to viewWillLayoutSubviews, this is the place where view bounds are set properly. I feel this answer misses explanation on why Child view controller's updateViewConstraints called before parent view controller, or maybe it's just some bug in my code, but this workaround solves the problem...
It is convenient to put the code for adding and removing a child controller into an extension. Usage example:
override func viewDidLoad() {
super.viewDidLoad()
let childController = YourCustomController()
add(childController)
}
The code itself:
extension UIViewController {
func add(_ controller: UIViewController) {
addChild(controller)
view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
controller.view.topAnchor.constraint(equalTo: view.topAnchor),
controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
controller.didMove(toParent: self)
}
func remove() {
guard parent != nil else {
return
}
willMove(toParent: nil)
view.removeFromSuperview()
removeFromParent()
}
}

Create NSScrollView Programmatically in an NSView - Cocoa

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))
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.
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)
}
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.