Why are my NSLayoutConstraints not added? - interface-builder

I have a view with layout constraints that I defined via the Interface Builder. Since they can not be temporarily deactivated, I decided to remove them selectively by just calling:
[view removeConstraint:myConstraint];
However, afterwards the constraint still resides inside view.constraints. Furthermore, I would then also like to add constraints programmatically (again, because I cannot (de-)activate them):
- (NSLayoutConstraint*) addHeightConstraint:(int)height forView:(UIView*)view {
NSString* v = [NSString stringWithFormat:#"V:[view(==%i)]", height];
NSArray* cs = [NSLayoutConstraint constraintsWithVisualFormat:v options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)];
NSLayoutConstraint* c = [cs objectAtIndex:0];
c.priority = 1000;
BOOL wasAdded = [view.constraints containsObject:c];
return c;
}
Any call of my method results in the value NO for the variable wasAdded. This is also reflected in the user interface which does not change, at all.
Concluding, I can neither add constraints programmatically nor remove constraints that were added to the storyboard. What am I doing wrong?

It seems you are not adding the new constraint to the view, therefore, it will not be in the array.
You need to call
[view addConstraint:c];
Some of the constraints are also kept in view.superview.constraints. So if you want to remove the constraints, you could try calling
[view.superview removeConstraints:view.constraints];
Also, you will need to call (if you did not do it already)
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
If you need to remove the constraints from the storyboard, you could also consider creating your view in code, if you don't have any special setup in the storyboard. Then you would not need to worry about removing and adding constraints.
Let me know if this works for you or you need more help!

Related

Autolayout constraint that uses UIScrollview contentSize

I have a UIView inside a UIScrollView. I can easily pin the height to the UIScrollView's frame height.
How do I add a constraint that pins to the UIScrollView's contentSize instead?
Thanks!
UIScrollView have dynamic constraints, left, top, width and height are generated at runtime. If you put a UIView inside a UIScrollview and Pin fixed constraints in Interface Builder it will generate an error because the parameters are relative to Superview/Container View.
You can try some workarounds:
1- Add UIView constraints Programmatically
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
2- Manually resize your view bounds in initWithFrame function inside a UIView Subclass
Please give me any feedback about your progress.
The answer "Adding constraints programatically" is correct but it was a little light on detail for me to accept it as the full answer.
Here's how I did it!
Remove all storyboard constraints on the WebView
Add a Placeholder constraint in storyboard for the constraint that you will add with code. This step is very important (and easily missed) or you will get an error about conflicting constraints.
Add code to webviewDidFinishLoad delegate method
--Code--
- (void)webViewDidFinishLoad:(UIWebView *)webView {
_scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, _headerImageView.frame.size.height + webView.scrollView.contentSize.height);
_webView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = #{#"myWebView":_webView};
NSString *constraintsString = [NSString stringWithFormat:#"V:[myWebView(%i)]", (int)_scrollView.contentSize.height];
NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:constraintsString options:0 metrics:nil views:viewsDictionary];
[_webView addConstraints:constraint_H];
}

NSTableView's frame inside a NSClipView/NSScrollView using auto layout

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.

In OSX 10.8 how do I constrain a subview to be the same size as its parent view

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;

Best way to make a wizard

