iOS Autolayout Constraints animation breaks inner view position - ios

I am using Autolayout and contraints, I need to animate a view as if it came from a thumbnail to fullscreen. The view has one child, a webview which is attached to its four sides with constraints.
This is what I do:
- (void)animate {
trailingConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual toItem:self.superview.superview attribute:NSLayoutAttributeTrailing multiplier:1
constant:_trailing];
leadingConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual toItem:self.superview.superview attribute:NSLayoutAttributeLeading multiplier:1
constant:_leading];
bottomConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual toItem:self.superview.superview attribute:NSLayoutAttributeBottom multiplier:1
constant:_bottom];
topConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual toItem:self.superview.superview attribute:NSLayoutAttributeTop multiplier:1
constant:_top];
[self.superview.superview addConstraint:trailingConstraint];
[self.superview.superview addConstraint:leadingConstraint];
[self.superview.superview addConstraint:topConstraint];
[self.superview.superview addConstraint:bottomConstraint];
[self.superview.superview layoutIfNeeded];
self.alpha = 0;
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.closeButton.hidden = NO;
self.alpha = 1.0f;
trailingConstraint.constant = 0;
leadingConstraint.constant = 0;
bottomConstraint.constant = 0;
topConstraint.constant = 0;
[self.superview.superview layoutIfNeeded];
} completion:nil];
}
I call that method after adding the view to the superview, and I call the opposite when I close the view
It works fine. But the inner view, a UIWebview does not resize together with the animation, it takes the final size before the animation starts, it flickers and becomes big (or small when closing) and then the animation starts on self and is self resizes correctly.
What could be wrong?
The webview does not have size constraints, it has position constraints, the same as self basically -> 0 for four sides to self and these constraints are not changed.

Related

XIB gave a constraint to launch the WKWebView, but there is an error A multiplier of 0 or a nil..... How can I fix this?

an error occurred while trying to run webview on xib using PageViewController.
Thread 1: "NSLayoutConstraint for <WKWebView: 0x14d0a8e00; frame = (0 0; 0 0); layer = <CALayer: 0x28109b600>>: A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs."
I made a view in xib (Not a ..cell. This is a view.) and made a wkwebview with code using that view, but I am having a hard time because of these errors. Is there a solution?
Below is the code I wrote.
CGRect rect = [[UIScreen mainScreen]bounds];
//CGRect rect = CGRectMake(0.0, 0.0, self.safeAreaContainerView.layer.frame.size.width, self.safeAreaContainerView.layer.frame.size.height); //I only received width and height as 0 and annotated it.
self.wkwebview = [[WKWebView alloc] initWithFrame:rect];
self.wkwebview.UIDelegate = self;
self.wkwebview.navigationDelegate = self;
self.wkwebview.translatesAutoresizingMaskIntoConstraints = NO;
[self.wkwebview sizeToFit];
self.wkwebview.scrollView.scrollEnabled= YES;
[self.safeAreaContainerView addSubview:self.wkwebview];
[self.safeAreaContainerView sendSubviewToBack:self.wkwebview];
//WKWebView Layout Setting
NSLayoutConstraint *firstViewConstraintTop = [NSLayoutConstraint constraintWithItem:self.wkwebview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.safeAreaContainerView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *firstViewConstraintBottom = [NSLayoutConstraint constraintWithItem:self.safeAreaContainerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.wkwebview attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *firstViewConstraintLeadingSpace = [NSLayoutConstraint constraintWithItem:self.wkwebview attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.safeAreaContainerView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *firstViewConstraintTrailingSpace = [NSLayoutConstraint constraintWithItem:self.safeAreaContainerView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.wkwebview attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
[self.safeAreaContainerView addConstraints:[NSArray arrayWithObjects: firstViewConstraintTop, firstViewConstraintBottom, firstViewConstraintLeadingSpace, firstViewConstraintTrailingSpace, nil]];
Does it not work in View when coding the WKWebView to the XIB?
Is it only available on TableViewCell or CollectionViewCell?
I do something similar and use the code I posted here to embed the webview into its container view.
How to: Constrain subview to safeArea Obj-c
Apart from using that I do preciously little on the webview itself, basically the following
webView = [[WKWebView alloc] initWithFrame:GCRectZero];
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
and nothing else.
HIH

Slide In A UIView by updating constraints

I am trying to animate a UIView by updating its constraints. I want to initially place the view at the bottom of the screen (off screen) and then set the top constraint to 0 so that the animation shows the view moving upwards.
I have tried the following with no luck at all, the animation is incorrect.
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
//CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
self.viewSearchWrapper.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.viewSearchWrapper];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:0.f];
// Leading
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.f];
// Bottom
NSLayoutConstraint *bottom = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.f];
// Bottom
NSLayoutConstraint *top = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:screenHeight];
[self.view addConstraint:trailing];
[self.view addConstraint:bottom];
[self.view addConstraint:leading];
[self.view addConstraint:top];
[self.viewSearchWrapper setNeedsUpdateConstraints];
[top setConstant:0.f];
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
Add layoutIfNeeded to first set the initial constraint to viewSearchWrapper.
// .... keep the code assigning the constraints as it is.
[self.view layoutIfNeeded]; // This will set the initial frame of viewSearchWrapper
[self.viewSearchWrapper setNeedsUpdateConstraints];
[top setConstant:0.f];
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
Add [top setConstant:0.f] inside animateWithDuration as shown below:
[UIView animateWithDuration:1 animations:^{
[top setConstant:0.f];
[self.view layoutIfNeeded];
}];

