I have implemented a custom segue class for emulating the push transition without navigation bar. The trick is to take to snapshots, add them to the view, replace the viewController, move the snapshots, and finally remove them. This emules a horizontal movement of the viewController, but actually only two UIImagesView are moved.
The following code implements this.
self.destinationImageView.frame = offsetFrameD;
self.sourceImageView.frame = offsetFrameS;
//ViewController replacement
[self.sourceViewController presentModalViewController:self.destinationViewController animated:NO];
//Overpose the snapShot who will simulate the transition between vies.
[destinationView addSubview: self.sourceImageView];
[destinationView addSubview: self.destinationImageView];
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.destinationImageView.frame = self.finalFrameD;
self.sourceImageView.frame = self.finalFrameS;
}
completion:^(BOOL finished) {
[self.sourceImageView removeFromSuperview];
[self.destinationImageView removeFromSuperview];
}];
This code worked with iOS 7. However, when using iOS 8, it seems like the animation is not performed. The destinationImageView and sourceImageView are directly moved to the finalPosition (WITHOUT ANIMATING) and the completion block is not called, so both UIImageView are not finally removed.
Does anyone knows how the animation should be performed with iOS 8?
You should adjust the auto layout constraints and not the frames position. Have a look at this solution. I hope it helps.
UIView transitionWithView & setting the frame no longer works iOS8
Use the following line of code before the start of the animation:
[UIView setAnimationsEnabled:YES];
Related
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.
I'm currently testing my apps for the release of IOS 8. I noticed that after I performed an animation block, the animation resets if I update the text of any label. I ran a simple example with one method shown below. Running this example results in the following:
Clicking myButton the first time- animation runs but resets when the label text is changed.
Clicking myButton the second time - animation runs but does not reset to original position.
It seems like this happens because the label text doesn't change. If I completely remove the line updating the text, this also stops the animation from resetting at the end.
I would like to fix this so that when the method runs, the label text can be updated without resetting the animation.
- (IBAction)move:(id)sender {
[UIView animateWithDuration:0.4 delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.myButton.center = CGPointMake(200, 300);
}completion:^(BOOL finished){
if(finished){
self.myLabel.text=#"moved";
}
}];
}
This problem can be caused by having Auto Layout set on the UIView. Strictly speaking, if you're using Auto Layout, then you shouldn't animate the absolute position of objects -- you should animate their constraints instead.
Changing the label text once your animation is underway triggers a layout refresh, and iOS shuffles everything around to comply with the original view constraints. (I suspect this is a behavioural change from iOS7).
Quick fix: un-check Auto Layout on the View, and this should work as expected.
Try this. Put the desired animation in the finish block also.
- (IBAction)move:(id)sender {
[UIView animateWithDuration:0.4 delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.myButton.center = CGPointMake(200, 300);
}completion:^(BOOL finished){
if(finished){
self.myLabel.text=#"moved";
self.myButton.center = CGPointMake(200, 300);
}
}];
}
I have UIScrollView subclass. Its content is reusable - about 4 or 5 views are used to display hundreds of elements (while scrolling hidden objects reused and jumps to another position when its needed to see them)
What i need: ability to automatically scroll my scroll view to any position. For example my scroll view displays 4th, 5th and 6th element and when I tap some button it needs to scroll to 30th element. In other words I need standard behaviour of UIScrollView.
This works fine:
[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
but I need some customisation. For example, change animation duration, add some code to perform on end of animation.
Obvious decision:
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self setContentOffset:CGPointMake(index*elementWidth, 0)];
} completion:^(BOOL finished) {
//some code
}];
but I have some actions connected to scroll event, and so now all of them are in animation block and it causes all subview's frames to animate too (thanks to few reusable elements all of them animates not how i want)
The question is: How can I make custom animation (in fact I need custom duration, actions on end and BeginFromCurrentState option) for content offset WITHOUT animating all the code, connected to scrollViewDidScroll event?
UPD:
Thanks to Andrew's answer(first part) I solved issue with animation inside scrollViewDidScroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[UIView performWithoutAnimation:^{
[self refreshTiles];
}];
}
But scrollViewDidScroll must (for my purposes) executes every frame of animation like it was in case of
[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
However, now it executes only once at start of animation.
How can I solve this?
Did you try the same approach, but with disabled animation in scrollViewDidScroll ?
On iOS 7, you could try wrapping your code in scrollViewDidScroll in
[UIView performWithoutAnimation:^{
//Your code here
}];
on previous iOS versions, you could try:
[CATransaction begin];
[CATransaction setDisableActions:YES];
//Your code here
[CATransaction commit];
Update:
Unfortunately that's where you hit the tough part of the whole thing. setContentOffset: calls the delegate just once, it's equivalent to setContentOffset:animated:NO, which again calls it just once.
setContentOffset:animated:YES calls the delegate as the animation changes the bounds of the scrollview and you want that, but you don't want the provided animation, so the only way around this that I can come up with is to gradually change the contentOffset of the scrollview, so that the animation system doesn't just jump to the final value, as is the case at the moment.
To do that you can look at keyframe animations, like so for iOS 7:
[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(index*elementWidth, 0)];
}];
} completion:^(BOOL finished) {
//Completion Block
}];
This will get you two updates and of course you could use some math and a loop to add up a lot more of these with the appropriate timings.
On previous iOS versions, you'll have to drop to CoreAnimation for keyframe animations, but it's basically the same thing with a bit different syntax.
Method 2:
You can try polling the presentationLayer of the scrollview for any changes with a timer that you start at the beginning of the animation, since unfortunately the presentationLayer's properties aren't KVO observable. Or you can use needsDisplayForKey in a subclass of the layer to get notified when the bounds change, but that'll require some work to set up and it does cause redrawing, which might affect performance.
Method 3:
Would be to dissect exactly what happens to the scrollView when animated is YES try and intercept the animation that gets set on the scrollview and change its parameters, but since this would be the most hacky, breakable due to Apple's changes and trickiest method, I won't go into it.
A nice way to do this is with the AnimationEngine library. It's a very small library: six files, with three more if you want damped spring behavior.
Behind the scenes it uses a CADisplayLink to run your animation block once every frame. You get a clean block-based syntax that's easy to use, and a bunch of interpolation and easing functions that save you time.
To animate contentOffset:
startOffset = scrollView.contentOffset;
endOffset = ..
// Constant speed looks good...
const CGFloat kTimelineAnimationSpeed = 300;
CGFloat timelineAnimationDuration = fabs(deltaToDesiredX) / kTimelineAnimationSpeed;
[INTUAnimationEngine animateWithDuration:timelineAnimationDuration
delay:0
easing:INTULinear
animations:^(CGFloat progress) {
self.videoTimelineView.contentOffset =
INTUInterpolateCGPoint(startOffset, endOffset, progress);
}
completion:^(BOOL finished) {
autoscrollEnabled = YES;
}];
Try this:
UIView.animate(withDuration: 0.6, animations: {
self.view.collectionView.contentOffset = newOffset
self.view.layoutIfNeeded()
}, completion: nil)
I have the following code to animation some view in my app:
void (^animate)() = ^() {
CGRect leftFrame = centerFrame;
leftFrame.origin.x -= centerFrame.size.width;
newViewController.view.frame = centerFrame;
oldViewController.view.frame = leftFrame;
};
if (animated) {
[UIView animateWithDuration:0.3f
delay:0.0f
options:nil
animations:animate
completion:^(BOOL finished){}];
} else {
animate();
}
This animates correctly on iOS 6, however on iOS 7 there is no animation. Oddly the code inside the block does get called and the view does update, just without the animation duration taken into account.
Is there a reason why this block isn't getting called?
I had the same issue: an animation block that works like a charm in iOS6 is executed without the animation in iOS7.
Not sure if this might help you but I moved the animation trigger to viewDidAppear: and now its being animated.
So for you this might look like this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
animate();
// do your other stuff here...
}
Your animation may be colliding with another competing animation block, perhaps one called by UIKit during a transition. Instead of passing nil to the animation options, use:
UIViewAnimationOptionOverrideInheritedOptions | UIViewAnimationOptionBeginFromCurrentState
and see if this helps.
I had exactly the same regression between iOS6 and iOS7.
In my case, it was due to the fact that animateWithDuration: was called before the UIView it animates appeared on screen.
In iOS7 it appears that you shouldn't call animateWithDuration: before -(void)viewDidAppear:(BOOL)animated is called on the controlling UIViewController.
I was having the same issue. What it turned out for me is I needed to reference a different part of the UITableViewCell I was animating than I did with iOS 6. It might be a similar issue for you.
It is probably autolayout in iOS 7 which is giving you troubles. I just had the same problem. Instead of animating the frame, you need to animate the constraints.
See Are NSLayoutConstraints animatable? for how to do it.
try using :
[UIView setAnimationsEnabled:YES];
I have two UIImageView filling the view controller, the first is filling the top half, and the second for the bottom half. I set them directly in the storyboard file.
In the viewDidLoad method, i am setting code in order to perform animation for the two UIImageView, so that it will look like a basket which gets opened (the first UIImageView moved to the top until it gets out of the view, and the second UIImageView moved to the bottom of the view until it gets out of the view).
This is my code so far:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGRect basketTopFrame = self.basketTop.frame;
basketTopFrame.origin.y = -basketTopFrame.size.height;
CGRect basketBottomFrame = self.basketBottom.frame;
basketBottomFrame.origin.y = self.view.bounds.size.height;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelay:1.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
self.basketTop.frame = basketTopFrame;
self.basketBottom.frame = basketBottomFrame;
[UIView commitAnimations];
}
But the behavior is different than i was expecting, the top frame is not moving, and the bottom frame is animated from top left to bottom (origin position, and not moving out of the view).
This is UIImageViews position as i set in the storyboard:
This is the UIImageViews when i launch the app:
And this is the UIImageViews when the app finish the animation(viewDidLoad):
Please note that this code works in Xcode 4.2, but since upgrading to Xcode 4.5 and using Storyboard, i begin getting this issue. Thanx in advance.
You may have auto layout enabled on your storyboard. In this case the view components will not have a valid frame value at viewDidLoad, so your code won't work.
You can disable auto layout by selecting the file inspector in the storyboard and unchecking "Use Autolayout".
In any case, that is the wrong method to be starting an animation from. Try moving the code to viewDidAppear.
Once you have "AutoLayout" turned off and if you are targeting IOS 4 and later than block based UIView animation is recommended by Apple as it simplifies the process and you can do things in one line. Please check UIView Reference for further information.
In your case , whether you plan to do the animation in ViewDidLoad / ViewWillAppear, it can be done in the following way using block based animation:
CGRect basketTopFrame = self.boxTop.frame;
CGRect basketBottomFrame = self.boxBottom.frame;
[UIView animateWithDuration: 0.5 delay:0.1 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.boxTop setFrame:CGRectMake(basketTopFrame.origin.x, -basketTopFrame.size.height, basketTopFrame.size.width, basketTopFrame.size.height)];
[self.boxBottom setFrame:CGRectMake(basketBottomFrame.origin.x, self.view.bounds.size.height, basketBottomFrame.size.width, basketBottomFrame.size.height)];
} completion:^(BOOL finished) {
// TODO : Do any additional stuffs.
}];