I'am trying to make the inserted view to fill the container view in which it is inserted, however iam getting
Invalid parameter not satisfying: [constraint isKindOfClass:[NSLayoutConstraint class]]
- (void)insertedView:(NSView *)insertedView needsToFillContainerView:(NSView *)containerView {
[containerView addSubview:insertedView];
[containerView setTranslatesAutoresizingMaskIntoConstraints:NO];
[containerView addConstraints:#[
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[insertedView]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(insertedView)],
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[insertedView]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(insertedView)]
]
];
}
Basically the one view i want to insert is loaded from a VC
- (void)addSubview:(NSView *)insertedView fillingAndInsertedIntoView:(NSView *)containerView {
[containerView addSubview:insertedView];
[insertedView setTranslatesAutoresizingMaskIntoConstraints:NO];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[insertedView]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(insertedView)]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[insertedView]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(insertedView)]];
[containeView layoutIfNeeded];
}
constraintsWithVisualFormat returns an NSArray.
#[] is the Objective C literal to create an NSArray.
So, here your method parameter for addConstraints is an NSArray with two elements, each of which is an NSArray.
That's an incorrect method parameter for addConstraints. It expects an NSArray of objects of type NSLayoutConstraint.
Changing your invocation to be along the lines of this will solve the problem:
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat: etc.
Incidentally, I see so many posts about creating Auto Layout constraints in code. Interface Builder is far the superior method. As Erica Sadun so succinctly puts it in her book iOS Auto Layout Demystified:
Any views you lay out in Interface Builder are guaranteed to be satisfiable. You cannot create a wrong interface with inconsistent rules in IB. The same is not true in code.
There are two methods in UIView
//parameter type is NSArray
-(void)addConstraints:(NSArray *) constraints
and
// parameter type is NSLayoutConstraint
-(void)addConstraint:(NSLayoutConstraint *) constraint
So,
constraintsWithVisualFormat
will return a NSArray, you need use
addConstraints
Related
I have a scrollview that contains an image and a single textview. The content of the textview is dynamic it could be 200 in height or it could be 1500 in height. I am using storyboard so how do I setup both the textview and the scrollview content size to allow for dynamic content in the textview. I can set them both to arbitrarily high number but then I end up with a bunch empty space in the scrollview. I want both the image and the text to scrolling on and off the screen as required.
You would use constraints. Here is an example of how I did it with a scrollview. With a textview it is scrollable, and doesn't require you to set a content size, you simply put the content in it. If you want to control the viewable area of the uitextview, you will need to adjust it to what you see fit based on the amount of text you have, font and spacing you use.
NSDictionary *viewsDic = #{
#"image":image,
#"text": text,
#"lable":label,
#"someview":someview,
#"anotherview":anotherview};
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[image(320)]|"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[image]|"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-14-[label]"
#"[text(<=200)]"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-22-[txtDollarAmountFront(==37)]"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-19-[label]"
options:0
metrics:0
I'm trying to create a NSTableView inside a NSScrollView (the standard configuration, that is) in code, using auto layout. I can't figure out how to make this work.
Here's my loadView:
- (void)loadView
{
NSView *view = [[NSView alloc] init];
NSScrollView *tableScroll = [[NSScrollView alloc] init];
NSTableView *fileTable = [[NSTableView alloc] init];
[tableScroll setDocumentView:fileTable];
[tableScroll setHasVerticalScroller:YES];
[tableScroll setHasHorizontalScroller:NO];
fileTable.delegate = self;
fileTable.dataSource = self;
[fileTable setHeaderView:nil];
[fileTable setAllowsColumnReordering:NO];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:#"column1"];
[fileTable addTableColumn:column];
[tableScroll setTranslatesAutoresizingMaskIntoConstraints:NO];
[fileTable setTranslatesAutoresizingMaskIntoConstraints:NO];
[view addSubview:tableScroll];
NSDictionary *topViews = NSDictionaryOfVariableBindings(tableScroll);
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tableScroll]|" options:0 metrics:nil views:topViews]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[tableScroll]|" options:0 metrics:nil views:topViews]];
self.fileTable = fileTable;
self.view = view;
}
What happens is that my table view's frame will be always equal to the bounds of the NSClipView. The view is inside a window and gets resized with it, and when I do that it'll resize the scrollview, the clip view and the table, but I can never scroll anywhere.
Looking at constraints I get, the NSScrollView gets constraints that set the clip view to fill it, the clip view has no constraints at all and the table view has a bunch of constraints related to the NSTableRowViews inside it.
If I add a constraint like |[fileTable(>=500)] to the clip view I'll get 500 pixels of NSTableView, but obviously I don't want to do that.
Even though this was answered by the poster in the comments above, I thought I’d put the answer here (having run into the same issue). If you are adopting auto layout, you would typically uncheck “Translates Mask Into Constraints” in the xib. However, for classes like NSScrollView and NSTableView, you should generally let them manage their own internal views by setting their translatesAutoresizingMaskIntoConstraints property to YES. It is still ok to set constraints that are external to these views, i.e. to resize in relation to their superview.
If you set translatesAutoresizingMaskIntoConstraints to NO, then you will need to supply constraints for all of the internal views, which unless you specifically need custom behavior (almost never), you will not want to do. This was the specific problem above.
An obvious side effect of not setting this correctly is that a table (for example) will not properly scroll beyond what is visible in the view.
According to the Cocoa Auto Layout Guide, I can use a dash in the visual constraint format language to "denote the standard Aqua space:"
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[a]-[b]"
options:0
metrics:nil
views:viewDict]];
However, I can't seem to find an NSLayout... constant or method that allows me to do the same thing if I'm building a constraint without using the visual format language:
[self addConstraint:[NSLayoutConstraint constraintWithItem:a
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:b
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:<# ??? #>]];
Is there a constant (or another value or method) that I can use to define the Aqua space in such a situation?
I've found the "standard Aqua space" to be 8.0 between sibling views, and 20.0 between a view and its superview.
NSView* view = [NSView new] ;
NSLayoutConstraint* constraintWithStandardConstantBetweenSiblings = [NSLayoutConstraint constraintsWithVisualFormat:#"[view]-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view) ] [0] ;
CGFloat standardConstantBetweenSiblings = constraintWithStandardConstantBetweenSiblings.constant ; // 8.0
NSView* superview = [NSView new] ;
[superview addSubview:view] ;
NSLayoutConstraint* constraintWithStandardConstantBetweenSuperview = [NSLayoutConstraint constraintsWithVisualFormat:#"[view]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view) ] [0] ;
CGFloat standardConstantBetweenSuperview = constraintWithStandardConstantBetweenSuperview.constant ; // 20.0
For iOS users:
#import "NSLayoutConstraint+StandardOffsets.h"
#implementation NSLayoutConstraint (StandardOffsets)
+ (CGFloat)standardConstantBetweenSiblings
{
static CGFloat value;
if(!isnormal(value)) {
UIView *view = [UIView new] ;
NSLayoutConstraint* constraintWithStandardConstantBetweenSiblings = [NSLayoutConstraint constraintsWithVisualFormat:#"[view]-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view) ] [0] ;
value = constraintWithStandardConstantBetweenSiblings.constant ; // 8.0
}
return value;
}
+ (CGFloat)standardConstantBetweenSuperview
{
static CGFloat value;
if(!isnormal(value)) {
UIView *view = [UIView new] ;
UIView *superview = [UIView new] ;
[superview addSubview:view] ;
NSLayoutConstraint* constraintWithStandardConstantBetweenSuperview = [NSLayoutConstraint constraintsWithVisualFormat:#"[view]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view) ] [0] ;
value = constraintWithStandardConstantBetweenSuperview.constant ; // 20.0
}
return value;
}
#end
PS: I entered a bugreport that no constant is offered in the header files!
Based on John Sauer's answer, I wound up writing a couple methods on an NSLayoutConstraint category to find the constants:
+standardAquaSpaceConstraintFromItem:toItem: returns a single NSLayoutConstraint constructed using the visual format language; it asserts that the array of constraints generated from the format has exactly one item, then gets that item and gives it back.
+standardAquaSpaceFromItem:toItem: pulls the constant out of the constraint from the previous method and returns it as a CGFloat.
This way, I can either get the constant value myself if I need to do computations, or just get a single layout constraint with the right spacing (e.g. for assigning to a constraint #property or directly adding to my view).
I am trying to build a view that will have several different controls, but I want the view to only scroll in the vertical direction. I'm using auto-layout so I don't want to have to specify the size manually (I know I can do that, but from what I understand according to the release notes on UIScrollView you should not have to).
So if I create a simple view controller (with no .xib file) and simply add the following to the init method I'd expect that my labels would wrap (which they do) and that my view would scroll vertically, however, this is not the case:
- (id)init {
self = [super init];
if (self) {
UIScrollView *_scrollView = [UIScrollView new];
[_scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
UILabel *label1 = [UILabel new];
UILabel *label2 = [UILabel new];
[label1 setTranslatesAutoresizingMaskIntoConstraints:NO];
[label2 setTranslatesAutoresizingMaskIntoConstraints:NO];
[label1 setNumberOfLines:0];
[label2 setNumberOfLines:0];
[label1 setPreferredMaxLayoutWidth:240];
[label2 setPreferredMaxLayoutWidth:240];
NSString *sampleText = #"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
[label1 setText:sampleText];
[label2 setText:sampleText];
[self.view addSubview:_scrollView];
[_scrollView addSubview:label1];
[_scrollView addSubview:label2];
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[label1]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(label1)];
[_scrollView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[label2]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(label2)];
[_scrollView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[label1]-[label2]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(label1, label2)];
[_scrollView addConstraints:constraints];
}
return self;
}
There are several results from this code that I think are odd. If you log the scrollviewheight in the viewDidLayoutSubviews method (after the super call), which is where all constraints should be applied, then the height is reporting 0. What's also strange is that the width is reporting a different number than I would expect. I would expect it to report the width of the superview due to the very first constraint (I even tried forcing the width inside that constraint with a priority that is supposedly required but it still didn't work).
As I understand it, the UIScrollView's content size should be set by the intrinsic sizes of its content but that doesn't seem to be happening.
So, does anyone know what is missing?
I had contacted Apple's support to get an answer on my question. The problem with the code is that the objects contained within the UIScrollView must be pinned to both the top and bottom vertically for it to determine its intrinsic content size. So the following line of code
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[label1]-[label2]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(label1, label2)];
[_scrollView addConstraints:constraints];
Should be changed to:
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[label1]-[label2]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(label1, label2)];
[_scrollView addConstraints:constraints];
They also pointed out that I was using the "new" keyword on several objects where the basic "init" is not the designated initializer. While that had nothing to do with my specific problem they did point out that you should avoid doing that as your object could be in an invalid state.
I have the default NSWindow created in a new application which has a single NSView. I then create a new NSViewController which has it's own XIB and a view. In the app delegate I do the obvious
self.mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
[self.window.contentView addSubview:self.mainViewController.view];
self.mainViewController.view.frame = ((NSView*)self.window.contentView).bounds;
OK, how do I set a constraint in the new way to have my subview keep its size identical to the Window, i.e. it's superview. It doesn't seem to work automatically. Autoresizessubviews is ON for both views.
Basically, you need to constrain four things:
The leading space of your subview to its superview to be zero
The top space of your subview to its superview to be zero
The width of your subview to be equal to its superview's width
The height of your subview to be equal to its superview's width
If the visual constraint isn't working out for you, you can build these four constraints individually in code. Use the method +constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:
constant: to specify exact relationships between different views' attributes. For example, constraint #1 above might be expressed by:
[NSLayoutConstraint constraintWithItem:mySubview
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:mySuperview
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.0f]
and #3 might be:
[NSLayoutConstraint constraintWithItem:mySubview
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:mySuperview
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]
Once you've built up those four constraints, you can add them to your superview as needed.
Note that there are multiple ways to achieve the same effect as above:
You might constrain the trailing space and bottom space instead of the width and height
You might constrain the center X and center Y instead of the leading and top spaces
You can also probably come up with the same constraints in a visual representation, as in Peter Hosey's answer. For example, an equal-width constraint might look like #"[mySubview(==mySuperview)]" with the appropriate views dictionary.
Keep in mind that the Auto Layout Guide is a wealth of information about constraints, including how to debug them when things go wrong.
In the nib editor, drag the subview's size until it is the same size as its superview. Xcode will create an appropriate width constraint automatically.
In code, I would try |-0-[mySubview]-0-| (adapted from the example in the constraint syntax documentation).
Just like Peter wrote, you should use the visual format language.
When doing this, however, order is important: when you create a constraint, all views it references have to be part of the same view hierarchy.
So given your example, the code would have to become:
self.mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewContoller" bundle:nil];
NSView *containerView = self.window.contentView;
NSView *contentView = self.mainViewController.view;
[contentView setTranslatesAutoresizingMaskIntoConstraints: NO];
[containerView addSubview:contentView];
NSDictionary *viewBindings = NSDictionaryOfVariableBindings(contentView);
[containerView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView]|" options:0 metrics:nil views:viewBindings]];
[containerView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|" options:0 metrics:nil views:viewBindings]];
You can override setContentView:, contentView:, and contentRectForFrameRect: so they will deal with window.frame - sized view.
If you're ok with using a 3rd party library, you can accomplish this with ReactiveCocoaLayout in one simple line:
RAC(view,rcl_frame) = parentView.rcl_frameSignal;
I had the same problem and I ended up with this solution which is working with SDK 10.10. Just set the autoresizingMask of the new view to be the same as the parent window. Only one row of code and it works like a charm...
self.masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
[self.window.contentView addSubview:self.masterViewController.view];
self.masterViewController.view.frame = ((NSView *)self.window.contentView).bounds;
self.masterViewController.view.autoresizingMask = ((NSView *)self.window.contentView).autoresizingMask;