Add subview that's docked to the bottom of superview (custom keyboard) - ios

I'm attempting to create a custom view that appears exactly like the keyboard, but I can't figure out how to use frame manipulation and/or programmatic auto layout to solve my problem.
Some context: I have a messaging app style view controller with a textview and button docked at the bottom of the screen. All views are wrapped into a nice single content view with autolayout set such that when the keyboard appears, it pushes the entire view up, and when it disappears it pushes the entire view back down. This is the behavior I'm trying to reproduce.
I messed around with trying to manually resize frames similar to my keyboard code but ended up throwing that away in favor of a auto-layout based solution. Here's what I have so far:
StickersCollectionViewController *stickerController = [self.storyboard instantiateViewControllerWithIdentifier:#"StickersCollectionViewController"];
[self addChildViewController:stickerController];
[self.view addSubview:stickerController.view];
[stickerController didMoveToParentViewController:self];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[self.view addConstraint:constraint];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:stickerController.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:240.0];
[self.view addConstraint:width];
[self.view addConstraint:height];
double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
constraint.constant = 240.0;
[UIView animateWithDuration:0.3
animations:^{
[self.view layoutIfNeeded];
}];
});
So far, this looks great: the view is instantiated and added just off screen, then animates into view. However, I'd also like my superview (mentioned above) to also animate with this view. This is the piece I need help with.
Can anyone offer help in this direction? Or offer a suggestion as to a different route I could go? Thanks.

Try this instead, the difference is using "[self.view layoutIfNeeded];" both before the animation and after the animation, and then placing this "constraint.constant = 240.0;" in the animation.
double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3
animations:^{
constraint.constant = 240.0;
[self.view layoutIfNeeded];
}];
});
So, try this as well:
this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
shoud probably be this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
because you are constraining to the top of the view and not constraining to the top of the view + self.view.bounds.size.height, not sure if this helps, but this is one idea.
A second possible solution is this, wrap up all UI elements into a UIView and set this UIView as the view of stickerController.view, this isn't so straight forward because technically you should assign this view as the view of stickerController.view in a load view method in the stickerController impelemntation file and then type cast the stickerController.view as this for example
Typcasting inside implementation file of stickerController
- (void)loadView
{
[self setView:[ContainerViewThatHasSubViews new]];
}
- (ContainerViewThatHasSubViews*)contentView
{
return (id)[self view];
}
then, create a subclass of ContainerViewThatHasSubViews like so
ContainerViewThatHasSubViews.h
#interface ContainerViewThatHasSubViews : UIView
#property (nonatomic) UIView *subContainerOfContainerViewThatHasSubViews;
#property (nonatomic) NSLayoutConstraint *tester;
#end
ContainerViewThatHasSubViews.m
#interface ContainerViewThatHasSubViews ()
#end
#implementation ContainerViewThatHasSubViews {
}
self = [super initWithFrame:frame];
if (self) {
_subContainerOfContainerViewThatHasSubViews = [UIView new];
[_subContainerOfContainerViewThatHasSubViews setTranslatesAutoresizingMaskIntoConstraints:false];
[self addSubview:subContainerOfContainerViewThatHasSubViews];
/// PSUEDO CODE HERE NOW
ALL OTHER UI ELEMENTS ARE ADDED TO THE UIVIEW "subContainerOfContainerViewThatHasSubViews"
like this *** [subContainerOfContainerViewThatHasSubViews addSubView:**another ui element***];***
etc. etc. etc.
then use layout constraints like this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[subContainerOfContainerViewThatHasSubViews.view addConstraint:constraint];
etc. etc., so the layout constraints are added to "subContainerOfContainerViewThatHasSubViews"
add separate constraints to this UIView for the items inside this view, and you can then animate these cosntraitns as well by declaring the constraints in your header file as propertyies like i did with the _tester constraint, you can animate these when you press a button in your UIViewController impelmentation file by addding a gesture recognizer or whatever you have to show the keyboard
then end with this:
NSDictionary* views = NSDictionaryOfVariableBindings(subContainerOfContainerViewThatHasSubViews);
NSDictionary* metrics = #{#"sp" : #(heightResizer(10)), #"sh" : #40}; //include sizing metrics here
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_subContainerOfContainerViewThatHasSubViews]|" options:0 metrics:metrics views:views]];
and this:
_tester =[NSLayoutConstraint constraintWithItem:_subContainerOfContainerViewThatHasSubViews attribute:NSLayoutAttributeCenterTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterTop multiplier:1.0f constant:0.0f];
[self addConstraint:_tester];
}
return self;
}
#end
Gist:
https://gist.github.com/anonymous/c601481d24ad1b98b219
https://gist.github.com/anonymous/b22b68d4bf8d7fa51d66
https://gist.github.com/anonymous/a9aaf922e0f5383256b6
https://gist.github.com/anonymous/fc6655ea8200cda9c0dd
There's a lot to this one, but it's just how programatic views work. I've never use storyboard because I feel like I have more control doing everything programatically, if you can handle how much code you'll need to write to make this happen then you are good to go, I know this because I'm building an app just like you that does the same thing that you want yours to do, and this works for me because I'm digging deep into the views. This is complete control, just make sure you make your other NSLayoutContraints as properities of the subclassed UIView, and then you can place methods in your subclassed UIView's header file that point to he implementation file of the UIView subclass and these methods can be the animation methods that you can call from your view controller like this:
[[self contentView] CALLYOURFUNCTION];
to access the other layout constraints and ui elements from the subclassed UIView in your View controller do this to call them:
[[self contentView] tester]; //this is the constraint from9 the header file of tbhe custom UIView
to set the constant in your UIViewController do this:
[[[self contentView] tester] setConstant:A_NUMBER];
The point is this, you are encapsulating all your views into a UIView subclass rather than accessing the view of your view controller itself and trying to animate it's view. I've not ever seen someone try to animate the "view" property of a UIViewController itself, I've only seen the animation of a UIView that is a subview of the UIViewController's view. The Point also is that you are forcing this UIView to be the view of the UIViewControllers view and then you are using another UIView within the subClassed UIView to animate the entire Contents of the view, so it's like this:
-UIViewController's view
Sub Class of UIView
UIView <=== this is the view that contains ALL THE OTHER UIElements of the View controller
Add a constraint to the UIView that contains all other UI elements, this contraint is the tester constraint I added to the header of the custom Sub class UIView.
You can then add additional constraints to the subViews of the the UIView that I've mentioned which contains all other UI elements of the UIViewController
When you add constraints on these sub items, you can animate them as well. This method is advanced so ask questions if you have them, my answer is a little messy.

