What is the proper way of animate views when constraints applied? - ios

I have to animate a couple of UIViews which I have added in a UIViewController inside the UIStoryboard. I have attached them with proper constraints so that they will always visible in a way I am looking. This is fine.
I am using https://github.com/satoshin21/Anima library to animate those views as per my need.
But the problem is they don't work as expected means, they are not animating in a direction or position it should be. I believe this is because of the constraints I have applied.
What is the best way to achieve this even if the constraints applied?
Setting, myView.translatesAutoresizingMaskIntoConstraints = true is coming up with lots of warning messages in console.
P.S. I am aware of taking references to the constraints in form of NSLayoutConstraints but this is not I am looking at as the above library is simply providing good chaining functions though we can do it without having references to the constraints.

The problem here, is that NSLayoutConstraint toggling works like properties in the sense that they are nothing but values which can be switched on/off, and alternated by playing with this toggling and other references to other possible values they can have. There's no real way of going around this that I know of unfortunately, and in fact i myself have built a small library similar to Anima, and it works rather well if you respect the NSLayoutConstraints' nature.
The proof of this is that under the hood of this Anima library, it's simply storing the animation points declared inside of the chain (inside Enum values in fact), and applying them as the animation moves along. Regardless, you should never re-set translatesAutoResizingMaskIntoConstraints to true when working with NSLayoutConstraints.
The second reason for this is that Constraints are the basis for all iOS frame operations, including .frame, and animations (which is why Anima works so well from the looks of it).
I wrote a post on this recently, but as I explain by referencing Apple:
Your problem is that when translatesAutoresizingMaskIntoConstraints is
called, methods like .frame or .frame.size are ignored/overriden
(depending on when you use them, before or after
translatesAutoresizingMaskIntoConstraints). As described by Apple:
Note that the autoresizing mask constraints fully specify the view’s
size and position; therefore, you cannot add additional constraints to
modify this size or position without introducing conflicts. If you
want to use Auto Layout to dynamically calculate the size and position
of your view, you must set this property to false, and then provide a
non ambiguous, nonconflicting set of constraints for the view.
UPDATED
Otherwise, try not to set translatesAutoResizingMaskIntoConstraints to true with these views, by doing that you basically tell your controller to ignore your constraints, and to try to apply constraints based on the .frame or .frame.size or position values set on the UIView. Thus, making your custom constraints obsolete. If by stopping this, you still get the issue, it's probably a constraint value issue, of which i can't give you much more advice without any code unfortunately.

First, you shouldn't set translatesAutoresizingMaskIntoConstraints to true if you have set suitable constraints on a view already. Setting this property to true will add more constraints to the view which leads to conflicts.
The general code for animation with constraints is
aConstraint.constant = 1234
anotherConstraint.isActive = false
thirdConstraint.isActive = true // thirdConstraint replaces anotherConstraint
UIView.animatewithDuration: 0.25) {
self.view.layoutIfNeeded()
}
Hope this helps. ;)

My solution is to remove constraints for that UIViewController and set the frame programmatically as per my needs. This works fine and no need to do this patchy thing with the usage of AutoLayout.

Related

Auto layout and animation

