Collision when bounds of a view are changing - ios

I am working with UIDynamicAnimator, UICollisionBehavior and several moving UIImageView.
It looks like this when everything is working fine. Pictures are moving on the screen without superposition.
But after I changed the bounds of some views, the collision doesn't update as it should and it gives me this:
The collision doesn't work anymore as expected: views became superposed.
I am updating the bounds with this code:
[UIView animateWithDuration:2
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
CGRect newBounds = imageView.bounds;
newBounds.size.height = 500;
newBounds.size.width = 500;
imageView.bounds = newBounds;
}
completion:^(BOOL finished) {
[animator updateItemUsingCurrentState:imageView];
}];
updateItemUsingCurrentState: function doesn't seem to have effect on bounds changes... Am I right?

I succeed to do it by setting my animator variable to nil and declaring it and every linked animation behaviors again.
If someone else has a better way to do it, I am still listening. Thanks.

I ended up removing all behaviors from the dynamic animator without deallocing it and re-adding them again:
let behaviors = (self.dynamicAnimator.behaviors as NSArray).copy() as! [UIDynamicBehavior]
self.dynamicAnimator.removeAllBehaviors()
for behavior in behaviors {
self.dynamicAnimator.addBehavior(behavior)
}

Related

Animate image change of UIImageView when having an UIVisualEffectView above

My problem is as follows:
An UIImageView's view is going to be changed with an animation, like this:
[UIView transitionWithView:_backgroundArtworkImageView
duration:ANIMATION_DURATION
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
_backgroundArtworkImageView.image = blurredImage;
}
completion:nil];
It works perfectly fine as long as I don't have an UIVisualEffectView over the image view. If using the blur view on top, it results in no animation at all.
I've looked around for a bit and seen that snapshots of views could be something to look into, which also seems to be what Apple uses internally on iOS, for example when bringing up the app switcher; I'm not really sure how exactly to approach it though.
Got it working by taking a snapshot, append it to the superview, hide the real view, change it without animation and then fade the real view in with animation.
__block UIView *snapshot = [_backgroundArtworkImageView snapshotViewAfterScreenUpdates:NO];
[_artworkContainer insertSubview:snapshot belowSubview:_backgroundArtworkImageView];
_backgroundArtworkImageView.alpha = 0.0f;
_backgroundArtworkImageView.image = blurredImage;
[UIView transitionWithView:_artworkContainer
duration:ANIMATION_DURATION
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
_backgroundArtworkImageView.alpha = 1.0f;
}
completion:^(BOOL _) {
[snapshot removeFromSuperview];
snapshot = nil;
}];
Edit: I realized that the blur view was taking a small time to update, but to solve that a new snapshot of the resulting view could be taken and then animated with.

UIView not changing position in animateWithDuration