iOS Autolayout relative to the sibling UIViews programatically

I am trying to add 3 custom views (redView, greenView, yellowView) inside a container view (C1) such that all the custom views (redView, greenView, yellowView) are below each other using Auto layout constraints programatically. I want the container view (C1) to get the same size as the size of it subview, so the output should be like this.
The red, green and yellow views are just to show the expected result. Actually the custom view i have is like this.
I am using Auto Layout to do this. Here is my code to do this. RatingsSingleView is my custom view which are shown in the above image.
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *ratingsContainerView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView *previousTopView = self.ratingsContainerView;
for(int i = 0; i < 3; ++i) {
RatingsSingleView *view = [[RatingsSingleView alloc] init];
view.translatesAutoresizingMaskIntoConstraints = NO;
[self.ratingsContainerView addSubview:view];
NSLayoutConstraint *topConstraint = nil;
if(i == 0) {
// Making the first subview top aligned to the container View top
topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousTopView attribute:NSLayoutAttributeTop multiplier:1.0 constant:10.0];
} else{
// Making the second and third subview top aligned to the view above it
topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousTopView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10.0];
}
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.ratingsContainerView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:10.0];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.ratingsContainerView attribute:NSLayoutAttributeRight multiplier:1.0 constant:10.0];
[self.ratingsContainerView addConstraint:topConstraint];
[self.ratingsContainerView addConstraint:leftConstraint];
[self.ratingsContainerView addConstraint:rightConstraint];
if(i == 2) {
// Adding last subview bottom to the container View bottom
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.ratingsContainerView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-10.0];
[self.ratingsContainerView addConstraint:bottomConstraint];
}
previousTopView = view;
}
}
#end
So the issue is i am not getting the expected result. I am pinned the container view to the left and right edges and set its height to 0 in the storyboard. once i run the above code i am getting the following result.
Can some body could guide me what i am doing wrong here. thanks
You have given some wrong constraints and I've corrected it try this...
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView *previousTopView = self.ratingsContainerView;
for(int i = 0; i < 3; ++i) {
RatingsSingleView *view = [[RatingsSingleView alloc] init];
view.translatesAutoresizingMaskIntoConstraints = NO;
[self.ratingsContainerView addSubview:view];
NSLayoutConstraint *topConstraint = nil;
if(i == 0) {
// Making the first subview top aligned to the container View top
topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousTopView attribute:NSLayoutAttributeTop multiplier:1.0 constant:10.0];
} else{
// Making the second and third subview top aligned to the view above it
topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousTopView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10.0];
}
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.ratingsContainerView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10.0];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self.ratingsContainerView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:10.0];
[self.ratingsContainerView addConstraint:topConstraint];
[self.ratingsContainerView addConstraint:leftConstraint];
[self.ratingsContainerView addConstraint:rightConstraint];
if(i == 2) {
// Adding last subview bottom to the container View bottom
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self.ratingsContainerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10.0];
[self.ratingsContainerView addConstraint:bottomConstraint];
}
previousTopView = view;
}