in my program I have 14 different buttons with a letter each. And every single button is connected to each other with auto layout. So there is crazy lots of constraints. See first image for explanation:
But I want to be able to move every single button back and forth through UIAnimation without messing up the whole auto layout setup. See second image for explanation, what I want to do:
Now the code I currently use to get these animation:
self.letterA.translatesAutoresizingMaskIntoConstraints = YES;
[UIView animateWithDuration:0.2 animations:^{
[letterA setFrame:CGRectMake(x, y, width, height)];
}];
Now the program works perfectly! Absolutely no problems at all! But the only "problem" is this code generates this message in the 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)
And this warning/error/problem goes on and on and on several times, every single time a UIButton is moved. Now what should I do?
Can I ignore the Unable to simultaneously satisfy constraints? Or will that cause trouble down the road?
If not, how to fix it? And since there are crazy amounts of constraints involved, how can it be fixed without editing every single constrain?
I would very much like to keep animating using setFrame:
To your points:
"Can I ignore the Unable to simultaneously satisfy constraints warning"
No. It's a really bad idea to ignore this, even if the UI appears more or less as you want it to even with the warning. It means that the system is deciding how to render your layout, because the instructions you have supplied are contradictory. It has analyzed the constraints you have provided and found a way to create a consistent layout by breaking one or several of them. There is no guarantee that it will decide to break the same constraints on different screen sizes or OS versions. Ignoring this warning massively increases the chance of UI bugs.
I'd rethink the whole way this UI is designed. IB/Storyboard Autolayout works well for UIs up to a certain level of complexity. This UI looks to be slightly beyond that level of complexity - if the tiles didn't need to move, it would be fine. As they do, programmatic autolayout may make things simpler.
The approach I'd take would be as follows.
a. Create a tile object, which exposes NSLayoutConstraint properties for top, left, width and height constraints. (These constraints would be added on the superview, but also stored on the tile itself).
b. Set up the views using a factory object method taking the starting position offset, width and height of the new tile as parameters. Use these values to set up the constraints on each tile independently. Don't constrain the tiles to each other at all - all constraints should be either internal (width,height) or relative to the superview (x,y). This means that when you animate changes only one tile is affected. You actually have more constraints, but they are in code and therefore easier to manage.
c). Use UIView animations to move the tiles around and resize them using the constraints on the tiles. You can store the initial frames for each tile position and use those values to determine the target constraint values. You should easily be able to resize the position and size of the tiles in this way.
d). Apple's API for NSLayoutConstraint is a bit verbose and ugly. Consider using Masonry, a nicer Autolayout DSL, in order to keep your code clean.
You can continue to use -setFrame:, as long as you don't use Autolayout - the two approaches just don't play well together. If you want your app to run on more than one screen size, you need to use Autolayout, or else recalculate every frame and offset dynamically in code. If you don't (maybe it's an iPad app and you don't care about the Pro, then just use -setFrame:). But, on balance, I'd advise biting the bullet and just learning Autolayout
If your using auto layout you should set the translateAutoResizingMask to false otherwise you will keep getting this error. Instead you should be setting the new location using constraints only before the animation block then call layoutIfNeeded in the animation block. As you are setting the constraints in interface builder that has the effect of setting the translateAutoResizingMaskToConstraints to false automatically.

Difference between UIView.layoutMargins and AlignmentRect

I'm figuring out (again) how to set the margin for a custom UIView instance. From what I recall I had to set the AlignmentRect via the alignmentRectInsets method. But that did not worked with auto layout.
Searching on google I found that there is another property called layoutMargins.
So the question is what does layoutMargins and alignmentRect do ? Do they affect each other? Totally different things ?
layoutMargins determines how things inside of the view are positioned with auto layout. Usually this is used to keep objects a specific distance away from the edges of the view.
alignmentRectInsets is for telling objects outside of your custom view how they should align with it. For example, you might have a view with a wavy or angled top. Aligning other objects with the top of the view may not look quite right, so you might set an inset on the top alignment to compensate.
You probably care about the layoutMargins. I've never actually seen anyone use alignmentRectInsets.

Animating views constrained with auto layout

Just for fun I started to play with animations after successfully applying them to some basic background color changes. I have some different images on my "welcome/splash" view that I would like to animate. One image should appear from the bottom, another from the top and so on.
I immediately ran into trouble since I am using auto layout it was not that easy as animating background colors. I found this post How do I animate constraint changes? and after doing what was described at least my image was animating. However, is it correct that the console window should be filled with warnings/info/errors about constraint violations? Also, animating the vertical position of one image causes all the other image to animate too, probably because of some constraint relations.
How are you supposed to deal with that? when animating the "Bottom layout attribute" with a new constant value I expect only the view it belongs to animate, not the whole screen.
And how are you supposed to refer to the constraints? By outlets?
I am creating my views and constraints in storyboard. I deleted my code for animating the image views since it was just a mess. But whats done with the button and textfields in this tutorial is pretty much what I am trying to do with my images. Without animating one image causing the whole view to animate with it. http://www.raywenderlich.com/113674/ios-animation-tutorial-getting-started
However, is it correct that the console window should be filled with warnings/info/errors about constraint violations?
Learning Auto layout can be a challenge which will fill your days with such warnings/info/errors about constraints. Many iOS developer's struggle with Auto Layout at first. Auto layout is an addition to the layout process. The issue here is how are you going to deal with them. I suggest reading the Apple Auto Layout Guide, it contains a section on debugging. Also look at the Debugging Tricks and Tips section.
Here's a great article explaining more of the concepts behind Auto layout.
Build a simply app that has only one view and a subview, so that you can reduce the noise around layout constraint errors in more complicated layouts.
Here is a code snippet of how to animate a constraint.
if myViewTrailingConstraint.constant == -2 {
myViewTrailingConstraint.constant = 200
} else {
myViewTrailingConstraint.constant = -2
}
UIView.animateWithDuration(0.3,
animations: {
self.view.layoutIfNeeded()
},
completion: nil)
How are you supposed to deal with that?
Auto layout is an inter connected system of relationships between your views. A constraint represents a relationship. So you really need to think through your view's layout and plan your constraints first. Why? because if you plan to animate certain views you need to need to make sure that the constraint constant you are going to change will effect only that view in question.
And how are you supposed to refer to the constraints? By outlets?
You can create constraints solely in code or with the interface builder (outlets). I would suggest that you start with interface builder as even when you are comfortable working in code, it is useful and time saving to be able to do your initial layout in interface builder - so learn to use both.
Warnings are not normal - you have to solve them to avoid strange effects.
If animating a constraint value of one image moves other images, then indeed you must have some constraints in place that also change - e.g "equal width" constraints or such. Normally it just works - if it doesn't, you have to show the warnings you get and the constraints you set in order for someone to see what is going wrong.