I have two UIViews (My bad it is a UIView and a UIButton) which I am animating at the same time. I originally had a view and a containerView which would animate just fine and worked like a charm.
Now only one of my UIViews will move/animate in animateWithDuration even though through debugging the frame of the other view says that it is in a position it is not.
CGRect rect = self.toggle.frame;
CGRect tabRect = self.tabButton.frame;
rect.origin.x = rect.origin.x - rect.size.width;
NSLog(#"%f Before",tabRect.origin.x);
tabRect.origin.x = tabRect.origin.x - rect.size.width;
NSLog(#"%f After", tabRect.origin.x);
[UIView animateWithDuration:0.3 animations:^{ // animate the following:
self.toggle.frame = rect; // move to new location
self.tabButton.frame = tabRect;
}];
NSLog(#"%f AfterAnimation", tabButton.frame.origin.x);
The toggle view moves fine, but the tabButton view does not animate or move. The strange thing is that both the "After" and "AfterAnimation" debugging code returns the same value, which suggests the frame has indeed moved. Is there a specific reason that this will not work when toggle is a UIView when it would work as a UIContainerView?
Note that if I remove the line
self.toggle.frame = rect;
tabButton will animate correctly, but if I move toggle, tabButton will not move regardless of whether it is first in the animation block or second.
Edit: I have tried moving them into separate blocks and to change the center point rather than the frame, to no avail. It seems that if the toggle view moves, the tabButton will not move.
Edit 2: The pictorial evidence.{
In the following screenshots tabButton bg is green and toggle bg is red.
Above: Initial position (toggle is off-screen) correct position
Above: The problem in question toggle is correct tabButton is not
Above: When self.toggle.frame = rect is commented out (tabButton correct, toggle not)
}
Edit 3: It's even worse than I feared.{
I have done a few more tests and even if I take the toggle change out of the animation block to make it an instant thing, the tabButton will still not animate. This makes me think the tabButton may just fundamentally dislike the toggle view and/or myself so will not move just to spite me.
}
Edit 4:{
If I change the tabButton animation to tabButton.frame = CGRectMake(10,10,100,100) the View snaps instantly to that location and animates back to its original position in the same time as the animation duration.
}
I better add more bookkeeping/TLDR information in case things aren't clear.
toggle is an instance of ToggleDraw which is a subview of UIView which I created.
tabButton is a UIButton which is part of my IB viewController and a property of the class
Both toggle and tabButton are subviews of self.view
The animations will work individually with no modifications to the logic of the rects but will not work if they are animated at the same time
toggle animation seems to take precedence over tabButton animation regardless of the order
I had a problem with the animation of an UIView created in IB (the animation didn't start from the current position of the view, and ended in the initial position).
All worked fine after sending layoutIfNeeded() to the underlaying view before the animation block:
self.view.layoutIfNeeded()
UIView.animateWithDuration(0.5) { () -> Void in
...
I think it is not a problem about a UIView Animation. Maybe your toggle posiztion is related to your tabButton. For a try, your can set toggle frame to a rect lick (10, 10, 100,100), then check the result.
I've created an example of what you describe and everything seems to work fine. This is what I used:
UIView *toggle = [[UIView alloc] initWithFrame:CGRectMake(320, 64, 100, 100)];
[toggle setBackgroundColor:[UIColor redColor]];
[self.view addSubview:toggle];
UIButton *tabButton = [[UIButton alloc] initWithFrame:CGRectMake(220, 64, 100, 100)];
[tabButton setBackgroundColor:[UIColor greenColor]];
[self.view addSubview:tabButton];
CGRect rect = toggle.frame;
CGRect tabRect = tabButton.frame;
rect.origin.x = rect.origin.x - rect.size.width;
NSLog(#"%f Before",tabRect.origin.x);
tabRect.origin.x = tabRect.origin.x - rect.size.width;
NSLog(#"%f After", tabRect.origin.x);
[UIView animateWithDuration:1 animations:^{ // animate the following:
toggle.frame = rect; // move to new location
tabButton.frame = tabRect;
}];
What I can suggest is to make sure that the code is being ran on mainthread:
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3 animations:^{
self.toggle.frame = rect; // move to new location
self.tabButton.frame = tabRect;
}];
});
Also take into account that the log you have after the animation code is incorrect as it won't run after the animation, but rather right next to asking for the animation.
If you want code to run after the animation you should use:
[UIView animateWithDuration:0.3 animations:^{
self.toggle.frame = rect; // move to new location
self.tabButton.frame = tabRect;
} completion:^(BOOL finished){
NSLog(#"Finished animating!");
}];
I have found a solution to the problem. If I initialise the UIButton (tabButton) programmatically rather than through the interface builder, both views will animate simultaneously.
This however seems very hacky to me, kind of like putting a bandaid over a missing foot and hoping it will sort itself out.
I could not work out the root cause of the problem but at least I could avoid the symptoms.
If anyone knows why the views would not animate when the button was made in the interface builder post an answer here, I am interested in knowing the reason behind this.
Thanks for your help everyone.

iOS: UIView Animation Refactoring

Whilst playing around with UIView animation, I came across a situation where I think some refactoring is needed:
The following views whose opacity are initially set to 0.0f.
Ex:
[UIView animateWithDuration:1.0f
animations:^
{
firstView.layer.opacity = 1.0f;
}
completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0f
animations:^
{
secondView.layer.opacity = 1.0f;
firstView.layer.opacity = 0.0f;
}
completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0f
animations:^
{
thirdView.layer.opacity = 1.0f;
secondView.layer.opacity = 0.0f;
}
completion:^(BOOL finished)
{
thirdView.layer.opacity = 0.0f;
}];
}];
}];
All 3 views are just subclass of UIView's, which are added as subviews of the main view.
This simply animates the opacity of the first view to 1.0f and then that of the second view, and then that of the third view.
Simple. Nothing special here.
My Question is:
What if I had more views, let say 100, that I wanted to perform the same action (same sequence of animation), this block of code would expand and expand.
So for the sake of refactoring and being adhered to good practice of writing code, I thought may be this could be done with less code via the use of a method and perhaps a loop.
Could you enlighten me on this in regards to refactoring; in addition, would dispatch_apply be useful here along with the refactoring process if a loop is needed?
If you wanted to animate 100 images, you would probably want to use 2 views and load alternating images into each one. I recently created a sample app on github that does exactly that:
Animating UIImages with cross-fade opacity changing

