Mac OS X use auto layout with view controller and allow window resize - macos

I'm trying to add a view to fill the entire window in Mac OS X like so:
func viewDidLoad() {
sparkView = NSView(frame: CGRectMake(0, 0, 600,400))
self.view.addConstraint(NSLayoutConstraint(item: sparkView!, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: sparkView!, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: sparkView!, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: sparkView!, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0))
sparkView?.contentCompressionResistancePriorityForOrientation(NSLayoutConstraintOrientation(rawValue: 499)!)
}
Why does this constrain the window size? If i do this I can't make the window bigger. I want sparkView to always fill the window. For context, this is created in the view controller that comes inside the NSWindow in storyboard.
I tried not setting the frame of sparkView, but then the window just collapses and there is no height at all.

Whenever you create a view programmatically and want to use auto layout to position it, you need to turn its translatesAutoresizingMaskIntoConstraints property to false.

Related

Autolayout not working - Child View Size always 0 while it should have same size as Superview

I have an odd behaviour for NSView in regards to autolayout.
I am bounding my only child view to the superview's size by this code:
func fillHorizontal() {
guard let sv = self.superview else {
assert(false)
return
}
self.translatesAutoresizingMaskIntoConstraints = false
sv.addConstraint(NSLayoutConstraint(
item: self, attribute: .width,
relatedBy: .equal, toItem: sv,
attribute: .width, multiplier: 1, constant: 0))
sv.addConstraint(NSLayoutConstraint(
item: self, attribute: .left,
relatedBy: .equal, toItem: sv,
attribute: .left, multiplier: 1, constant: 0))
}
func fillVertical() {
guard let sv = self.superview else {
assert(false)
return
}
self.translatesAutoresizingMaskIntoConstraints = false
sv.addConstraint(NSLayoutConstraint(
item: self, attribute: .height,
relatedBy: .equal, toItem: sv,
attribute: .height, multiplier: 1, constant: 0))
sv.addConstraint(NSLayoutConstraint(
item: self, attribute: .top,
relatedBy: .equal, toItem: sv,
attribute: .top, multiplier: 1, constant: 0))
}
func bindFrameToSuperviewBounds() {
fillHorizontal()
fillVertical()
}
The resulting constraints are as I would expect
However, I was never able to see the child view (GraphView). When started debugging/tweaking the setFrameSize function in child view like this:
override func setFrameSize(_ newSize: NSSize) {
if let superSize = self.superview?.frame.size, newSize == superSize { //this is quite dodgy. Not sure why I get too many zero-sized initiations.
super.setFrameSize(newSize)
}
}
then it became clear that all the layout engine is always calculating a size 0 for the child view.
How can that be? Who can tell me my mistake? Many thanks in advance!
Kamil Szostakowski asked the right question. Autolayout doesn't work well with manual size setting. I need to set NSView frame size through constraints.
Thanks Kamil.

Shadow is drawn in "main" Storyboard but not in any other storyboard

I try to write my own comboBox as NSComboBox does not has features I need.
So, I subclassed an NSTextField and monitor the textinput and depending of the stringValue a TableView will get it's data.
So when the TableView should be displayed the TextFields superview will add the NSScrollView and adjust it's height and so on.
All of that works fine.
What goes not so well is the DropShadow.
So, I create a new Xcode-Project an in the main.Storyboard add a NSTextField and change the class to my Subclass.
As soon as I type text the TableView appears and has a the dropShadow.
Next I create a new Storyboard, add a new WindowController and do the same steps as in the main.Storyboard: Adding an NSTextField, change the class to my subclass.
Now I add a Button in the main.Storyboard which has an action to present the new Storyboard.
In the new storyboard the textfield and tableView behave as expected except that the TableView/ScrollView does not have any shadow.
Even when I change MainInterface in the Generals tab to the new Storyboard, no DropShadow for the TableView.
The Settings in the IB for both Storyboards look equal.
So, any hint how I can fix this?
Here is the code for adding and displaying the scrollView:
self.scrollView = NSScrollView(frame: NSRect.zero)
self.scrollView!.documentView = self.tableView
self.scrollView!.translatesAutoresizingMaskIntoConstraints = false
self.scrollViewHeightConstraint = NSLayoutConstraint(item: self.scrollView!, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
let topConstraint = NSLayoutConstraint(item: self.scrollView!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
let widthConstraint = NSLayoutConstraint(item: self.scrollView!, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.width, multiplier: 1, constant: 0)
let leadinghConstraint = NSLayoutConstraint(item: self.scrollView!, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.leading, multiplier: 1, constant: 0)
superview.addSubview(self.scrollView!)
superview.addConstraints([self.scrollViewHeightConstraint!,topConstraint,widthConstraint,leadinghConstraint])
let dropShadow = NSShadow()
dropShadow.shadowColor = NSColor.darkGray
dropShadow.shadowOffset = NSMakeSize(0, 10.0)
dropShadow.shadowBlurRadius = 3.0
self.scrollView!.wantsLayer = true
self.scrollView!.shadow = dropShadow
After a quick test … I found the "problem".
For the new ViewControllers view I had to set
self.view.wantsLayer = true

Swift 3 | Adding constraints programmatically doesn't center my subview

I try to write an UIImageView extension which display an ActivityIndicator in the middle of my ImageView, I tried the following, note that my ImageViews have a constraint on height (100) and width (100) :
public extension UIImageView {
public func downloadedFrom(url: URL) {
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
activityIndicator.startAnimating()
let leftSpaceConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 40)
let topSpaceConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 40)
let widthConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 20)
let heightConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 20)
self.addSubview(activityIndicator)
self.addConstraints([leftSpaceConstraint, topSpaceConstraint, widthConstraint, heightConstraint])
}
}
I also tried to set leadingSpaceConstraint / trailingSpaceConstraint / horizontalConstraint / verticalConstraint but my code has no effect, every constraints I set doesn't do change anything. My activityIndicator stay at the top left like this :
I don't understand why ? The method "downloadedFrom" is called in the viewDidLoad like this :
myImageView.downloadedFrom(url: imageUrl)
Where did I go wrong ? TY
You are placing your imageView on the top of parent UIView from the left side. It needs to be depending on coordinates of the middle of parent UIImageView for x-axis and y-axis. And also, set translatesAutoresizingMaskIntoConstraints to false. Look at the code below:
public extension UIImageView {
public func downloadedFrom(url: URL) {
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
activityIndicator.startAnimating()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
let leftSpaceConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let topSpaceConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)
let widthConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 20)
let heightConstraint = NSLayoutConstraint(item: activityIndicator, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 20)
self.addSubview(activityIndicator)
self.addConstraints([leftSpaceConstraint, topSpaceConstraint, widthConstraint, heightConstraint])
}
}