How to change or update NSLayoutConstraint programmatically

I have implemented AutoLayout programmatically using the Code :
- (void)addConstraintWithListNavigationViewController:(UIView *)listViewNavigation y:(CGFloat)y height:(CGFloat)height
{
//WIDTH_ListTableView = 0.4
//set x = 0;
NSLayoutConstraint *constraintToAnimate1 = [NSLayoutConstraint constraintWithItem:listViewNavigation
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:0.00
constant:0];
[self.view addConstraint:constraintToAnimate1];
//set y = y;
NSLayoutConstraint *constraintToAnimate2 = [NSLayoutConstraint constraintWithItem:listViewNavigation
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:0.00
constant:y];
[self.view addConstraint:constraintToAnimate2];
//set Width = self.view.frame.size.width*0.4
NSLayoutConstraint *constraintToAnimate3 = [NSLayoutConstraint constraintWithItem:listViewNavigation
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1-WIDTH_ListTableView
constant:0.0];
[self.view addConstraint:constraintToAnimate3];
//Set height = height
NSLayoutConstraint *constraintToAnimate4 = [NSLayoutConstraint constraintWithItem:listViewNavigation
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:0.00
constant:height];
[self.view addConstraint:constraintToAnimate4];
}
And this works perfect, but every-time this ViewController receives a Notification, it will run:
[self.view layoutIfNeeded];
But I want to set the width of listViewNavigation according a boolean variable connected.
if(connected){
listViewNavigation.view.frame.size.width = 0.4 * self.view.frame.size.width;
}
else{
listViewNavigation.view.frame.size.width = 0.6 * self.view.frame.size.width;
}
But i do not know how can I update the NSLayoutConstraint :
NSLayoutConstraint *constraintToAnimate3 = [NSLayoutConstraint constraintWithItem:PreView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1 - WIDTH_ListTableView
constant:0.0];
[self.view addConstraint:constraintToAnimate3];
when this ViewController receive the notification.
I think you have 2 options.
Option 1
Keep a property
#property (strong,nonatomic)NSLayoutConstraint *constraintToAnimate3;
Then use this property to
self.constraintToAnimate3 = [NSLayoutConstraint constraintWithItem:PreView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1
constant:-1 * 0.4 * self.view.frame.size.width];
[self.view addConstraint:self.constraintToAnimate3];
When you want to change
if(connected){
self.constraintToAnimate3.constant = -1 *0.6 * self.view.frame.size.width;
}
else{
self.constraintToAnimate3.constant = -1 *0.4 * self.view.frame.size.width;
}
[UIView animateWithDuration:yourduration animations:^{
[self.view layoutIfNeeded];
}];
Option 2
Set an identifier of constraintToAnimate3
constraintToAnimate3.identifier = #"1234"
Then search to get the constraint
NSLayoutConstraint * constraint3 = nil;
NSArray * constraints = self.view.constraints;
for (NSLayoutConstraint * constraint in constraints) {
if ([constraint.identifier isEqualToString:#"1234"]) {
constraint3 = constraint;
break;
}
}
Then change the constant as shown in Option1
Update:
If use constant in the code I post
PreView.frame.size.with = self.view.size.width * multiplier + constant
OK, I figure out.
[self.view removeConstraint:self.constraintOld];
[self.view addConstraint:self.constraintNew];
[UIView animateWithDuration:time animations:^{
[self.view layoutIfNeeded];
}];
It's essential that you don't just add the constraints to your view, but that you also remember them.
It's easiest to change a constraint if you only need to change its constant - the constant is the only part of a constraint that can be changed later repeatedly. To do that, you need to store the constraint on its own.
Otherwise you need to remove old constraints and add new ones. Since you usually have more than one constraint, store arrays with sets of constraints that may need to be replaced, then update the whole array. You can also use the activateConstraints and deactivateConstraints methods.

Animating an UIImageView (View) from Point A to Point B with constraints programmatically

I have an image view that I have lined up on the center right of my screen. All is fine when adding the image view by itself with constraints. I need to animate the image view from that original position(Point A) to the bottom portion(Point B) of the screen; the problem is when I try to animate from Point A to Point B, the image view starts off in the upper left hand corner of the screen, like it was simply added with no constraints (as opposed to where I want it starting off in the center) .
Here is the code and some comments:
#define DEGREES_TO_RADIANS(x) (M_PI * x / 180.0) //defined above
- (void)viewDidLoad
{
UIImage *cardFaceDownImage = [UIImage imageNamed:#"b-top#2x.png"];
UIImageView *dealCardDown = [[UIImageView alloc] initWithImage:cardFaceDownImage];
dealCardDown.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *dealCardConstraintY = [NSLayoutConstraint constraintWithItem:dealCardDown attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:1];
NSLayoutConstraint *dealCardConstraintX = [NSLayoutConstraint constraintWithItem:dealCardDown attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:130];
[self.view addConstraint:dealCardConstraintY];
[self.view addConstraint:dealCardConstraintX];
[self.view addSubview:dealCardDown];
//IF I comment out the below code the UIImageView lines up where I want it (Point A)
//IF I don't coment it out, the imageview starts in the upper left hand corner of the screen
//where it normally would as if just adding an image view programatically without
//constraints would, but it does end up where I want it to (Point B)
[self.view removeConstraint:dealCardConstraintX];
[self.view removeConstraint:dealCardConstraintY];
dealCardConstraintY = [NSLayoutConstraint constraintWithItem:dealCardDown attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:1];
dealCardConstraintX = [NSLayoutConstraint constraintWithItem:dealCardDown attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:130];
[self.view addConstraint:dealCardConstraintY];
[self.view addConstraint:dealCardConstraintX];
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:2.0 delay:2.0 options:UIViewAnimationCurveEaseIn animations:^(void)
{
dealCardDown.transform = CGAffineTransformRotate(dealCardDown.transform, DEGREES_TO_RADIANS(90));
[self.view layoutIfNeeded];
}
completion:^(BOOL finished)
{
}
];
}
..
You need to move change of constraint into animation block, so it will be animated properly.
This code works for me well.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIView *dealCardDown = [[UIView alloc] initWithFrame:(CGRect){0,0,50,50}];
dealCardDown.backgroundColor = [UIColor redColor];
dealCardDown.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *sizeConstraint = [NSLayoutConstraint constraintWithItem: dealCardDown attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem: nil attribute:NSLayoutAttributeNotAnAttribute multiplier: 1 constant: 50];
[dealCardDown addConstraint:sizeConstraint];
sizeConstraint = [NSLayoutConstraint constraintWithItem: dealCardDown attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute:NSLayoutAttributeNotAnAttribute multiplier: 1 constant: 50];
[dealCardDown addConstraint:sizeConstraint];
[self.view addSubview:dealCardDown];
NSLayoutConstraint *dealCardConstraintX = [NSLayoutConstraint constraintWithItem:dealCardDown attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f];
NSLayoutConstraint *dealCardConstraintY = [NSLayoutConstraint constraintWithItem:dealCardDown attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.f constant:-self.view.center.y+dealCardDown.frame.size.height/2];
[self.view addConstraint:dealCardConstraintX];
[self.view addConstraint:dealCardConstraintY];
[self.view layoutIfNeeded];
[UIView animateWithDuration:2.0 delay:0.0 options:UIViewAnimationCurveEaseIn animations:^(void)
{
dealCardDown.transform = CGAffineTransformRotate(dealCardDown.transform, 90*M_PI/180);
dealCardConstraintY.constant = 0;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished)
{
}
];
}

Resources