Animation UITableView Width Constraint - ios

Inside a sidebar menu view controller (based on SWRevealViewController), I'm using two tables to simulate two columns. So when user clicks on an option that has sub-options, the other table view controller loads them. (See Image)
Now the problem is that not all options in the first table actually has sub-options. Thus, I need to animate the first table (table with main options) so it takes full superview width when there is no sub-options to show on the other table. Only clicking on an option that has sub-options should animate the width so the two columns appear side by side. (See Images)
(Left: Intended behaviour, Right: Current behaviour)
The logic for showing sub-options and reloading tables are done and it's working perfectly. The only problem I have is with the animation. To animate the table widths I thought of setting the auto layout for the labels inside cells as follow: H:|-8-Label-8-|, V:|-8-Label-8-|, and for the both tables: H:|LeftTable(LeftTableWidth)-0-RightTable| with creating an outlet for the LeftTableWidth constraint. So when I need to animate them I do this:
[self.view layoutIfNeeded];
self.subTableWidthConstraint.constant = width;
[UITableView animateWithDuration:0.45f
animations:^{
[self.view layoutIfNeeded];
}];
Now the table widths are getting correctly animated. However, the labels inside the cells are not being animated from the initial width values to the final values. Instead, their new values are immediately set, the moment the constraint gets changed (before the tables complete their animation). I've even tried to remove everything other than:
self.subTableWidthConstraint.constant = width;
and they're still get their new values, without even call [self.view updateConstraints].
So... any clue what I did wrong?
Thanks all!

I have the same issue with animation - layoutIfNeeded for all animation-related views in animation block helps me.
self.subTableWidthConstraint.constant = width;
[self.subTable setNeedsUpdateConstraints];
[UITableView animateWithDuration:0.45f
animations:^{
[self.subTable layoutIfNeeded];
[self.mainTable layoutIfNeeded];
[self.view layoutIfNeeded];
}];

Just figured it out! Since I'm using RTL language, I needed to set the mode of the labels to Right instead of the default Left value.

Related

Auto-Layout Issues: iOS 7 vs iOS8

I am trying to create a collapsible toolbar that works like this (running in iOS 7 -- ugly colors etc. for visualization purposes):
However, when I run the code in iOS 8, this is what happens:
I have set up a constraint system that is based on the following:
A centering view (not shown) keeps the toolbar in the screen middle.
A sizing view is adjusted to collapse the toolbar. The sizing view is anchored to the right of the centering view(via a trailing constraint).
A container view holds the actual content of the toolbar. It is anchored to the right of the sizing view (also via a trailing constraint).
Various content views are contained in the container view. They have no constraints. The default constraints applied by the system should be width, height, top, left, which ensures that they keep their relative positions in the container view.
The collapsing of the toolbar is achieved as follows:
- (IBAction)showLess:(id)sender {
self.widthConstraint.constant = 50; // adjust this number for collapse / expand
[UIView animateWithDuration:0.3 animations:^{
[self.centeringView layoutIfNeeded]; // trigger animation
}];
}
Which adjusts the width of the sizing view.
Problem:
iOS 8 seems to behave as if I had left anchored the content view, but this is not true.
I would sincerely appreciate:
An explanation as to why iOS 8 would have such a radically different interpretation of the given (reasonably simple) constraints.
A pointer as to how I can get the intended behavior in iOS 8
Source code available here (updated version that works in iOS 8).
UPDATE:
The issue was solved with answers from Stack-overflow. Basically, the right answer is this, but it was nicely summarized in this answer.
The difference between iOS7 and iOS8 is not in the way the constraints are interpreted, but in the way that update commands are trickled down through the view hierarchy.
When I implemented the behavior first in iOS 7, I noticed that the animation would only work properly if I called layoutIfNeeded on the parent view of the sizing view (i.e. on centering view). In iOS 7 this apparently trickled down the view hierarchy automatically. In iOS 8, this is not the case. You have to manually invalidate the view whose constraints have changed with setNeedsLayout, and then update the layout with layoutIfNeeded. My solution in the updated code looks like this:
- (IBAction)showLess:(id)sender {
self.widthConstraint.constant = 50;
[self.sizingView setNeedsLayout]; // *** THIS LINE IS NECESSARY TO MAKE THINGS WORK IN iOS 8
[UIView animateWithDuration:0.3 animations:^{
[self.sizingView layoutIfNeeded]; // trigger animation
}];
}
I hope this helps others who are also stuck on this forward compatibility issue.
The issue was solved with answers from Stack-overflow. Basically, the right answer is this, but it was nicely summarized in this answer. The difference between iOS7 and iOS8 is not in the way the constraints are interpreted, but in the way that update commands are trickled down through the view hierarchy. When I implemented the behavior first in iOS 7, I noticed that the animation would only work properly if I called layoutIfNeeded on the parent view of the sizing view (i.e. on centering view). In iOS 7 this apparently trickled down the view hierarchy automatically. In iOS 8, this is not the case: You have to manually invalidate the view whose constraints have changed with setNeedsLayout, and then update the layout with layoutIfNeeded. My solution in the updated code looks like this:
- (IBAction)showLess:(id)sender {
self.widthConstraint.constant = 50;
[self.sizingView setNeedsLayout]; // *** THIS LINE IS NECESSARY TO MAKE THINGS WORK IN iOS 8
[UIView animateWithDuration:0.3 animations:^{
[self.sizingView layoutIfNeeded]; // trigger animation
}];
}
I've updated the question to include the answer, but in response to #unmircea I am posting a separate answer, as well.

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.

