how to animate views change views hierarchy using autolayout? - ios

I have superview called self.myMainView
In that view I have three subviews: UIView *view1 (green view), UIView *view2 (grey view), UIImageView *view3
I want to animation moving of view 3
and when view 3 is finished this animation than I want to change view hierarchy, like on example screenshots.
Here is example images:

What you are doing is the normal, standard way to change the layering order of views. So the problem is not this, but in your words "But, it violates my animation" and "crushing my animation". The problem, then, is with your animation, if it can be "violated" and "crushed" by rearranging the layers. However, you do not provide, in your question, any information about the animation! Yet that is clearly the heart of the problem.
EDIT: Now that you've posted the animation code, I was able to test, and I see what the problem is. The problem is that you are using autolayout.
view3 is positioned by constraints. When you animate the position of view3, you violate those constraints, but this does not become immediately evident. But when you exchange the layering order of view1 and view2, layout is performed! The constraints on view3 are then enforced. Since those constraints did not change, we see view3 back where you originally had it.
The simplest solution is to turn off autolayout if you don't need it. Otherwise, you will have to change the constraints, after the animation or as part of the animation. In fact, you can reposition view3 by animating its positional constraints. I describe all this in my book, here: http://www.apeth.com/iOSBook/ch17.html#_animation_and_autolayout

If all you want to do is switch which of your 2 subviews are on top of one another, can you simply use [UIView bringSubviewToFront:] rather than directly messing with the order of your subview array? That might solve the animation problems. The other thing I would try is put an if( finished ) around your call to exchangeSubviewAtIndex to make sure it doesn't interfere with the other views being animated.

The problem was in the constraints of view 3 which doesn't change after animation and when we wants to manipulate the view hierarchy, animation is stopped work properly. Matt said about that in his answer, also he provide a solution from his great book: http://www.apeth.com/iOSBook/ch17.html#_animation_and_autolayout
thank you for that Matt.
I change this solution a little and decide to post it here for anyone who will need this. But if you want to understand this better I highly recommend you to read chapter about animation from book, link is above.
First, we need to animate our movement of view 3, then change our constraints and only after this change the view hierarchy. In the book a found several ways to done this, but this one is seemed to me the best:
layout = -35.0; // new constraint constant of self.view3
NSArray *constraintsForAnimation = self.myMainView.constraints; // all constraints of superview
NSUInteger indexOfconstraintsAttribute = [constraintsForAnimation
indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop){ // get index of constraint of view 3
NSLayoutConstraint *constraint = obj;
return (([constraint.firstItem isEqual: self.view3])) ? idx : NO; // return this index if we found constraint of view 3
}];
constraint.constant = layout; // changing the constraint of view3
[UIView animateWithDuration:0.3 animations:^{ // animation
[self.view3 layoutIfNeeded]; // apply new constraints to view3
}];
[self.myMainView exchangeSubviewAtIndex:3 withSubviewAtIndex:4];// toggle view 1 with view 2
We move our view 3 only with help of constraints, we don't set frame or centre view property.
Okay, we done with and this working like as I want. Hope this help you too.

Related

Combining autolayout with core animation

