How to set self view height using constraints? - ios

So I have been moving parts of the code to use constraints rather than the older way of doing things using frames. This is the current code that I need to move. I need to change the height of the view when I hide\unhide the tabbar below when the user presses a button.
CGRect newFrame = self.view.frame;
newFrame.size.height += 44
self.view.frame = newFrame;
self.view is the UIViewController. I want to set the height of the view shown in the picture. There is no way to add a height constraint to it (you can set it on the subviews though easily which i dont want)
I don't see a way of setting the self view height using constraints.
thanks
arpit

This short tutorial will be helpful for you.
You can do it using VFL (I personally prefer, see link above) or by this kind of code:
[yourView addConstraint:[NSLayoutConstraint constraintWithItem:yourView
attribute:NSLayoutAttributeHeight
relatedBy:0
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:requiredHeight]];
This piece of code will add a constraint on height for yourView specified by requiredHeight. Before start coding please take a look at intrinsicContentSize and updateConstraints methods.
When setting constraints please remember to firstly set (for a view you will set constraints) translatesAutoresizingMaskIntoConstraints flag to NO (it is set to YES by default). Also reading this should put some light on the topic.

Related

How do I animate constraint changes if constraints are created programmatically with VFL?

I have trouble on something, I would be grateful if you could give me some advice.
Basically, I created some constraints with Visual Format Language and makes my base more dynamic. I didn't touch my views on storyboard.
You can consider red area as a navigationBar.
In example view looks like;
My problem is; I want to hide my topView on some viewController.
First I created a property;
#property (strong, nonatomic) NSLayoutConstraint *constraintTabbarHeight;
But I realized I couldn't set my NSLayoutConstraint property with VFL. Because VFL is create an array of constraints.
So this is my first question;
Is there anyway to keep my VFL constraint with NSLayoutConstraint property?
Anyway, so I decided to create my height constraint with constraintWithItem method.
self.constraintTabbarHeight = [NSLayoutConstraint constraintWithItem:self.tabbarContainerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:95];
Second, I search on stackoverflow and I have found a method. I can call this method from any viewController I want.
- (void)updateTabbarConstraintWith:(int) heightValue{
self.constraintTabbarHeight.constant = heighValue;
[UIView animateWithDuration:0.3
animations:^{
[self.view layoutIfNeeded];
}];
}
If I set self.constraintTabbarHeight.constant = 0 ,everything is fine. My TopView (redArea) is disappearing but PlaceholderView is still on same position.
I thought [self.view layoutIfNeeded] will update my placeholder View and it fills my screen.
It looks like this;
How do I make it correct?
Thanks for your answers.
You need to create a constraint from your placeholderView.Top to your TopView.Bottom, otherwise, one will be able to move without impacting the other one

Correct way to programmatically update constraints when the device orientation changes?

I'm using storyboard and autolayout, and setting the constraints in IB as IBOutlet in the corresponding view controller. I'm reading several posts regarding how to update the constraints to be different in portrait and in landscape but I'm still not sure of how should I do this:
Should I set the new constraints in -viewWillTransitionToSize:withTransitionCoordinator: method, or in updateViewConstraints method?
When the new constraints are set, should I call [self.view setNeedsUpdateConstraints];, or layoutIfNeeded, or setNeedsLayout?
How should I update, for example, the constant of a certain constraint:
self.myConstraint.constant = 30.0
or doing:
[self.view removeConstraint:self.passwordViewHeight];
self.passwordViewHeight = [NSLayoutConstraint constraintWithItem:self.passwordView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:34.0];
[self.view addConstraint:self.passwordViewHeight];
Thanks in advance
Orientation change be detected using the method viewWillTransitionToSize. This method will be called when the device orientation is about to change.
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator (id<UIViewControllerTransitionCoordinator>)coordinator{
//change constraints
}
Alternatively, if you want to change the constraints after the orientation changes, use the coordinator object's animateAlongsideTransition method in the viewWillTransitionToSize method.
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
//change constraints
}];
}
The best way would be to change the constant instead of removing the constraint and recreating it again. Just adding or subtracting from the constant is much faster in the long run as well. What you would want to do is something like:
[self.view layoutIfNeeded];
self.myConstraint.constant = 30.0;
[self.view layoutIfNeeded];
It is recommended that you call layoutIfNeeded before and after you change the constant as well. This is because you may have some constraint that has not yet been done and will do so then, before you change more constraints.
The best idea will be use Size Classes in Interface Builder to achieve autorotation change.
https://www.codefellows.org/blog/size-classes-with-xcode-6-one-storyboard-for-all-sizes