daddy warbucks answer did indeed get me close to the solution I was looking for. For those of you who have built most of your UI skeleton with the storyboard, I ended up adding a container view to the bottom of my view controller and modifying the center values of the main content view and container view on button press.
Layout content view as you normally would. Set Leading, Trailing, and Top constraints to the content view, and a Bottom constraint to the container view (described in step 2).
Create a container view and dock it below the content view. Set equal width to the content view, Top constraint to the Bottom of the content view, and a height of 240 (or whatever you wish). Note: the view will be positioned OFF the view controller. If you're slightly OCD like me, this will bother you for a little bit until you realize that it's k.
Here's what I used to move the container view into place and have the content view follow with it. Note that I probably will clean this up more, but this is the general idea:
if (_isStickersViewOpen) {
self.tableView.contentInset = UIEdgeInsetsZero;
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.center.y);
self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height + self.containerView.bounds.size.height / 2);
}
completion:^(BOOL finished){
_isStickersViewOpen = NO;
}];
} else {
UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.containerView.frame.size.height, 0.0, 0.0, 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height / 2 - self.containerView.bounds.size.height);
self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height - self.containerView.bounds.size.height / 2);
}
completion:^(BOOL finished){
_isStickersViewOpen = YES;
}];
}
I'm not claiming this is the most effective or clean solution available, but it works for my purpose.

Related

Resizing UIViews on click of a button using Autolayout

