Combining autolayout with core animation - ios

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).

Related

Custom frame by frame animation in iOS

Is there a way to perform an animation in iOS where at every frame I can do some calculations, update constraints and perform a layout (layoutIfNeeded())?
The animation I'm trying to achieve seems impossible to do using the standard way of doing a constraint animation, which internally works by calculating just two frames, the first and the last one, so layoutIfNeeded() is called just twice. In the intermediate frames, view positions are determined by simply interpolating between the first and last frames. I want to recalculate the layout at every frame.
You can always try to use CALayer animations, depending on your needs.
This is not a direct answer but answer pointing to a place where you can find answer: https://www.objc.io/issues/12-animations/

How do I layout a UITableViewCell using only frames (no Auto Layout)?

This may sound like a silly question, but most of my experience with iOS development has revolved around Auto Layout being prominent, and I'm curious now (for performance purposes) how I'd go about laying out a cell without Auto Layout and using only frames (and perhaps auto-resizing masks). Turns out this is very hard to Google due to the prominence of Auto Layout.
Essentially am I setting up and adding the subviews in the initWithStyle method of the UITableViewCell subclass?
Positioning wise, am I just relying on the bounds of the contentView, and then if I want one view beside another view, would I basically do newView.frame.origin.x = CGRectGetMaxX(otherView.frame) + spacing?
What happens when I rotate? I know I can watch for rotation in viewWillTransitionToSize, but how do I go about re-positioning the cells? Simply calling tableView.reloadData() would be both expensive and not do much as the cells are laid out in initWithStyle, correct?
I'm targeting iOS 8+.
Any insight would be truly appreciated.
Actually its
newView.frame.origin.x = CGRectGetMaxX(otherView.frame) + spacing + leftMargin;
// you have to include all the spacing, including margins
Tip:
Do not rely on bounds of the contentView by default it is set to maximum width of 320, i suggest you use main screen's frame for that.
AutoLayout is our friend and can save us a lot of time.
how do I go about re-positioning the cells?
If you're planning to do it programmatically, you need to setup the new height and width of the view after the rotation.
Calling tableView.reloadData() to update the views in the cell is not expensive, that's how it works. We don't have a choice but to live with it.
Then it will be better to write the frame calculation portion in viewDidLayoutSubViews.
I'm not entirely certain I understand your question. Please let me know if this makes sense:
Adding Subviews
To add subviews programmatically, it's actually best to do so in the override of layoutSubviews by checking if the contentView has subviews already added and calling a method to add them if needed. This way, you can load the table cell via either a storyboard/nib or using initWithStyle. Alternatively, you can use a shared commonInit method that you call in both initWithCoder and initWithStyle.
Layout of Subviews
To layout your subviews programmatically, you override layoutSubviews (and remember to call super) and set up the frames in that method. This method will be called whenever the view changes size (rotation, initial presentation, etc) and will always include the current bounds of the content view. To calculate your subview frames, you can do so as you've suggested, but you need to define the subview frame and then set the frame for the view:
frameB.origin.x = CGRectGetMaxX(viewA.frame) + spacing;
viewB.frame = frameB;
And keep in mind that this will not correct for the width of frameB. Therefore, you might want to consider using CGRectDivide() instead.
Scrolling performance
That said, the big performance hit when using auto layout in table view cells is not auto layout itself, but the calculations that are done many times on the same set of data to lay it out - all on the main thread. Such as calculating frames for all the text to layout a bunch of labels relative to one another or something that uses drawRect rather than drawing to an image bitmap on a background thread and then loading the image in to a UIImageView once the drawing is complete. Without knowing what data you are displaying it is hard to guess what is causing the performance issues, but you should be sure to use profiling to determine if you are actually improving performance. Additionally, you may need to consider moving some of the number crunching and/or image rendering onto a background thread.
Good luck!

UILabel redraws the view. How to avoid this behavior?