Autolayout Dumbness (Mine)?

I'm using auto layout in one of my projects and am having trouble with what I thought should be a trivial task.
As an example, I have two text boxes below each other. I have created a vertical spacing constraint between the two, of 5 points.
I would expect that if i move the top text box (using setFrame), the constraint would mean that the bottom one would automatically move with it to enforce the constraint?
Is my thinking correct? or am I using auto layout in a way that it was not meant to. ie. I have misunderstood its purpose. Or should I be moving the top text box using a different manner other than setFrame?
Any input would be great. Thanks in advance :-)
PS. I have played around with using: setTranslatesAutoresizingMaskIntoConstraints which seems to work. However, this doesn't seem like the proper way to do this. My initial thoughts are that auto layout is supposed to handle size/position relationships with little code, in this case, i would expect it would be handled with zero code.
You can't use setFrame to do that. Auto-layout decides the frame. If you change it after the fact the constraints aren't automatically updated.
You could get a reference to the constraint that decides the Y position (either when you create it programmatically or using an IBOutlet) of the top box and change its value. That would trigger an update to recompute the position of everything and run layout again.
You would do that by changing the value of the constant property on the NSLayoutConstraint object.
Reference Docs Here

iOS AutoLayout - when does it Run?

I have a XIB file where I specify constraints at design time.
In addition to controls with constraints, I also have controls that have no constraints in the XIB that I want to position programmatically at run time.
I do this by repositioning views manually in didRotateFromInterfaceOrientation.
When I rotate the device, my code places the controls that are manually controlled but then later it appears that auto layout messes up the placement of manual controls that have no constraints on them in the Interface builder.
Question - When exactly does Auto layout run if I rotate the device.
Auto Layout runs at the end of the run loop after setNeedsLayout is called on a view, or immediately when setNeedsLayout is followed by layoutIfNeeded. Note that there are many methods that may call setNeedsLayout in their implementation, such as addSubview:, removeFromSuperview, etc.
A couple suggestions:
Instead of updating your constraints in didRotateFromInterfaceOrientation:, try doing so in updateViewConstraints.
You may also want to add placeholder constraints in Interface Builder to the views you will position programmatically. I think IB will automatically constrain views to their x/y position if a placeholder constraint is not set, but I'm not 100% sure on that.
The important thing to know is that the results of constraint calculations are applied in layoutSubviews. So if you want to set frames, you do so after calling super in layoutSubviews or in your view controller's viewDidLayoutSubviews.
Having said that, I've only experimented with this. I've not done it in production code and can't say that you won't run into issues.
I'm actually not clear on whether it's OK to set frames manually like this. The only relevant bit of information I've come across is the following from Apple's Auto Layout documentation:
You cannot set a constraint to cross the view hierarchy if the
hierarchy includes a view that sets the frames of subviews manually in
a custom implementation for the layoutSubviews method on UIView (or
the layout method on NSView).
Here, the phrase "view that sets the frames of subviews manually" implies to me that manually setting frames is OK. I'd be interested to know if anyone has a see a more explicit discussion on this.
you must not change the frame you must connect the constraint with iboulet and in the didRotateFromInterfaceOrientation you can change the constraint like this:
(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
if (UIInterfaceOrientationIsLandscape(fromInterfaceOrientation)) {
self.constraintTop.constant = 50;
}else{
self.constraintTop.constant = 100;
}
}

Resources