I'm new to core animation and I'm struggling with one thing - how to combine autolayout with core animation. Actually I've found only one sentence in the documentation of Core Animation which refers to Autolayout here is it
Remember to Update View Constraints as Part of Your Animation
If you are using constraint-based layout rules to manage the position of your views, you must remove any constraints that might interfere with an animation as part of configuring that animation. Constraints affect any changes you make to the position or size of a view. They also affect the relationships between the view and its child views. If you are animating changes to any of those items, you can remove the constraints, make the change, and then apply whatever new constraints are needed.
But as I've tried all is not as strait-forward as it may seem.
Here is my scenario.
I've designed a sliding menu which uses autolayout extensively. Here is the appearance of that view.
I'm using autolayout constraints to force proportional positioning of those items in the sliding menu. Actually there are a lot of constraints there and I didn't want to post all of those in my question, and even may be they are not needed for direct answer of this question, however if you need them I can update the post with those constraints.
The animation that you see in the gif was reached by only autolayout. I just added outlet to the height constraint of the sliding menu and changed it in this way: (the code is written using Xamarin Monotouch, but I believe it should be clear what is done here for pure iOS developers)
private void AnimateSlideMenuAppearance()
{
float height;
if (isSlideMenuShown) {
height = 0;
} else {
height = slideMenuHeight;
}
UIView.Animate (0.4,
delegate {
this.slideMenuHeightConstraint.Constant = height;
this.View.LayoutIfNeeded ();
},
delegate {
isSlideMenuShown = !isSlideMenuShown;
});
}
Now I want to get more sophisticated appearance transition. CLICK HERE to see the effect that I want to reach.
Just to try out I tried to implement the disappearing part of that animation with series of CABasicAnimations, but it was unsuccessful, I get strange behaviour.
Can anybody suggest what I should do here? Is that possible to use autolayout to calculate the positions of the views, but somehow override the animation between autolayout size changes? I mean in my concrete example instead of proportionally decreasing the sizes of all buttons in menu I need to add FadeOut animation to them, animate the bounds to zero and also radically increase begin time of the animations from button to button in order to get the effect that I want. Or may be I need to completely get rid of autolayout and manually calculate the sizes and animations?
What is the best practice in these kind of scenarios - when you have complex autolayouting and you need custom Core Animation transitions between autolayout changes? I hope that I described the question well.
Thank you for your answers.
This is completely feasible, while it may be complex solely because it looks like your desired cases will have multiple animations.
However, I noticed one thing in your code that's odd: you change the constant on the constraint (this.slideMenuHeightConstraint.Constant = height) in the animation block, instead of before it. For nearly all cases I can imagine, you should change the constraint before the animation block. Constraints are not visually rendered until either the next UI run loop (or by setNeedsUpdateConstraints to force it for the next run loop), or immediately by layoutIfNeeded. And since [UIView animate:...] is doing this for you, layoutIfNeeded should (generally) be the only thing in your animate block, when animating autolayout.
In your case, you will have to make the animation somewhat reactive, however - for e.g., if you want to add those buttons in like in the example and have them pop in, animate out, and grow. After calling layoutIfNeeded, you can safely check the frame size. If it's beyond your threshold (or some other metric), you can trigger then animations of the buttons. (So yes, this may be a case where I'd add more code inside the animate block -- check the threshold, begin other animation, etc).

UIViews revert to a previous position after adding a subview

