I'm having trouble getting my view to lay itself out properly after modifying a second constraint (the first works fine). I'm modifying one constraint, animating it, and then modifying another constraint in the completion block (without animation).
My reason for doing this is I don't want the second constraint change to affect the animation, but I want it to instantly take effect as soon as the initial animation completes.
The problem is the second constraint change is simply not taking effect, no matter what I try.
Here's what I'm trying (note the problem is only when toolbarVisible is YES, as I perform both animations up front when it is NO):
if ( !toolbarVisible )
self.containerViewBottomSpace.constant = toolbarVisible ? 60.f : 0;
self.toolbarBottomSpace.constant = toolbarVisible ? 0 : 60.f; // this one works fine
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.3f animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
if ( toolbarVisible )
self.containerViewBottomSpace.constant = 60.f; // this one is not taking effect
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}];
The above method takes place inside an observeValueForKeyPath:... method. I've tried wrapping the whole above code in a dispatch_after block, but that has an even worse effect in that it is always one step behind (i.e. containerView is always in the opposite position as it should be) as if the second constraint modification isn't taking effect until the next animation.
Similarly, if I push another view onto the navigation controller (which is embedded inside the containerView), the layout corrects itself. It's as though it's just ignoring my command to lay itself out again for some reason.
Actually I just noticed that even if I make the second change initially and skip the completion block altogether, it doesn't appear to be animating the second constraint change in all cases. Something is fishy.
Update: I was using dispatch_after incorrectly thinking it accepted an NSTimeInterval. After giving it a proper dispatch_time_t and putting it inside the completion block, the layout is now sometimes updating. However, it's very flaky and this seems very hackish so I feel as though I'm missing something. If anyone knows more about this please enlighten me and I'll give you the answer.
Update 2: I decided to try logging the height of the element I am trying to change in viewDidLayoutSubviews. Interestingly, it gets called twice, first with the previous value and second with the correctly adjusted height. So I guess it is at least partially laying things out correctly, but for some reason the tableView that is inside the containerView is still not resizing itself properly. I'm not sure what I'm missing.
I'd first breakpoint and make sure you're not hitting the animation code a second time during the animation duration. If it's in observeValueForKeyPath you could hit the animation multiple times and it causes some inconsistency.
You also shouldn't need to call setNeedsUpdateConstraints or layoutIfNeeded. Setting the constant should be enough.
I'd also make sure that you've got the constraints wired up correctly and toolbarVisible is true.
Related
Suppose that I need to do something whenever a view's width or height changes. When not using autoLayout, I can do this by implementing layoutSubviews. But I find that this function is not always called when using autoLayout.
Right now, I'm using the following code where something could possibly cause the view's frame to change:
[self setNeedsLayout];
[self layoutIfNeeded];
and do what I must do in layoutSubviews. But I wonder if this is the best way. Is there a simpler solution, for example a callback function I can use?
You should not have to call both methods at once. Calling setNeedsLayout schedules the view for layouting at the next layouting run, you don't know when this will happen.
On the other hand, layoutIfNeeded layouts the view and all it's subviews immediately. Then layoutSubviews should be called for all the views in the hierarchy starting with the one on which you called layoutIfNeeded.
If you changed your constraints and you need to update your view you should call layoutIfNeeded probably in an animation block so that the views don't "jump", using something like this
// Update constraints ...
[UIView animateWithDuration:0.3 animations:^{
[view layoutIfNeeded];
}];
If you don't change your frame "by hand" (meaning using setFrame:) and update your view only through constraints, after which you call layoutIfNeeded, layoutSubviews should be called every time.
If you do use setFrame:, which you should not do if you are using auto layout, then you can try using an observer.
I think you should check viewDidLayoutSubviews for a callback. Apple's documentation says
Called to notify the view controller that its view has just laid out
its subviews.
Have a closer look here: https://developer.apple.com/library//ios/documentation/UIKit/Reference/UIViewController_Class/index.html
Happy coding!
Z.
Update:
I've answered the wrong question before! :)
For UIViews you can override bounds' setter setBounds (not frame/setFrame, that's a value derived from bounds), and in there you'll get notified of any size change.
Don't forget to call [super setBounds:bounds] in your implementation :)
Z.
So here's the situation:
I have let's say 20 views who are all subviews of the same view. Now I want to remove and add new views in an animated matter.
I previously did this by using regular UIView animations and fading them out or in respectively. The problem is though that the animation isn't flawless when the new subview who is to be inserted overlaps with the subviews who are fading out. So I tried using the +[UIView transitionFromView:toView:...] animation block to make the animation cross dissolve. This works well, animation looks good.
This basically solves my issue. The only problem is that it might be that I want to replace 2 subviews with one bigger subview. This doesn't really work out as I can't just pas nil to the transition method.
So my question basically is how I can simultaneously cross dissolve multiple subviews no matter how many views are animated?
Thanks for your help!
Have you tried putting the views to remove inside a transparent bigger view? So you just call transitionFromViewToView using the container with the 2 little ones inside and the big one that is entering?
You can use + [UIView transitionWithView:duration:options:animations:completion:], passing the common superview (usually self.view of your view controller) to perform all animations at once.
Excerpt from the doc:
This method applies a transition to the specified view so that you can
make state changes to it. The block you specify in the animations
parameter contains whatever state changes you want to make. You can
use this block to add, remove, show, or hide subviews of the specified
view. If you want to incorporate other animatable changes, you must
include the UIViewAnimationOptionAllowAnimatedContent key in the
options parameter.
I'm having an issue with my app's layout which is a bit tricky to explain. When the app first starts, this is what I'm showing:
After the user taps "Create Profile", I animate those buttons and show a registration form instead:
Needless to say, the buttons are now not in their "natural" position. Note, however, that the text fields are - that's where I have placed them in the storyboard, but when the view first loads I hide them. The animations are working great, but then I needed to scroll my view up when the user gives focus to a text field and the keyboard hides the field. The details of how to trigger the bug are a bit hard to explain, so I managed to boil it down to what seems to be a redraw event, except that it isn't... Let me try and explain that.
First of all, here's what happens when the keyboard is about to show:
- (void)keyboardWillShow:(NSNotification*)notification
{
CGRect frame = self.view.frame;
frame.size.height -= 1;
self.view.frame = frame;
}
Notice that this is a test only, probably the minimal I found that would still trigger the bug. All it does is resize the view. I would expect the view to be exactly as it was, with one less pixel, right? Wrong. Here's what I get:
That is, all elements returned to their "natural" positions, completely ignoring their previous positions. My first guess was that it would seem that the window is redrawing, so I tried this:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
NSLog(#"View was drawn");
}
But this only triggers when the window is first drawn, not when this strange behaviour happens. To understand what I mean by "natural position", here's what I have in storyboard:
You can also see that I'm not using constraints and the underlying structure of my view:
The full code for the entire setup is quite extensive, so pretty much not practical at all to show. However, how I animate the subviews resumes to changing their frame as I did in keyboardWillShow, and setting their positions to whatever I need.
Any ideas?
So you're using storyboards and you have "Use AutoLayout" set to false for your entire storyboard?
In that case your app is using "struts and springs" style placement rules. You're going to have to debug those.
It's a bit hard to describe everything in a view controller in a post. It's easier to go over it in IB. Perhaps you can write a utility function that logs all the autoresizingMask values for the views in your view controller, and go over those, and perhaps post them here describing the autoresizingMask values for each view in your original post.
I have started using CoreAnimation in my new app, i use CA to make some cool animations. But i'm new to objective C and it's difficult to me and i'm facing this big problem :
i have made a custom UIView : customView, i'm using a xib to get a user interface and set IBOutlet, custom view is a simple small view with an UIImageView inside.
In my view controller i have made a loop that insert severals customView, in this loop i set both a CAnimation for the view and for the imageView :
for (int i = 0 ; i < 8 ; i ++ )
{
CABasicAnimation *balanceAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
// ... set Animation here...
[customView.myImgView.layer addAnimation:balanceAnimation forKey:nil];
CABasicAnimation *moveUp3Bis;
// ... setting animation here...
moveUp3Bis.repeatCount = 1;
[customView.layer addAnimation:moveUp3Bis forKey:nil];
}
I call this loop as long as the view controller is current controller after 10 sec delay
So the views gets animate, move, and go beside the main window, but after a long time animations get slower and slower...
Of course i have understood that my loop add to the stack too many customView, the view are not released (may be because animations is still on ?)
I thought that views would be released after a while but i think the view will be moving for ever.
May be i need to set a pointer to my view and release it after a delay ?
I'm sure CA have planned something for case like that ?
But i do not know what can i do ?
Thank you for any help.
Have you tried setting -[CABasicAnimation removedOnCompletion]?
It looks like it's default value is YES, so I don't think this is your problem, but it's worth looking into.
Have you tried using Profile to see what's slowing everything down?
From the code snippet that you added is hard to tell what are you doing with your views but you definitely must remove them form their parent view otherwise they won't be released.
So what I suggest is that you should try to remove the views before you start the loop and just so you have a cool animation, you should remove a view only if is not animating, for this you will have to add a key string for your animation using your current method addAnimation:balanceAnimation forKey:nil instead of setting it to nil and check the view's layer for animationKeys if the array is empty or nil then the animation is completed if not is still animating.
I have a placeholder UIImageView (loaded from a .xib) which I want to load with a picture of the back of a card and then flip to the front image.
[cardView setImage:backCardImage];
[UIView transitionWithView:cardView
duration:1
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[cardView setImage:frontCardImage];
}
completion:NULL];
The problem is that the initial image it starts with is not backCardImage, instead, it uses the no image (i.e. blank image) from my .xib file. If I had the backCardImage loaded in the .xib and only call transitionWithView, I believe it would work properly, but I would really like to have the two calls (setImage, and transitionWithView) in the same method. Is there any way to do this? Do I need to call something between the two?
I tried using UIViewAnimationOptionBeginFromCurrentState, but that doesn't work either.
The cardView won't draw, even if you put in setNeedsDisplay, until the next drawing cycle, so you need to set that image in another method first, or move the animation to another method, and call it with performSelector:withObject:afterDelay:. Even a delay of 0 will work. I don't think the animation looks very good though, because you only see the back image for an instant before the flip (a delay of .1 or .2 seconds looks better I think). Why not set the back image earlier? What's the purpose of a placeholder image in this context?