AutoLayout issue with resizing subviews

I'm trying to figure out how to use Autolayout and I've founded a problem. I want to create a view in IB with size 200x200. This view, called them PieView, has two UIImageViews with frames (0, 0, 200, 200), for both of them.
My question is, how to override updateConstraints in code (I like visual format language), or in IB, that if I change size of my PieView (for example to 100), and subviews will changed too (0, 0, 100, 100).
And how can I change the size of PieView, I'm trying for width and for height
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:100.f];//kDiameter * sizeCoef];
If the subviews of the container view (PieView) are correctly positioned/sized/pinned relative to their container, all you need to do is update the width of the container view (PieView).
To change the view's size in code, you'll need to make sure you keep a reference (in a property, for example) to the constraint on the view's width. So if you added that constraint in Xcode, that means connecting an outlet for that constraint. Or if you added it in code (as you have written in your question), just assign the constraint to a property instead of a local variable.
Then, in updateConstraints, change the constant property of the constraint to the new width. Here's an example:
- (void)updateConstraints
{
// Probably want to wrap the below line with a check for when you should actually do this change,
// as updateConstraints may get called more than once (including when you aren't ready to change the width).
self.widthConstraint.constant = 100.0f;
}
Then just call setNeedsUpdateConstraints on the view when you're ready to change its width!
I'm not sure that you even need to re-configure the height or width constraints for the image views. If you want the image views to grow and shrink with their superview, then just pin the the sides of the images views to the superview in IB. You would not need to override updateConstraints.

Matching subview's width to it's superview using autolayout

