Force multiplier to update on Interface Builder constraint - objective-c

I've created a height constraint for a UIView in the Interface Builder that sets the height to = the superview. I set the multiplier to 1/3 of the superview's width. This works great, it sets the height, say on iPhone 5/5S, to ~107pts.
For wider screens, I want to set the multiplier to 1/4 instead; I suppose I could do this with Size Classes, but I'm not sure that's the best way to go for this?
In my viewWillAppear method of a view controller, I've tried doing the following; no matter what I do though, it still sets the height to only ~107pts. Note that _controlsAspectRatio is an IBOutlet to the IB constraint.
_controlsAspectRatio = [NSLayoutConstraint constraintWithItem:_controlsAspectRatio.firstItem attribute:_controlsAspectRatio.firstAttribute relatedBy:_controlsAspectRatio.relation toItem:_controlsAspectRatio.secondItem attribute:_controlsAspectRatio.secondAttribute multiplier:1.0f/4.0f constant:0];
// I've tried several combinations of several layout refresh options, to no avail
[_controlsContainer setNeedsUpdateConstraints];
[_controlsContainer setNeedsLayout];
[_controlsContainer layoutIfNeeded];
NSLog(#"%0.4f - %0.4f", _controlsContainer.frame.size.height, _controlsAspectRatio.multiplier);
// Output: 106.6667 - 0

Had to remove the original and add a completely new constraint:
NSLayoutConstraint* newControlsAspectRatio = [NSLayoutConstraint constraintWithItem:_controlsAspectRatio.firstItem attribute:_controlsAspectRatio.firstAttribute relatedBy:_controlsAspectRatio.relation toItem:_controlsAspectRatio.secondItem attribute:_controlsAspectRatio.secondAttribute multiplier:1.0f/4.0f constant:0];
[self.view removeConstraint:_controlsAspectRatio];
[self.view addConstraint:newControlsAspectRatio];
[_controlsContainer layoutIfNeeded];
NSLog(#"%0.4f - %0.4f", _controlsContainer.frame.size.height, newControlsAspectRatio.multiplier);

Related

Autolayout: how to hide UIView containing subViews?

The best solution for hiding views with the new Autolayout is definitely to create height constraint for the view, connect it and create an outlet for it, and change self.myViewHeightConstriant.constant equals to 0. But suppose the view contains some other views, suppose an imageView and some label below it. Now, the imageView is 10px away from the top and has top space to superview constraint with 10px value. Trying to hide container UIView with constant = 0 shows an error in console:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this: (1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't
understand, refer to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)
(
<NSLayoutConstraint:0xc7cedb0 V:[UIView:0xc7ce1e0(0)]>,
<NSLayoutConstraint:0xc7ceea0 V:[UIImageView:0xc7ce270]-(0)-| (Names: '|':UIView:0xc7ce1e0 )>,
<NSLayoutConstraint:0xc7cef30 V:|-(10)-[UIImageView:0xc7ce270] (Names: '|':UIView:0xc7ce1e0 )>
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0xc7ceea0 V:[UIImageView:0xc7ce270]-(0)-| (Names: '|':UIView:0xc7ce1e0 )>
The guess the problem is container UIView has height 0, but the imageView has top space offset 10px from it and Autolayout engine doesn't understand how to handle this situation. Tried to set clipSubviews for container view but that didn't help. Any ideas?
UPDATE several thoughts, creating an outlet topSpaceToSuperView constraint for the imageView and set its constaint also to 0 doesn't look very appealing. There should be more elegant solution than trashing the code with multiple outlets...
You can't go simple with container.hidden = YES?
Otherwise, it's the bottom constraint that's breaking things. #"V:|-10-[imageView]|" tells the container view that has to be at least 10 pts tall. But #"V:|-10-[imageView]" would be fine.
Perhaps instead of anchoring the imageView to the bottom of the container, setup a constraint for the imageView's height.
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[imageView]"
options:nil
metrics:nil
views:views];
[NSLayoutConstraint constraintWithItem:self.imageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeHeight
multiplier:1.
constant:-10.f];
Update
You mention in the comments that the imageView isn't a predictable height. Since that's the case, it might be easier to just manage the container's height, but do it with separate constraints:
containerOpen = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[imageView]|"
options:nil
metrics:nil
views:views];
containerClosed = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[containerView(0)]"
options:nil
metrics:nil
views:views];
// Toggle between the constraints to open close the container
- (void)toggleContainer
{
[self.containerView.superview removeConstraints:containerOpen];
[self.containerView.superview removeConstraints:containerClosed];
self.containerView.isOpen = !self.containerView.isOpen;
if (self.containerView.isOpen)
[self.containerView.superview addConstraints:containerOpen];
else
[self.containerView.superview addConstraints:containerClosed];
[self.containerView.superview setNeedsUpdateConstraints];
[self.containerView setNeedsLayout];
[self.containerView layoutIfNeeded];
}
If you have a leading, trailing, top, or bottom constraint you can set just set the relation to LessThanOrEqual when you hide it, and then back to equal when you show it.
Because relation is read-only, you'd do this by:
Outletting the constraint
Programmatically removing it from
the superview
Setting the outlet equal to a new constraint that
has the same parameters, except with your desired <= or = (depending
on if you are hiding or showing
Re-adding the constraint to the
superview
Essentially, all you're doing here is making that constraint small enough so that the height of your "big" view can == 0 when it's hidden.

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.

Resize a view when another is hidden

I have two views on the iPhone screen, one above the other (mediaControls above deviceWebView). When I hide the top view I want the bottom view to take up the entire screen, and when I reveal the top view I want the bottom view to resize again to be below the top view. It seems very simple but I am having trouble with it.
I've tried only hiding the view as well as adjusting the layout constraints as I show below.
Heres my code:
-(void)hideVideoButtons{
self.mediaControls.hidden = YES;
[self.view removeConstraint:self.deviceLayoutConstraint];
[self.deviceWebView setNeedsDisplay];
}
-(void)showVideoButtons{
self.mediaControls.hidden=NO;
self.deviceLayoutConstraint = [NSLayoutConstraint constraintWithItem:self.deviceWebView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.mediaControls attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
[self.view addConstraint:self.deviceLayoutConstraint];
have you tried the UIView animateWithDuration: animations: method? You can animate the UIView(s) to whatever location you'd like, you can resize them, and much more.
[UIView animateWithDuration:1.0 animations:^{
// moving the device web view
self.deviceWebView.transform = CGAffineTransformMakeTranslation(xVar, yVar);
// moving the other view
otherView.transform = CGAffineTransformMakeTranslation(xVar, yVar);
}];
the xVar and yVar are not variables being set. They are just showing the usage of the method. You'd replace them with the x and y locations of the screen you are wanting to move the x and y of the view to. I'm not fully sure if this is what you're looking for, but it seems you are looking for a way to animate the views, so here's a suggestion :)
Hope this helps!

Animate intrinsicContentSize changes

I have a UIView subclass that draws a circle whose radius changes (with nice bouncy animations). The view is deciding the size of the circle.
I want this UIView subclass to change its frame size to match the animated changes to the circle radius, and I want these changes to modify any NSLayoutConstraints connected to the view (so that views that are constrained to the edge of the circle will move as the circle resizes).
I understand that implementing -(CGSize)intrinsicContentSize and calling invalidateIntrinsicContentSize when the radius changes will tell constraints to update, but I cant figure out how to animate the changes to intrinsicContentSize.
Calling invalidateIntrinsicContentSize from within a [UIView animateWith... block just instantly updates the layout.
Is this even possible, and is there a workaround/better approach?
invalidateIntrinsicContentSize works well with animations and layoutIfNeeded. The only thing you need to consider is, that changing the intrinsic content size invalidates the layout of the superview. So this should work:
[UIView animateWithDuration:0.2 animations:^{
[self invalidateIntrinsicContentSize];
[self.superview setNeedsLayout];
[self.superview layoutIfNeeded];
}];
Swift version of #stigi's answer which worked for me:
UIView.animate(withDuration: 0.2, animations: {
self.invalidateIntrinsicContentSize()
self.superview?.setNeedsLayout()
self.superview?.layoutIfNeeded()
})
Width / height constraint doesn't help? Keep reference of this constraint and ...
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:myViewInitialWidth];
... when you do want to animate myView resize, do this ...
self.viewWidthConstraint.constant = 100; // new width
[UIView animateWithDuration:0.3 animations:^{ [view layoutIfNeeded]; }];
... do the same thing for the height.
Depends on your other constraints, maybe you will be forced to raise priority of these two constraints.
Or you can subclass UIView, add - (void)invalidateIntrinsicContentSize:(BOOL)animated and fake it by yourself. Get new size from - (CGSize)intrinsicContentSize and animate it by animating width / height constraints. Or add property to enable / disable animations and override invalidateIntrinsicContentSize and do it inside this method. Many ways ...
In the code below, intrinsic class is the class that has just changed it's size on changing a variable. To animate the intrinsic class only use the code below. If it impacts other objects higher up the view hierarchy then replace self.intrinsic class with the top level view for setNeedsLayout and layoutIfNeeded.
[UIView animateWithDuration:.2 animations:^{
self.intrinsicClass.numberOfWeeks=8;
[self.intrinsicClass setNeedsLayout];
[self.intrinsicClass layoutIfNeeded];
}];
None of this has worked for me. I have a UILabel which I am setting with a NSAttributedString. The text is multiline and wrapping on word boundaries. Therefore the height is variable. I've tried this:
[UIView animateWithDuration:1.0f animations:^{
self.label = newLabelText;
[self.label invalidateIntrinsicContentSize];
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
And a number of variations. None work. The label immediately changes it's size and then slides into it's new position. So the animation of the labels position ons screen is working. But the animating of the label's size change is not.
Well for Swift 4/3 this works and I think this is best practise. If you have a UIView with a UILabel in it and the UIView adapts the frame from the UILabel, use this:
self.theUILabel.text = "text update"
UIView.animate(withDuration: 5.0, animations: {
self.theUIView.layoutIfNeeded()
})
The normal self.view.layoutIfNeeded() will work most of the time as well.

Subview disappears with auto layout

I just added a UIView as subview to the main UIView on interface builder (basic single view application).
Without setting any constraints, my subview disappears.
subview's frame = (0 0; 320 0);
Why is that?
If I try to add some constraints like for trailing space, leading space, top space and bottom space to be fixed, still my view disappears.
How can I solve this?
Thank you.
Just to clarify thing a little I created a test project (single view application), and added 2 subview to the main view like in the image. I didn't change any default constraint.
And you can see the error in the log of the image.
Logs:
**2013-01-19 17:16:02.435 Test[8871:c07] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x106178c0 h=--- v=--- V:[UIWindow:0x9917040(480)]>",
"<NSLayoutConstraint:0x106159e0 UIView:0x991a5a0.bottom == UIWindow:0x9917040.bottom>",
"<NSLayoutConstraint:0x991ab00 V:|-(518)-[BottomView:0x9919c90] (Names: '|':UIView:0x991a5a0 )>",
"<NSLayoutConstraint:0x10615960 V:|-(20)-[UIView:0x991a5a0] (Names: '|':UIWindow:0x9917040 )>",
"<NSLayoutConstraint:0x991aa80 BottomView:0x9919c90.bottom == UIView:0x991a5a0.bottom>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x991aa80 BottomView:0x9919c90.bottom == UIView:0x991a5a0.bottom>
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.**
Constraints:
Also here is the result on the simulator:
It is good practice to understand those logs but if you are going to use Autolayout you are going to have to read up on this. Everyone says it is simply but I personally have not found it simple.
Apples Programming Guide For AutoLayout
Please read this guide especially the debugging section.
As a very very general rule if you are going to add a view then you need to turn off autoresizingmasks (Spring and struts) for the view. Add the view as a subview and give it 2 or 3 constraints. In your case above you would give it a constaint that it should have a left or leading space to superview of 0. A top space to superview of 0 and a width of 320.
EDIT; Here is an example of adding a view; note you do not need to create a frame. The constraints may be a little strange. The first puts the view in the centre of the superview. The second gives it a width of 200. The next method is the vertical constraint which puts the view at the bottom and makes it 2 high.
UIView *sView = [[UIView alloc] init];
[sView setTranslatesAutoresizingMaskIntoConstraints:NO];
[superView addSubview:sView];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:sView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:superView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:sView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:Nil
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:200]];
[superView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[sView(2)]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(sView)]];