I have a viewController with three subviews.
Also I use AutoLayout and size classes.
These views are animated and change location and size.
After the animations I update a label but the whole view is redrawn so each view is in their initial position and size. This shouldn't happen.
As in Apple developer reference that says:
"The default content mode of the UILabel class is
UIViewContentModeRedraw. This mode causes the view to redraw its
contents every time its bounding rectangle changes. You can change
this mode by modifying the inherited contentMode property of the
class."
It doesn't seem clear to me how to modify the -contentMode- in order to update that label and leave the view -as is-. Can anyone give me a clue?
Thanks in advance.
It sounds like you may be using autolayout to lay out your view (e.g., via constraints in IB), but then you're manipulating your views' frames directly for your animations - is this the case? If so, you should instead be animating the constant values of your constraints, or possibly the transforms of your subviews.
If you manipulate frames directly in a view which uses autolayout, your changes will be over-written the next time the system lays out your view (e.g. after a label's text changes).
You have 3 options to overcome your issue -
Stop using AutoLayout in your Storyboard/Xib.
Not a great solution
Animate changes to the transform property of your subviews. e.g. myView.transform = CGAffineTransformMakeScale(2.0,2.0);
Useful for presentation and dismissal animations, but mixing AutoLayout and transforms has some issues pre-iOS 8.0
Add IBOutlets for the constraints you need to change in your animations. Animate changes to the constant values of those constraints.
Most robust approach but can lead to a lot of properties and code for complex animations
Try contentMode = UIViewContentModeCenter. This should prevent the redraw you're seeing. Although that may not be the contentMode you want.
Option 2 is to use CATextLayer to draw the label. It will resize very nicely with animation, but it's a lot more work to set up.

Handling interface rotation in iOS 7

Very simple question, but I'm asking as there seems to be a lot of conflicting views and I've been completely unable to find a definitive answer, let alone a modern one.
I use Auto Layout for 99% of my handling of the user changing from portrait to landscape or vice-versa in a UIViewController. Works great. But sometimes, I have to rely on frames still for one reason or another.
How would I best handle this?
You have willAnimateRotationToInterfaceOrientation, willRotateToInterfaceOrientation, the NSNotification methods with checking status bar, and I'm sure there's some others.
If I want to change the position of a view when the user rotates, which method should I be changing the frame in? And is it best to do it with a simple setFrame: or should I be using autoresizing masks?
Since iOS6, you should not be using willRotateToInterfaceOrientation: and didRotateFromInterfaceOrientation:. These are only called on the front-most presented view controller, and will not be called on others. This means if you have a pushed view controller or a presented view controller, all others will not layout correctly.
Instead, you should always use viewWillLayout and viewDidLayout to handle rotation. They are both called inside an animation block, so anything you do which is animatable, will be animated.
For positioning views, you can either set the frames yourself, or use auto-layout constraints and adjust the constraints in viewDidLayout. If you go the auto-layout route, never remove and add constraints. Use the same constraints as much as possible and just adjust their constant values.
When I'm changing the main view frame, I typically adjust the frame in willRotateToInterfaceOrientation if I need to. Then I adjust any subviews by overriding layoutSubviews for my main view.
I don't know that this is a definitive answer, though - I don't think there really is a definitive answer - it depends on how your application is structured.
wilLRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are best used for stuff you need to do before and after rotation, respectively (for example, disabling user interaction before the rotation begins, and reenabling it after). Everything else should be done in layoutSubviews if possible.
Autoresizing masks are useful sometimes, but I usually lay everything out manually to avoid any surprises when things change between iOS releases (as they often do).

Is there an easy way to reposition views in iOS without manually changing origin/size/center?

I find myself often having to reposition subviews of a view after hiding or showing one of them. The way I'm doing this is by programmatically changing a view frame's origin and/or size, or its center. But is there an easier way I'm missing? Is there a way to do it with Autosizing masks?
I don't think there's any automatic way of doing this. You could probably get clever about how you do it programmatically (e.g. if you used a set of sequential tag identifiers, you could loop through and calculate the height of the previously visible tag to calculate the origin of the next subview; or if there are a group of subviews that are always going to move together, you could put them in a container UIView and thus move a whole bunch of them by just moving their container view; etc. ... it depends upon how they're laid out and which fields might be hidden).
This won't help you for now, but check out WWDC 2012 session 202 for a discussion of a relevant improvement in iOS 6.

Resources