Goal:
Have a UIWebView be the same width as it's superview, which is a UIScrollView, using autolayout constraints.
Code
NSLayoutConstraint *makeWidthTheSameAsScrollView =[NSLayoutConstraint
constraintWithItem:self.questionWebView
attribute:NSLayoutAttributeWidth
relatedBy:0
toItem:self.masterScrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0];
[self.view addConstraint:makeWidthTheSameAsScrollView];
NSLog(#"The width of questionWebView *AFTER* adding the constrain is: %f", self.questionWebView.frame.size.width);
NSLog(#"The width of scrollView *AFTER* adding the constrain is: %f", self.masterScrollView.frame.size.width);
Current Result
When I log the width of self.questionWebView (the UIWebView), it's width does not change when the autolayout constrain is applied.
Questions
Is this the correct approach?
What am I doing wrong?
p.s I know it is against Apple's recommendations to place a UIWebView in a UIScrollView, however I've turned off the ability to scroll the UIWebView using the property self.questionWebView.userInteractionEnabled = NO;. And currently using a UIWebView is my best strategy for displaying an HTML table.
Improving on Rob's answer, as requested.
As Rob already mentioned, UIScrollViews have peculiar behavior under Auto Layout.
What is of interest in this case is the fact that the scrollView total width is determined by using its subviews total width. So while the scrollView already asks the webView for its width, you're telling the webView to also ask the scrollView for its width. That's why it doesn't work. One is asking another, and no one knows the answer. You need another reference view to use as a constraint for the webView, and then the scrollView will also be able to successfully ask about its expected width.
An easy way this could be done: create another view, containerView, and add the scrollView as a subview to that. Then set the proper constraints for containerView. Let's say you wanted the scrollView centered on a viewController, with some padding on the edges. So do it for the containerView:
NSDictionary *dict = NSDictionaryOfVariableBindings(containerView);
[self.view addConstraints:[NSLayoutConstraints constraintsWithVisualFormat:#"H|-(100)-[containerView]-(100)-|" options:0 metrics:0 views:dict];
[self.view addConstraints:[NSLayoutConstraints constraintsWithVisualFormat:#"V|-(100)-[containerView]-(100)-|" options:0 metrics:0 views:dict];
Then you can proceed adding the webView as a subview to the scrollView and setting its width:
NSLayoutConstraint *makeWidthTheSameAsScrollView =[NSLayoutConstraint
constraintWithItem:self.questionWebView
attribute:NSLayoutAttributeWidth
relatedBy:0
toItem:containerView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0];
[self.view addConstraint:makeWidthTheSameAsScrollView];
This would make the scrollview as large and tall as the webView, and they both would be placed as intended (with the constraints set on containerView).
Scrollviews are a bit strange in how they interact with auto layout. See TN2154 (UIScrollView and Autolayout).
See also UIScrollView doesn't use autolayout constraints.
In general, you need to get the width of the contained view some other way than "the current width of the scrollview" since in auto layout the scrollview's width (i.e. content width) is defined in terms of its content. Thus your current request is circular.

Why when I made my constraint relative to the superview's bottom did it operate from the superview's top? And is it now right?

I have the following code:
self.noArticlesView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, self.view.frame.size.height)];
UIImage *backgroundPattern = [UIImage imageNamed:#"no-articles-background.png"];
self.noArticlesView.backgroundColor = [UIColor colorWithPatternImage:backgroundPattern];
[self.view addSubview:self.noArticlesView];
UIImage *noArticlesImage = [UIImage imageNamed:#"no-articles-icon.png"];
UIImageView *noArticlesImageView = [[UIImageView alloc] initWithImage:noArticlesImage];
noArticlesImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self.noArticlesView addSubview:noArticlesImageView];
NSLayoutConstraint *horizontalCenterConstraint = [NSLayoutConstraint constraintWithItem:noArticlesImageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.noArticlesView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
[self.noArticlesView addConstraint:horizontalCenterConstraint];
NSLayoutConstraint *verticalPlacementConstrant = [NSLayoutConstraint constraintWithItem:noArticlesImageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.noArticlesView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-230.0];
[self.noArticlesView addConstraint:verticalPlacementConstrant];
Basically, I'm adding a view (called noArticlesView) on top of the view controller's view, and this view displays a message saying there's no content currently added (in the case that there isn't, of course).
To that view I then add an image as a subview as an indicator of this.
But when I change the above constraint on the image to have the property toItem:self.view and add it to self.view instead of self.noArticlesView it pushes it from the top of the view and not the bottom (i.e. 200 as the constant will push it 200px from the top).
I got it sorted out by setting the image's constraint relative to noArticlesView instead of self.view, but I'm still curious for the behavior (I'm still learning Auto Layout and want to get a grasp of it).
Also, is what I'm doing now correct? If I want it to be be positioned 230px from the bottom, is setting the constant to -230 the way to go? Is setting constants as negative bad form or anything?
I have experienced weird things like this too, and I suspect it is because the superview (in your case self.view) has no height--it doesn't resize based on its superviews. That means that the bottom of self.view is equal to the top, making it look like the constraint works from the top. You can try two things to verify this theory:
Inspect self.view's frame (the height will be 0)
Set self.view.clipsToBounds = YES (your views, including the background view, won't show because they're clipped)
This is (or might be) only an explanation of what's going on, I am not sure what the right way to deal with this is.
As far as your other question goes: it is perfectly valid to do it that way, although there often is a better way to do it. Ask yourself "why is is -230"? If that's because the image is 230 tall, then use -noArticlesImage.size.height. If 230 just happens to be the number that makes the whole view look best, it's fine to use (though best practice dictates using a constant or preprocessor macro to define the 230).

Resources