Autolayout of View Controller View in a Container - objective-c

I have an NSWindow that has 2 container views within it, all in one xib, like so:
In another xib, I have a the view for the sidebar, managed by a different view controller, like so:
When I add the subview to the container view, I do it like this:
self.sidebarViewController.view.frame = self.sidebarContainer.bounds;
[self.sidebarContainer addSubview:self.sidebarViewController.view];
When I build and run, and resize this window, this is what happens:
The container tracks the height of it's superview properly, but the sidebar view itself does not track the height of the container.
How can I set things up so that the height of the sidebarVCs' view tracks the height of the container as the window is resized?
I think I've solved it like so:
[self.sidebarViewController.view setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *w = [NSLayoutConstraint constraintWithItem:self.sidebarViewController.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:150.0];
NSArray *c1 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|"
options:0
metrics:nil
views:#{ #"view":self.sidebarViewController.view }];
[self.sidebarContainer addConstraints:c1];
[self.sidebarContainer addConstraint:w];
I still don't yet understand why the autoresizemask layout constraint doesn't do this automatically, but I think I'm closer to understanding the relationships between the views here.

Use NSBoxes as containers. In your sidebar, add an NSBox inside the NSView and constrain all sides to the view. Then when you want to swap in a view for the side bar you can call container (the NSBox object) setContentView:.

Related

Using Xib's for a multi-device UI

I am using a xib file to create the view for a settings view controller. The user taps an object in the main view which animates a narrow view (preferably 35% the width of the screen) over from the left.
How do I set a relative width using xibs. Right now I am setting the simulated metrics - > size property to freeform and I only have the option to hard code a width in points.
Autolayout - make a width constraint for the view:
myView.translatesAutoresizingMaskIntoConstraints = NO;
[myView addConstraint: [NSLayoutConstraint constraintWithItem:myView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:.35]];

Autolayout NSScrollview does not grow contentView

I have a scrollview that I'm trying to setup with auto layout.
The actual content is in a container view, this container view a set of visible box views. They are added without any problem to the container view. Each one is given a couple of constraints
NSViewController prev;
for(NSViewController *lvc in vcs) {
//each view is padded by 5 from left
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:lvc.view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:prev.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:5]];
//Constant height
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:lvc.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:300]];
//Constant width
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:lvc.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:300]];
prev = lvc;
}
[_scrollView setDocumentView:containerView];
[_scrollView addConstraint:[NSLayoutConstraint constraintWithItem:listContainerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:_scrollView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
However the scroll view does not allow the container view to grow without growing the scrollview itself. The right edge of the containerView is attached to the right edge of the scroll view. If I grow the scroll view I can see there's more content from the list container view but only as big as the scroll view is.
The last row is my attempt to fix this by basically saying "let the container view grow larger than the scroll view". Isn't that the correct way of setting the constraint?
Add an NSStackView to your NIB and then select Editor > Embed In > Scroll View. Add constraints to the scroll view to keep it laid out relative to its superview and/or sibling views. Add constraints between the stack view and its superview (the clip view) to keep its top and leading edges the same as the clip view's. Add a constraint to keep the stack view's trailing edge greater than or equal to the clip view's trailing edge. Add a constraint to the keep the stack view's bottom edge greater than or equal to the clip view's bottom edge.
In your code, add the views of the view controllers in vcs as subviews of the stack view. The stack view's spacing will default to 8. In your code, you set spacing of 5. So, you can change the stack view's spacing to 5. (This can be done in the NIB.)

Resize NSView to its subview using auto layout

In Interface Builder I've added a NSScrollView with a NSImageView, some labels, a horizontal separator and a NSView. The NSScrollView looks like the following:
Every element has constraints from the top left, and both the separator and the custom view also have a constraints to the right.
Sometimes I need to change the content of the custom view. I do this with the following code (options is the custom view, view is the view I want to display):
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
options.subviews = [NSArray arrayWithObject:view];
// Fix the constraints.
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(view);
[options addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:nil views:viewsDictionary]];
[options addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:nil views:viewsDictionary]];
This works to some extent: the correct content is displayed in the view and the content's width is changed according to the constraints. However, the height of the options view doesn't change, so some content of the view view is not displayed. I tried to change this by manually setting the frame, but that doesn't work. Also, I tried the following to set a constraint:
[options addConstraint:[NSLayoutConstraint constraintWithItem:options attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
Nothing works though. Does anyone know how I can fix this?
A second problem I noticed is that even when the content of the NSScrollView is larger than the NSScrollView itself, scrolling doesn't work. No scrollbar is displayed. How can I fix that?
I still can't get my head around these harder problems with constraints...
I managed to solve my problem. It turned out I really had two problems:
I didn't set the correct constraints on the NSScrollView and its children;
I changed the content of the custom view, but didn't change its height.
My fix to both problems follows below.
NSScrollView constraints
After adding a NSScrollView in Interface Builder and placing some UI elements in the document view (the clip view's child), I had the following interface (the white part of the window is the NSScrollView, don't look at the constraints for now):
If you run this and resize the window, no scrollbars are displayed though. First of all we have to add top and leading constraints on the document view relative to its superview. Now we need to specify the size of the document view. This can be done in two ways, depending on your use-case:
Add constraints for the width and height of the document view. This way the same scrollbars are always displayed. The document view will then not change size depending on the content though, unless you automatically update these constraints;
Add constraints to every child of the document view, so that document view's width and height can be calculated. This was needed for my problem, and these constraints are displayed in the image above.
If you run this, everything works as expected. We still can't change the content of the custom view though, and that's a different problem.
Changing the custom view
Now we just need to change the content of the custom view. Since we set constraints on the custom view, these should also be changed. All of this is done with the following self-explanatory code (the custom view is named content):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[content setAutoresizesSubviews:NO];
}
- (IBAction)button1:(id)sender {
[self showContent:view1];
}
- (IBAction)button2:(id)sender {
[self showContent:view2];
}
- (void)showContent:(NSView *)c {
// Remove all constraints.
[content removeConstraints:content.constraints];
// Add a height constraint.
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:content attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:c.frame.size.height];
[content addConstraint:constraint];
// Add a width constraint.
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:content attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:c.frame.size.width];
[content addConstraint:constraint];
[c setTranslatesAutoresizingMaskIntoConstraints:NO];
// Set the content.
content.subviews = [NSArray arrayWithObject:c];
// Add constraints from the content view to its child view.
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(c);
[content addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[c]|" options:0 metrics:nil views:viewsDictionary]];
[content addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[c]|" options:0 metrics:nil views:viewsDictionary]];
}
I hope this answer saves someone else the trouble of finding out about the strange behavior with NSScrollView and autolayout.

How to add uiview and make it the self view center using autolayout and without setting frame or center property

How to add uiview and make it the self view center using autolayout and without setting frame or center property.
I know that we can set like this
view.center = window.center;
or
view.center = self.view.center;
but i want to set to the center of the view to self view center using autolayout.
You should be adding 4 constraints to your view then:
One for "center horizontally"
One for "center vertically"
One to set its height to a certain value
One to set its width to a certain value
You can create constraints in code like this, and add them to your view:
[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:tab
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
You can do this with storyboard you don't need to write any code. Use Size Inspector and remove all the line in Autosizing part.

Resizing a custom view with the window

So I've seen this question asked a couple times, but haven't come across an answer that solves my problem. Right now I basically have MainMenu.xib with a label centered on the top, a large custom view and a button centered on the bottom that switches subviews of the custom view (see picture below). I've set the window and the custom view to autoresize subviews in the interface builder and all the buttons, labels, etc. have constraints relating them to the sides of the view, but when I resize the window, the contents of the subview do not resize. I think the custom view is resizing with the window because when I switch subviews with the button, the subview that loads is resized with the window, they just aren't resizing with the window in real time.
MainMenu.xib:
Normal subview:
After window is expanded:
Subview switched and then switched back:
CONSTRAINTS:
MainMenu.xib:
BlockViewController.xib:
Figured it out! Really simple, just one line of code in the awakeFromNib method of each ViewController subclass:
[self.view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
From the pictures and the description in the comments it seems that the problem is that the custom view does not get any layout constraints to determine how to resize the subview.
The following piece of code (typed directly in the browser, so beware) should provide the necessary constraints to "glue" the borders of the subview to the custom view.
[self.customView addSubview: self.blockSubView.view];
self.blockSubView.view.frame = self.customView.bounds;
NSView *blockSubView = self.blockSubView.view;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(blockSubView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[blockSubView]|"
options:0
metrics:nil
views:viewsDictionary];
constraints = [constraints arrayByAddingObjectsFromArray: [NSLayoutConstraint constraintsWithVisualFormat:#"V|[blockSubView]|"
options:0
metrics:nil
views:viewsDictionary]];
[self.customView addConstraints: constraints];
There is more about this in the docs. Notice the tricks for debugging - very useful to the point of being indispensable.