Facing a really strange issue trying to dynamically add a left navigation panel to a View Controller, (should be able to support any view controller in the app, vaguely similar to the Facebook navigation) My idea seemed fairly simple, but I'm really not seeing where it's breaking down. What I've done is created a Category on UIViewController with the following method which I would think would move all the subviews to the right, and then add the new view.
-(void)addLeftView:(UIView *)newView
{
newView.frame=CGRectMake(0, 0, newView.frame.size.width, self.view.frame.size.height);
for(UIView *view in [self.view subviews])
{
view.frame=CGRectMake(view.frame.origin.x + newView.frame.size.width, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
}
[self.view addSubview:newView];
}
What actually happens, though, is that the view is added, but the subviews do not move to the right. However, if you comment the addSubview out, everything actually does move to the right exactly as expected.
To make matters even weirder, if you wrap the view movement in a [UIView animateWithDuration:completionHandler:], where the completion handler adds the subview, the animation actually happens - all the views shift to the right, but when the subview gets added, they jump back to their starting position.
I assumed this was some sort of wacky auto-layout issue, so just to see what happened, I cleared all the constraints out of that view controller, and get the same result.
Found the answer in a similar but un-related thread. Can I disable autolayout for a specific subview at runtime?
Basically it was auto-layout reverting my positioning, so disabling it by setting this user variable on just the UIViews of problem fixed my issues.
You can set the translatesAutoresizingMaskIntoConstraints type Boolean, Value to Yes in the User Defined Runtime Attributes of the UIView you want in the xib/storyboard.

UIView animation does not animate subviews

I have a UIScrollView with some UIViews, which again contain all kinds of other views (labels, text fields, etc.). Now, when I rotate the device I would like to alter the size and position of the views within the scroll view. In order to do so I calculate a set of constraints for both portrait and landscape mode up front and switch the constraints on the scroll view according to the current interface orientation. Basically, this works as expected. The thing is that I do not want the views to be simply put, but smoothly animated into their new positions. And this is where I am completely stuck right now. The basic animation works just fine, i.e. the views within the scroll view are perfectly animated but the subviews within these views are simply repositioned and not animated. I use the following code to kick off the animation
NSMutableArray *constraints = UIInterfaceOrientationIsLandscape(interfaceOrientation) ? self.landscapeConstraints : self.portraitConstraints;
[self.scrollView removeConstraints:self.scrollView.constraints];
[self.scrollView addConstraints:constraints];
[UIView animateWithDuration:duration
animations:^{
[self.scrollView layoutIfNeeded];
}];
As far as I know (and as far as my research goes), this is just how it should be done... but obviously I am wrong and I cannot for the life of me figure out what I need to do differently. At the end of the animation everything is positioned perfectly fine, but the subviews simply refuse to animate. Any help is greatly appreciated!
For the sake of completeness it might be worth mentioning that I load the views that I position within the scroll view from a storyboard where I also set the constraints for their subviews. I think this should not be a problem though, since the layout actually works... except for the animations.

UIScrollView - Moving view to front, but below scroll indicators

I am working with a custom view which inherits from UIScrollView. The problem I am having is adding a UILabel that should be displayed at the 'top', in front of the other views. It works, but unfortunately it also covers up the scroll indicators.
As you can see, the scroll indicator is being obscured. I don't want to disable scroll indicators.
I am adding the views directly to the UIScrollView:
UILabel *subsection = [[UILabel alloc] initWithFrame:CGRectMake(0, y * h,
[self totalContentWidth], [tableViewDelegate rowHeight])];
[subsection setText:subsectionName];
[subsection setTextAlignment:NSTextAlignmentCenter];
[subSectionRows setValue:subsection forKey:key];
[self addSubview:subsection];
and then bringing them to the front, which
- (void) bringSubsectionsToFront {
for (UIView* row in [self.subSectionRows allValues]) {
[self bringSubviewToFront:row];
}
}
This behavior is confusing to me. If I peek at UIView.layer.zPosition of all the UIScrollView's subviews, they are all the same.
If I adjust the bringSubsectionsToFront method to instead move the labels in front of the view that contains the grid lines, the behavior is the same.
Looking at some internal view classes whose behavior works, it looks as if they are being added to the scroll view with: [self insertSubview:cell atIndex:0];
What am I missing here?
Solution:
verec remarked that I could find the bars if I really wanted to. So I iterated over the subview list at a suitable point, and sure enough, there they were. The order of the objects was as follows:
CustomCell
...
UIImageView
UIImageView
CustomBorderView
UILabel
UILabel
Assuming this is the z ordering, no wonder my labels were always on top.
Solution becomes simple; add UILabel's after CustomCells. This solution only works because the custom table adds CustomCells at index 0, which I believe makes scroll indicators appear after the CustomCells. If this assumption holds, I can reorder the other views relative to the CustomCells, achieving the layering effect I want.
The simplest is to use and intermediate UIView, call it 'contentView' whose children are the views you want to bringToFront.
If that contentView is what is inside the scrollView, it will stay 'below' the scroll indicators whichever contentView child you bring to the front.
The alternative would be to scan into the scrollView looking for the scroll indicators (you will find them!) but that's a compatibility risk, as you never know if/when Apple may decide to remove/reshape/reposition them according to some new (flatter ...) design paradigm ...

UIView subview auto resizing

I wrote a little UIView subclass to show a progress HUD. That HUD view works perfect but I faced a little problem in the last days.
In my application I'm presenting a UIViewController in a custom way. When the user selects a row in a tableView I'm creating an instance of my second viewController, move it to the current VC, set it's view's height to zero, add it as subview of the curren VC's view (at the position of the selected cell) and animate the height back to original.
The behaviour looks pretty cool and works great.
But when the second view is added as subview, I'm adding a HUD to this view. When the second view is resizing to the original height, the HUD sticks to the top of the view and is just a few pixels high:
I played around a bit with NSLayoutConstraints... But I didn't get it working until now...
Has someone a good idea on that one? Or does anybody know good and well explained resources on these constraints?
The HUD is actually a background view with the little window as subview. All other elements (the progress view, labels and so on) are subviews of the little window.
In terms of good resources, I definitely recommend WWDC2012's 3 videos: Introduction to Auto Layouts for OSX/iOS, Auto Layout by Example, and Best Practises for Mastering Auto Layout. These have some tips for looking at ambiguity in the layout and dealing with conflicts
Another great reference is Erica Sadun's iOS6 recipe book.
Re your problem. I'm assuming that you're not seeing an error message and you're laying out the progress HUD entirely in the XIB. If so, it sounds like you have two constraints that aren't behaving as you'd like from the xib - the height from the top of the superView and the height of the HUD
Firstly, create an outlet for the constraints to the .h file
#property (strong) IBOutlet NSLayoutConstraint *HUDSuperViewToHUDConstraint;
#property (strong) IBOutlet NSLayoutConstraint *HUDHeight;
Next, in the method in which you open the new viewController with the HUD remove the constraints so that there is no conflict when you first show the new view
[HUDSuperView removeConstraint:self.HUDSuperViewToHUDViewConstraint];
[HUDSuperView removeConstraint:self.HUDHeight];
After you've called [HUDSuperView layoutIfNeeded] for the first time, in the animation or wherever, add the constraints and call layoutIfNeeded again
[HUDSuperView addConstraint:self.HUDSuperViewToHUDViewConstraint];
[HUDSuperView addConstraint:self.HUDHeight];
[HUDSuperView layoutIfNeeded];
If you call these within an animateWithDuration it may even animate the appearance...cheesey
Hope this helps - it's probably more of a step toward the solution rather than the solution itself. Recommend minutes 17 and 53 in the AutoLayout by Example video too.
Steve

Resources