how to animate views change views hierarchy using autolayout?

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.

UIScrollView renders different when switching back and forth using UITabBar

I've been struggling with this problem for a couple of days now and I can't seem to find any concrete solution online, so here it goes...
The scenario is simple: I want the table view to be expanded (i.e. not scrollable) in a scroll view, and therefore I need to resize and move the view(s) within the scroll view. This I have achieved quite easy by sub classing the UIScrollView and re-implemented the layoutSubviews method (see below).
Implementation of the layoutSubViews:
- (void) layoutSubviews
{
[super layoutSubviews];
//Resize the UITableView containing all the rows (i.e. it should not scroll within the tableview)
UITableView *bt = [self.subviews objectAtIndex:0];
//1 - Set the height of the table cells
//2 - Calculate the total height for the tableview (i.e.: numberOfRows*rowHeight)
bt.frame = CGRectMake(bt.frame.origin.x, bt.frame.origin.y, bt.frame.size.width, ([bt numberOfRowsInSection:0]*bt.rowHeight));
//Move down the note text view, so that it don't overlaps the table.
UITextView *note = [self.subviews objectAtIndex:1];
note.frame = CGRectMake(note.frame.origin.x, (bt.frame.origin.y + bt.frame.size.height)+15, note.frame.size.width, note.frame.size.height);
//Set the content size to the scroll view.
//Note: If the note is hidden, then we should not include it (the same goes for the padding between note and table)
[self setContentSize: CGSizeMake(self.contentSize.width, bt.frame.size.height + note.frame.size.height)];
UIEdgeInsets insets = UIEdgeInsetsMake(0.0f, 0.0f, /*padding#bottom*/50.0f, 0.0f);
[self setContentInset:insets];
[self setScrollIndicatorInsets:insets];
}
The implementation above solves my problems and renders perfectly. Try to think of it as rendering an recipe where each row in the table view is an ingredient - that is what I'm aiming for.
My app is utilizing the UITabBar and everything renders and behave fine except for the case when I scroll down a bit in the scroll view and then switch to another tab and back. The scroll view is then somehow altered and it is no longer possible to scroll to the top (depending on how much you've scrolled down before switching tab) and is also rendered somewhat strange.
Step 1: Step1.png (see URL below)
Scrolled down to be able to see the textview below expanded tableview
Step 2: step2-switched-back.png (See URL below)
Switching to Second tab and back to First, causing odd rendering behavior and scrolling behavior where it is no longer possible to reach the first row in tableview by scrolling the scroll view.
I've created an example project since I believe the code talks for itself, and I hope someone out there can see through this and point out if I've done something wrong or if there are any way to get round this.
Project & screenshots available at: http://eddiex.se/tmp/demo/
Thanks in advance!
I've now come up with an alternative solution that will give me the same rendering as I was aiming for with having a UITableView expanded inside a UIScrollView.
Solution
I removed the UIScrollView (Editor->Unembed) and set the size of the UITableView to cover whole screen (in UI editor) and I, visually in UI editor, moved the UITextFieldView in to the UITableView (as footer view).
The tiny shadow (gradient view) below the last row in the expanded table was also easy to implement within this solution since no change was needed; UITableView's delegate implemented viewForFooterInSection method, which returned a simple gradient view.

Resources