I need to make a wizard with multiple interactive pages that gathers data from the user. Making so many pages is a tough work by making every single window. Is there any simple class or command to manage it?
There's a couple approaches you could take to do this. First off you could use a UINavigationController which allows you to easily move between multiple view controllers. This is probably the best option if you are okay with using multiple view controllers.
To push to the next controller in a navigation stack you can use:
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"someID"];
[self.navigationController pushViewController:controller animated:YES];
UIScrollView is also an option but would require careful manual memory management when items moved on and off screen, however this could be done all in one class.
[arrayOfViews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx,BOOL *stop) {
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(320 * idx, 0, 320, 480)];
float randNum = arc4random() % 255;
[subView setBackgroundColor:[UIColor colorWithRed:randNum/255.0 green:randNum/255.0 blue:randNum/255.0 alpha:1.0]];
[myScrollView addSubview:subView];
[myScrollView setContentSize:CGSizeMake(320 * (idx + 1), 480)];
}];
Then your final and most flexible option would be to just make subviews of your main view and you could make your own custom animations for how every item moves around on screen.
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[someSubview setTransform:CGAffineTransformConcat(CGAffineTransformMakeScale(0.5, 0.5), CGAffineTransformMakeTranslation(-300, -300))];
}completion:^(BOOL done){
//some completion items
}];
Expanding on H2CO3's comment, you'll want to probably use a UINavigationController, assuming the users are allowed to go backwards at will. Then, to go forwards, you'll just push a new UIViewController onto the stack.
Alternatively, you can check out storyboards, which let you define the whole thing, in a row, with transitions using IB. However, those end up being embedded in a UINavigationController anyway.

Can't get subview animation to appear even after alloc :)

I have a subview loaded into an UIView. In the subview's .m file I have the following:
- (void)startAnimation {
// Array to hold png images
imageArray = [[NSMutableArray alloc] initWithCapacity:22];
animatedImages = [[UIImageView alloc] initWithImage:viewForImage];
// Build array of images, cycling through image names
for (int i = 1; i < 22; i++){
[imageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"image%d.png", i]]];
}
animatedImages.animationImages = [NSArray arrayWithArray:imageArray];
// One cycle through all the images takes 1 seconds
animatedImages.animationDuration = 2.0;
// Repeat forever
animatedImages.animationRepeatCount = 0;
// Add subview and make window visible
[viewForMovie addSubview:animatedImages];
// Start it up
animatedImages.startAnimating;
NSLog(#"Executed");
}
Please be noted that I have in the .h file:
UIImageView *animatedImages;
NSMutableArray *imageArray;
UIView *viewForMovie;
#property(nonatomic,retain)IBOutlet UIView *viewForMovie;
and in the .m file:
#synthesize viewForMovie;
and I have connected viewForMovie to a UIView in IB. I've been on this for several hours now and have tried many variations I've found on the web but cannot get it to work. There are no errors and the other GUI graphics in the subview appear very nicely....but the animation just doesn't appear over top where it should. Also the NSlog reports that the method has in fact been called from the parent. Can anyone see any blaring issues? Thx.
PS: I'm pretty new at this.
Based on the code shown and the behavior you see so far, here are my suggestions:
Make sure the viewForMovie IBOutlet is connected properly in Interface Builder. If it's not connected properly (and so nil), nothing will appear. If you didn't mean to make it an IBOutlet in the first place, then you'll need to manually create it and add it as a subview to self before using it.
Not sure why you have the viewForMovie UIView in the first place. Is this subview's class (let's call it MySubview) a subclass of UIView? You can just show the animation in self instead of adding another subview inside it. Are you going to add more uiviews to this subview besides the viewForMovie?
To get rid of the "may not respond to" warning, declare the startAnimation method in the MySubview.h file (under the #property line):
-(void)startAnimation;
The fact that the warning says "UIView may not respond" also tells you that the parent view has declared newView as a UIView instead of MySubview (or whatever you've named the subview class). Change the declaration in the parent from UIView *newView; to MySubview *newView;.
In the initWithImage, what is "viewForImage"? Is it a UIImage variable or something else?
If all of the images are the same size and fit in the subview as-is, you don't need to set the frame--the initWithImage will automatically size the UIImageView using the init-image dimensions.
Double check that the images you are referencing in the for-loop are named exactly as they are in the code and that they have actually been added to the project.
Finally, you should release the objects you alloc in startAnimation. At the end of the method, add:
[imageArray release];
[animatedImages release];
The only item, however, that I think is actually preventing the animation from appearing right now is item 1.

Resources