I am new to Auto layout constraints. I have 2 views(topView and paintView) on my main view, along with a button on the top right corner of the main view. On loading the view, the topView occupies the whole main view(excluding the button). On click of the button, I want the topView to occupy 70% of the main view and the paintView to occupy the rest(excluding the button).
I have set up the the X, Y and top constraints for the topView using storyboard. The paintView and the corresponding constraints have been set up programmatically.
The code I have now is this:
-(void)setupPaintView
{
UIView *pPaintView = [UIView new];
[pPaintView setBackgroundColor:[UIColor yellowColor]];
pPaintView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:pPaintView];
self.paintView = pPaintView;
[self addConstraintsToView];
//[self setTopViewFrame];
}
-(void)addConstraintsToView
{
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.paintView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.topView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.topView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.paintView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
NSLayoutConstraint *pHeightConstraintTopView = [NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0];
self.heightconstraintTopView = pHeightConstraintTopView;
[self.view addConstraint:pHeightConstraintTopView];
NSLayoutConstraint *pHeightConstraintPaintView = [NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.0
constant:0.0];
self.heightconstraintPaintView = pHeightConstraintPaintView;
[self.view addConstraint:pHeightConstraintPaintView];
}
On button click the following method gets called:
-(IBAction)detailBtnClick:(id)sender
{
if(self.heightconstraintPaintView.constant == 0)
{
self.heightconstraintTopView.constant = 0.7*self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.frame.size.height;
[self.view setNeedsUpdateConstraints];
}
else
{
self.heightconstraintTopView.constant = self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0;
[self.view setNeedsUpdateConstraints];
}
}
When the view loads, the topView acquires the main view's height, which is desired here. But when I click on the button, the topView remains at 100% i.e. it does not resize and neither does the paintView. I am modifying the constant property of the topView and the paintView constraints, but I am not sure that is the correct way to go about it. The constraint here is that the views have to be laid out using Autolayout constraints only. How can I get the views to resize at the click of the button?
Any help is welcome.
Thanks to timothykc and others, I have successfully navigated the problem stated above. But I am facing another issue now.When I change the orientation of the simulator to landscape, the paintView remains almost hidden. Following is the code (toggle is a boolean value that decides whether to stretch/shrink the views):
-(IBAction)detailBtnClick:(id)sender
{
if(self.toggle == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.bounds.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.bounds.frame.size.height;
//[self.view layoutIfNeeded];
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
//[self.view layoutIfNeeded];
}
self.toggle = !self.toggle;
}
The topViewHeightConstraint has been added as a property as indicated by timothykc. This is working properly for the portrait orientation, but is not working properly for landscape, as the height of the topView does not change as desired(70%), meaning that the ratios are not getting handled properly.
I'm going to provide a storyboard driven solution that should help you with other autolayout problems down the road.
My solution to your specific problem, you've got two views (1 and 2 in diagram below):
For view 1, pin the view to the left, top, and right of the superview. Then set a height constant. (e.g. 568, the full height of an iphone 5s)
For view 2, pin it to the left, bottom, and right of the superview. Then pin it to the bottom of view 1.
Open up the assistant editor view, and here's the key trick--turn the height constraint on view 1 into a nslayoutconstraint property on your VC. You do this by locating the constraint, and then control-dragging onto the VC. (e.g.`
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewHeight;`
Now you can manipulate this property with an action linked to your button, such as
- (IBAction)scale:(id)sender {
self.viewHeight.constant = 397.6; //%70 of 568....
}
In my example, I change the nslayoutconstraint.CONSTANT manually to an arbitrary value.
To understand what's happening, you need to know that autolayout is a means for determining the (x coord,y coord,width, height) of any layout object. Warnings occur when xcode cannot ascertain all 4 values...
In View 1, we give a constraint for Height. X,Y, and Width are extrapolated from the distance to the superview. (if something is 0 from the left and right, then the width fills the whole screen...; if 0 from top and left, then coords must be (0,0))
In view 2, X must be 0 since distance from left is 0. width whole screen... Height and Y are extrapolated based on the height of View 1!
So when we mess with height constraint in View 1, it effects the height and Y coord of View 2!
To get constraints to update on a view you would need to call [self.view layoutIfNeeded]; instead of [self.view setNeedsUpdateConstraints]; after setting the new constant on whichever constraint(s) you would like to update.
Actually this is more of an comment about my methods, but I decided to post it as an answer because firstly, this has solved my problem and secondly, it involves some snippets of code which is hard to read in the comments section. Regarding the orientation problem mentioned in the edit, I came up with a workaround to accommodate the view reszing requirements with respect to the toggle button and with respect to orientation change. The three methods used for this purpose are:
The following method is called on the button click event.
-(IBAction)detailBtnClick:(id)sender
{
[self updateViewConstraints:self.toggle];
self.toggle = !self.toggle;
}
The following method updates the constraints.
-(void)updateViewConstraints :(BOOL)toggleValue
{
if(toggleValue == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.bounds.size.height;
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
}
}
The following method calls the method above to update constraints in case of orientation change:
-(void)viewWillLayoutSubviews
{
[self updateViewConstraints:!self.toggle];
}

Positioning a view on top of a tableview using Auto Layout

I have a tableview in my storyboard that has its class set to my UITableView subclass which is named SPSExplanationTableView. There are no constraints set on this tableview in Interface Builder.
I am trying to programmatically create a UIView that displays in front of the tableview—which I know how to do (blog post link)—but that is sized and positioned using Auto Layout. This is my code:
#import "SPSExplanationTableView.h"
#interface SPSExplanationTableView()
#property (nonatomic, strong) UIView *explanationView;
#end
#implementation SPSExplanationTableView
- (void)awakeFromNib
{
[super awakeFromNib];
self.explanationView = [[UIView alloc] init];
self.explanationView.translatesAutoresizingMaskIntoConstraints = NO;
self.explanationView.backgroundColor = [UIColor blueColor];
[self addSubview:self.explanationView];
[self bringSubviewToFront:self.explanationView];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f constant:150.0f];
[self.explanationView addConstraint:heightConstraint];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f constant:200.0f];
[self.explanationView addConstraint:widthConstraint];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0f constant:0.0f];
[self addConstraint:topConstraint];
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self.explanationView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0.0f];
[self addConstraint:leftConstraint];
#end
When I run the app it crashes with the following assertion failure:
*** Assertion failure in -[SPSExplanationTableView layoutSublayersOfLayer:],
/SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still
required after executing -layoutSubviews. SPSExplanationTableView's
implementation of -layoutSubviews needs to call super.'
Taking the message literally and overriding layoutSubviews has no effect i.e. I still get the same crash.
- (void)layoutSubviews
{
[super layoutSubviews];
}
What's the correct way to implement what I'm trying to achieve?
For Aubada Taljo, here's the tableview in Interface Builder:
Update: I solved this myself in the end by not using Auto Layout! I overrode the layoutSubviews method in my SPSExplanationTableView class and set the center property of the explanationView to the centre of self's bounds, with some slight adjustments to the y-axis position to make it look how I wanted it.
This is crashing because UITableViews are not designed to do this. A UITableView is only concerned about its cells, the headers, maybe its background, and it has logic for this that doesn't use autolayout. So it will crash if you try to involve it in any constraints calculation between it and any subviews, e.g. this will also crash if you add a constraint between the cell and the table.
What I suggest you do is to add a superview that will contain both your table and the view that you want to overlay:
SuperviewWithConstraints
|
|-- YourTableViewWithConstraintsRelativeToSuperview
|
|-- YourOverlayWithConstraintsRelativeToSuperview
And set up the constraints there. Then make that superview as your view controller's view. You will have to move away from using the UITableViewController as the controlling class, though.
If you would take my advice and make your life easier, simply go to your storyboard in Interface Builder and set the correct constraints on the your table view, now go back to the code editor and in the UIViewController that owns your table view, write the view creation code in viewDidLoad e.g.
UIView* someView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, self.tableView.frame.size.height)];
[self.view addSubView someView];
I guess this should be more than enough to solve your issue, now if you face any problems, move your code to viewDidLayoutSubViews in the UIViewController
Please tell me if you need more details but I use this way of creating dynamic controls all the time.

Building custom split view using container views

I am trying to build a custom split view controller for iPad using 2 container views that embed UINavigationControllers...
I managed to do all that but I am having problems with setting up constraints so that everything resizes nicely.
This is what I have so far:
I wont to be able to resize master view (left container) in a way that detail view (right container) resizes itself also to consume the space that master view left while resizing it self. So I would like to end up like this:
among other tries I tried adding these constraints:
master container: top to parent, left to parent, bottom to tabbar, height, placeholder width
detail container: top to parent, left to master container, bottom to tabbar, right to parent, height, placeholder width
When I resize master container from code, the detail controller always stays at its original place.
I tried 100 scenarios and nothing worked yet.
I am doing something fundamentally wrong with auto layout but just cant figure out what..
This is the resize code:
-(void)ShowMaster:(BOOL)bShow animated:(BOOL)bAnimated
{
CGRect frame = masterViewContainer.frame;
if(bShow){
frame.size.width = 280;
}
else{
frame.size.width = 50;
}
masterViewContainer.frame = frame;
}
Any kind of help would be much appreciated.
EDIT:
OK, I made some progress, replaced my resize code with this one:
-(void)ShowMaster:(BOOL)bShow animated:(BOOL)bAnimated
{
[UIView beginAnimations:#"" context:nil];
if(bShow){
[masterViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:masterViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:masterViewContainer
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:280]];
}
else{
[masterViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:masterViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:masterViewContainer
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:50]];
}
[UIView commitAnimations];
}
Now detail resizes, but when I try to expand master, nothing happens...
try this:
-(void)viewDidLayoutSubviews {
//your code
}
I resolved it. In my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
if(masterWidthConstraint == nil){
masterWidthConstraint = [NSLayoutConstraint constraintWithItem:masterViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:masterViewContainer
attribute:NSLayoutAttributeWidth
multiplier:0.0
constant:280];
[masterViewContainer addConstraint:masterWidthConstraint];
}
}
In my ShowMaster method i just update that constraint and animate like this:
-(void)ShowMaster:(BOOL)bShow animated:(BOOL)bAnimated
{
[UIView beginAnimations:#"" context:nil];
if(bShow){
masterWidthConstraint.constant = 280;
}
else{
masterWidthConstraint.constant = 50;
}
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.9 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished){}];
}
And in my IB i set placeholder constraint form master view width... And it works. Only thing that I couldn't do is make a IBOutlet to IB width constraint, and couldn't figure out why, but it works like this just fine so...

Adding View Programatically With Auto Layout Gives 'NSGenericException', reason: 'Unable to install constraint on view

I am adding a view as a subview using [self.view addSubview:myView]. This works fine in portrait mode. However, it doesn't work at all in landscape. How do I add layout constraints programatically?
My view currently looks like portrait rectangle and I need it to look like landscape rectangle in landscape mode.
I tried this code to see how constraints in code work but it always results in an exception. The code is:
[self.view addSubview:_preView];
NSLayoutConstraint *myConstraint = [NSLayoutConstraint
constraintWithItem:_preView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view.superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-239];
[_preView addConstraint:myConstraint];
This always results in an exception. I know the above code just attempts to ensure that the bottom of preview is 239px above the bottom of main view. But that doesn't work either.
Could you help me out with sorting this so that I can resolve the landscape issue?
UPDATE
The exception generated is:
2013-08-05 16:13:28.889 Sample Code[33553:c07] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal. constraint:<NSLayoutConstraint:0x912c430 UIView:0x8561340.bottom == UILayoutContainerView:0x8257340.bottom - 20> view:<UIView: 0x85774e0; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; autoresizesSubviews = NO; layer = <CALayer: 0x8577490>>'
*** First throw call stack:
(0x1a04012 0x173be7e 0x1a03deb 0x12ee4a0 0xbb983e 0xbb9a27 0xbb9b76 0xbb9d3b 0xbb9c4d 0x1c0d9 0x11395b3 0x19c3376 0x19c2e06 0x19aaa82 0x19a9f44 0x19a9e1b 0x24027e3 0x2402668 0x67fffc 0x2d3d 0x2c65)
libc++abi.dylib: terminate called throwing an exception
(lldb)
I have added the subview before adding in the constraint so I am pretty sure the view is in hierarchy.
UPDATE 2
I set the parent view's property to `Autoresize Subviews' in IB. The subview now converts into landscape rectangle when the device is turned but its too narrow. I now need the code to make sure its of correct width maybe?
A couple of observations:
Your constraint references a toItem of self.view.superview. I assume you meant self.view.
You're adding the constraint to _preView, but you should add it to self.view (if you make the above change; if not, you'd use self.view.superview). You always add the constraint to the nearest shared parent.
For the views you're creating programmatically, make sure to set translatesAutoresizingMaskIntoConstraints to NO.
Thus:
_preView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_preView];
NSLayoutConstraint *myConstraint = [NSLayoutConstraint constraintWithItem:_preView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-239];
[self.view addConstraint:myConstraint];
Chatting to you offline, two final observations:
Your constraints were ambiguous. In the future, you can identify that by running the app in your debugger, hitting the pause button while the app is running () and then at the (lldb) prompt, you can enter
po [[UIWindow keyWindow] _autolayoutTrace]
If you see AMBIGUOUS LAYOUT, then your constraints are not fully qualified (and thus you'll get unpredictable behavior). If you add the missing constraints, you should be able to eliminate this warning.
If you want to animate constraint based views, you animate the changing of constant properties of the constraints, not by changing frame properties yourself. For example:
// create subview
UIView *subview = [[UIView alloc] init];
subview.backgroundColor = [UIColor lightGrayColor];
subview.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:subview];
// create dictionary for VFL commands
NSDictionary *views = #{#"subview" : subview, #"superview" : self.view};
// add horizontal constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subview]|" options:0 metrics:nil views:views]];
// set the height of the offscreen subview to be the same as its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[subview(==superview)]" options:0 metrics:nil views:views]];
// set the location of the subview to be just off screen below the current view
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[self.view addConstraint:constraint];
// then in two seconds, animate this subview back on-screen (i.e. change the top constraint `constant` to zero)
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
constraint.constant = 0.0;
[UIView animateWithDuration:1.0
animations:^{
[self.view layoutIfNeeded];
}];
});
From your code above, there are 2 issues.
1. The constraint should be added to the parentview (self.view or self.view.superview as appropriate).
2. The items which are part of the myConstraint should be present in the view hierarchy to which you add your constraints.
My suggestion would be to check if your myConstraint can be formed with _preView and self.view , add the _preView to self.view as a subview and then add the myConstraint to self.view.
Also, the constraints should ideally be placed in -(void)updateConstraints method in your view (if you have a custom view) and you should call [self setNeedsUpdateConstraints]; in your view whenever you want the updateConstraints to be called on your view (after initializing your view, after rotation etc). You won't be calling updateConstraints directly.
There are few things about Auto layouts. When ever you add layout constraints make sure it is not ambiguous. Ambiguous layout would result in undefined behaviour in your display. So good idea is to use IB which will never allow you to create a ambiguous layout, but you got to go through all the constraints to make sure they are valid.
If you want to do it programatically I would suggest you to use Visual language.
It will be helpful to go though these tips before using layout.