How to add a custom UIView programmatically with Swift and see it in Interface Builder

I'm trying to add a UserView with controller to a view with Xcode 7 / Swift 2.0.
I was not able to figure out how to do this using Interface Builder, instead I added a NSView as a placeholder view and then add the view in code.
Is it possible to do it with the Interface Builder?
In the image, the orange is the place holder and the green is the user view:
When adding the view in code, I assume I have to add the constraints. I tried to use the constraintsWithVisualFormat like below, but when I add this, the view cannot be resized. I think it might be related to the priorities.
How do I get the green subview to fill the place holder container and be resizable?
override func windowDidLoad() {
super.windowDidLoad()
print("MainWindowController.windowDidLoad");
let view = UserView01();
self.m_viewPlaceHolder.addSubview(view.view);
let hor = NSLayoutConstraint.constraintsWithVisualFormat("H:|-10-[view]-10-|", options: .AlignAllLeft, metrics: nil, views: ["view" : view.view]);
let ver = NSLayoutConstraint.constraintsWithVisualFormat("V:|-10-[view]-10-|", options: .AlignAllLeft, metrics: nil, views: ["view" : view.view]);
NSLayoutConstraint.activateConstraints(hor);
NSLayoutConstraint.activateConstraints(ver);
}
First of all, there is no need to add any placeholders for the view you create programmatically. If you want to see it in the storyboard, I'd suggest you make the view class #IBDesignable:
#IBDesignable class UserView01: UIView {
// class declaration
}
The properties you want to be changing as you code should be declared as #IBInspectable similarly to class declaration.
To ensure the programmatically set constraints are executed you have to add the following line before the constraints code:
view.translatesAutoresizingMaskIntoConstraints = false
It tells Xcode to use the constraints you set without trying to add any automatically, which normally Xcode does. Other than that - your code should work fine.
There is also another way to add relative constraints which you might use as well:
let leadingConstraint = NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: placeholder, attribute: .Leading, multiplier: 1, constant: 20)
let trailingConstraint = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: placeholder, attribute: .Trailing, multiplier: 1, constant: 20)
let topConstraint = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: placeholder, attribute: .Top, multiplier: 1, constant: 20)
let bottomConstraint = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: placeholder, attribute: .Bottom, multiplier: 1, constant: 20)
view.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
Just change the view and placeholder to the names you use for the view and its superview in your project.

how to instantiate an NSLayoutConstraint in swift? (Xcode 6.1.1)

I'm getting an error "Extra argument in call" pointing at the first parameter.
let imageView = UIImageView()
let imageHeight = 10.0
let heightConstraint = NSLayoutConstraint(imageView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1.0, constant: imageHeight)
I've also tried using item: imageView,..., but I get the same error.
I think the problem is that imageHeight is double, but the constructor expects a CGFloat, so try
let imageHeight: CGFloat = 10.0
and looks like there is one more little thing:
NSLayoutConstraint(item: imageView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1.0, constant: imageHeight)
you left the item before imageView, but you've mentioned that.

Resources