UIView animation with CGAffineTransformMakeScale changes size instantly with decimal number

- (void)startAnimation {
//reverse - shrinking from full size
if (_reversed == YES) {
//self.transform = CGAffineTransformMakeScale(1.0, 1.0);
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.transform = CGAffineTransformMakeScale(0.1, 0.1); //this line does it instantly
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
} else {
//non reverse - expanding from middle
self.transform = CGAffineTransformMakeScale(0.001, 0.001);
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.transform = CGAffineTransformMakeScale(1.0, 1.0);
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
}
The non reverse piece of code does work fine as I expect, however when I do the _reversed == YES bit, the transformation inside the animation block happens instantly. If I comment that line of code, then the view stays the right size, but if i uncomment it then it shrinks instantly but the alpha still does the fade animation. Why does this happen?
Edit: I figured out what happened but I don't know how to fix it. The view does do the animation, only the size of the view changes instantly but it still 'slides' into the centre as if it is shrinking (what you see is just a small rectangle sliding to the middle as if it's the top left corner of the object). If I scale the view to 2 first, then scale down to 1 the animation works fine, its only when going from 1 to a decimal number that it doesn't work. Also I used draw rect to create an object with core graphics and the transform problem affects that, but not the actual frame if a set background colour.
I was running into a similar issue, where the position of the view would suddenly jump before animating in a change of transform with CGAffineTransformMakeScale. I noticed that the size of the "jump" seemed to be proportional to the scaling that would occur later in the animation.
I could fix this problem by finding that in a viewWillLayoutSubviews() override, I was setting the frame of the view being animated. As a rule, don't set the frames of views that will have a non-identity transform. So in the viewWillLayoutSubviews() override, I set the view's bounds and layer.position instead and now the animation is smooth as silk.

View positioning issue

My ViewController's View has several subviews. On the user's action I add more subviews to it, and try to modify the positions of all the subviews. Only the positions of the newly added subviews are changing, the old ones are not.
If I change the positions of the old subviews without adding the new ones to my View, everything works as expected.
I've also tried changing their sizes. That appears to be working. The problem is only with the position.
Anyone encountered a similar problem? Thanks in advance!
Edit:
I`ve tried by changing the center and by changing the origin of their frames.
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
for (UIView *view in self.oldSubviews) {
view.center = center1;
}
for (UIView *view in self.newSubviews) {
view.center = center2;
}
} completion:^(BOOL finished) {
}];
When the views in the newSubviews aren't added as subviews, everything is fine. I've printed the array of self.view.subviews and there is no visible difference between the new and the old subviews.
This is what I tried with the frame:
for (UIView *view in self.oldSubviews) {
CGRect frame = view.frame;
frame.origin = CGPointZero;
frame.size = CGSizeMake(150, 300);
view.frame = frame;
}
Their sizes are changed, but they are not moved to the upper left corner.
Ok, found the issue, i guess. I was doing some settings in the viewWillLayoutSubviews method. I did not know, it is called after you add some subviews. Thanks for the help

Resources