NSLayoutConstraint between Navigation Bar & ViewControllers View

Can we add a NSLayoutConstraint between self.navigationcontroller.navigationbar and a view inside the self.view. Here self is a UIViewController instance and _textField is a subview of self.view
What I need is that the UI should look alike irrespective whether the navigationBar is Translucent or not.
I've tried the following. But It does not work.
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:_textField
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:20];
[self.navigationcontroller.view addConstraint:cn];
Yes you can add a constraint between the Navigation Bar and a view. Your root view conroller added to the navigation controller contains topLayoutGuide. so adjust your code like this:
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:_textField
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self.rootViewController.topLayoutGuide attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:20];
[self.rootViewController.view addConstraint:cn];
notice that i'm not referencing the navigation controller at all but the rootViewController of the navigation Controller.
Also you can use bottomLayoutGuide to go above the TabBar the same way. (however if you need to do that you'll run into a bug in iOS frameworks with a workaround patch here: UIViews ending up beneath tab bar )
Check out the topLayoutGuide property on UIViewController.
There's an example in Apple's doc for `UIViewController' that goes like this...
topLayoutGuide
Indicates the highest vertical extent for your onscreen content, for use with Auto Layout constraints. (read-only)
#property(nonatomic, readonly, retain) id<UILayoutSupport> topLayoutGuide
And then...
As an example of how to programmatically use this property with Auto
Layout, say you want to position a control such that its top edge is
20 points below the top layout guide. This scenario applies to any of
the scenarios listed above. Use code similar to the following:
[button setTranslatesAutoresizingMaskIntoConstraints: NO];
id topGuide = myViewController.topLayoutGuide;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings (button, topGuide);
[myViewController.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat: #"V: [topGuide]-20-[button]"
options: 0
metrics: nil
views: viewsDictionary]
self.view layoutSubviews; // You must call this method here or the system raises an exception
];
Add the constraint between the top of the textField and the top of the parent view. The constant for the constraint can be set to the height of the status bar + height of the navigation bar.
Obviously, the following code snippet will only work if both the Status Bar and Navigation Bar are translucent and the view controller wants full screen layout. You can easily test for transparency and adjust accordingly, if necessary.
If you're using interface builder, you can also create an IBOutlet for the existing constraint and just set it's constant rather than creating a new constraint.
// Obtain the view rect of the status bar frame in either portrait or landscape
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
CGRect statusBarWindowRect = [self.view.window convertRect:statusBarFrame fromWindow: nil];
CGRect statusBarViewRect = [self.view convertRect:statusBarWindowRect fromView: nil];
// Add Status Bar and Navigation Bar heights together
CGFloat height = self.navigationController.navigationBar.frame.size.height +
statusBarViewRect.size.height;
// Create & Add Constraint
NSLayoutConstraint *constraint =
[NSLayoutConstraint constraintWithItem:self.fieldLabel
attribute:NSLayoutAttributeTop
relatedBy:0
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:height];
[self.view addConstraint:constraint];

Resources