Fake push and pop animation when replace UIView - ios

For some reasons, I have to use addSubview and addChildViewController to replace view instead of push/pop view controller. The problem is that I want to fake exactly the animation when changing UIViewController (push/pop).
Here is my try:
In RootViewController.m
// switch controller view
currentController = nextViewController;
[self addChildViewController:currentController];
[self.view addSubview:currentController.view];
// pop - move down
[currentController.view setFrame:CGRectMake(self.view.frame.size.width, 0, currentController.view.frame.size.width, currentController.view.frame.size.height)];
[UIView animateWithDuration:0.5
animations:^{
[currentController.view setFrame:CGRectMake(self.view.frame.size.width, self.view.frame.size.height, currentController.view.frame.size.width, currentController.view.frame.size.height)];
[self addConstrainForView];
}];
//Push - move up
[currentController.view setFrame:CGRectMake(0, self.view.frame.size.height, currentController.view.frame.size.width, currentController.view.frame.size.height)];
[UIView animateWithDuration:0.5
animations:^{
[currentController.view setFrame:CGRectMake(0, 0, currentController.view.frame.size.width, currentController.view.frame.size.height)];
[self addConstrainForView];
}];
This code doesn't work as aspect because the next controller move from bottom to top (push), and when it is moving, there is a blank background behind it (root controller 's background).
So I need the correct way to fake push and pop animation (pull up & pull down). Any help would be appreciated.

I use this effect in an app. I wanted (and have) the look of an iPhone series of pick screens embedded into the edge of an iPad app. So the user presses a button from a list then the screen transitions, that looks like a UIViewController advancing, to more options, though it's all in a window on the edge (and the relevant content pops up on the right). Here's the code I use:
- (void)advanceLevel
{
UIImageView *screenShot = [self getTableScreenShot];
[self.tableView.superview addSubview:screenShot];
// Put screen shot over the whole thing with mask for iPhone style animation
TableViewMask *mask =
[[TableViewMask alloc] initWithFrame:CGRectMake(0, 0, 1024, 768) withImage:[Util TakeScreenshot]];
[self.tableView.superview addSubview:mask];
tableLevel++;
[self updateData];
[UIView animateWithDuration:.6 animations:^
{
screenShot.frame =
CGRectMake(self.tableView.frame.origin.x-self.tableView.frame.size.width, self.tableView.frame.origin.y,
self.tableView.frame.size.width, self.tableView.frame.size.height);
controller.toolbar.backButton.alpha = (tableLevel>1?1:0);
}
completion:^(BOOL finished)
{
[screenShot removeFromSuperview];
[mask removeFromSuperview];
[self updateUI];
}];
}
- (void)goBackLevel
{
if(tableLevel==1){ return; }
if(tableLevel==3 && isEditing==YES)
{
// Skip this screen if coming from higher screens
tableLevel--;
}
UIImageView *screenShot = [self getTableScreenShot];
[self.tableView.superview addSubview:screenShot];
// Put screen shot over the whole thing with mask for iPhone style animation
TableViewMask *mask = [[TableViewMask alloc] initWithFrame:CGRectMake(0, 0, 1024, 768) withImage:[Util TakeScreenshot]];
[self.tableView.superview addSubview:mask];
tableLevel--;
[self updateData];
[UIView animateWithDuration:.6 animations:^
{
screenShot.frame =
CGRectMake(self.tableView.frame.origin.x+self.tableView.frame.size.width, self.tableView.frame.origin.y,
self.tableView.frame.size.width, self.tableView.frame.size.height);
controller.toolbar.backButton.alpha = (tableLevel>1?1:0);
}
completion:^(BOOL finished)
{
[screenShot removeFromSuperview];
[mask removeFromSuperview];
[self updateUI];
}];
}
... here's the screen-shot taker:
+ (UIImage *)TakeScreenshot
{
CGSize imageSize = [[UIScreen mainScreen] bounds].size;
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Iterate over every window from back to front
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![window respondsToSelector:#selector(screen)] || [window screen] == [UIScreen mainScreen])
{
// -renderInContext: renders in the coordinate space of the layer,
// so we must first apply the layer's geometry to the graphics context
CGContextSaveGState(context);
// Center the context around the window's anchor point
CGContextTranslateCTM(context, [window center].x, [window center].y);
// Apply the window's transform about the anchor point
CGContextConcatCTM(context, [window transform]);
// Offset by the portion of the bounds left of and above the anchor point
CGContextTranslateCTM(context,
-[window bounds].size.width * [[window layer] anchorPoint].x,
-[window bounds].size.height * [[window layer] anchorPoint].y);
// Render the layer hierarchy to the current context
[[window layer] renderInContext:context];
// Restore the context
CGContextRestoreGState(context);
}
}
// Retrieve the screenshot image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
.. and finally TableViewMask:
#implementation TableViewMask
UIImage *screenShotImage;
- (instancetype)initWithFrame:(CGRect)frame withImage:(UIImage *)_screenShotImage
{
self = [super initWithFrame:frame];
if(self)
{
self.backgroundColor = [UIColor clearColor];
screenShotImage = _screenShotImage;
[self setNeedsDisplay];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[screenShotImage drawInRect:rect];
CGRect intersection = CGRectIntersection( CGRectMake(10.2, 130, 299.6, 600), rect );
if( CGRectIntersectsRect( intersection, rect ) )
{
CGContextFillRect(context, CGRectMake(10, 130, 300, 600));
CGContextClearRect(context, intersection);
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextFillRect( context, intersection);
}
}
#end
I left in some things that are unique to mine because you'll likely need similar calls. For example [self updateData] takes the state of the system into account to update the model and set any other state-based calls. There's also an exception when a screen should be skipped I've left in. You can safely remove those, though you'll probably need similar variants (since you're not really changing UIViewController's).
The only caveat I'd have with this is that unless you have a corner case like mine -- imitating an iPhone in a popped out view on an iPad or something similar -- it's probably better just to use real UIVewController's.

This article would seem relevant to what you are trying to do?
When to use addChildViewController vs pushViewController
It seems to suggest you can use transitionFromViewController:toViewController:duration:options:animations:completion: to animate transition between controllers you are managing yourself.

Related

Custom transition results in black screen or unresponsive screen

Ive created a custom UIViewControllerAnimatedTransition (the code is show below). The transition will focus only on the animation when dismissing the view. When I dismiss the view I get a black screen, the master is briefly shown before it disappears. Please see the code bel
#import "GCSplitDismissTransition.h"
#implementation GCSplitDismissTransition
#pragma mark - UIViewControllerAnimatedTransitioning protocol
-(void) animateTransition: (id < UIViewControllerContextTransitioning > ) transitionContext {
UIViewController * fromVC = [transitionContext viewControllerForKey: UITransitionContextFromViewControllerKey];
UIViewController * toVC = [transitionContext viewControllerForKey: UITransitionContextToViewControllerKey];
UIView * inView = [transitionContext containerView];
UIView * masterView = toVC.view;
UIView * detailView = fromVC.view;
masterView.frame = [transitionContext finalFrameForViewController: toVC];
// add the to VC's view to the intermediate view (where it has to be at the
// end of the transition anyway). We'll hide it during the transition with
// a blank view. This ensures that renderInContext of the 'To' view will
// always render correctly
[inView addSubview: toVC.view];
// if the detail view is a UIScrollView (eg a UITableView) then
// get its content offset so we get the snapshot correctly
CGPoint detailContentOffset = CGPointMake(.0, .0);
if ([detailView isKindOfClass: [UIScrollView class]]) {
detailContentOffset = ((UIScrollView * ) detailView).contentOffset;
}
// if the master view is a UIScrollView (eg a UITableView) then
// get its content offset so we get the snapshot correctly and
// so we can correctly calculate the split point for the zoom effect
CGPoint masterContentOffset = CGPointMake(.0, .0);
if ([masterView isKindOfClass: [UIScrollView class]]) {
masterContentOffset = ((UIScrollView * ) masterView).contentOffset;
}
// Take a snapshot of the detail view
// use renderInContext: instead of the new iOS7 snapshot API as that
// only works for views that are currently visible in the view hierarchy
UIGraphicsBeginImageContextWithOptions(detailView.bounds.size, detailView.opaque, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, 0, -detailContentOffset.y);
[detailView.layer renderInContext: ctx];
UIImage * detailSnapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// take a snapshot of the master view
// use renderInContext: instead of the new iOS7 snapshot API as that
// only works for views that are currently visible in the view hierarchy
UIGraphicsBeginImageContextWithOptions(masterView.bounds.size, masterView.opaque, 0);
ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, 0, -masterContentOffset.y);
[masterView.layer renderInContext: ctx];
UIImage * masterSnapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// get the rect of the source cell in the coords of the from view
CGRect sourceRect = [masterView convertRect: self.sourceView.bounds fromView: self.sourceView];
CGFloat splitPoint = sourceRect.origin.y + sourceRect.size.height - masterContentOffset.y;
CGFloat scale = [UIScreen mainScreen].scale;
// split the master view snapshot into two parts, splitting
// below the master view (usually a UITableViewCell) that originated the transition
CGImageRef masterImgRef = masterSnapshot.CGImage;
CGImageRef topImgRef = CGImageCreateWithImageInRect(masterImgRef, CGRectMake(0, 0, masterSnapshot.size.width * scale, splitPoint * scale));
UIImage * topImage = [UIImage imageWithCGImage: topImgRef scale: scale orientation: UIImageOrientationUp];
CGImageRelease(topImgRef);
CGImageRef bottomImgRef = CGImageCreateWithImageInRect(masterImgRef, CGRectMake(0, splitPoint * scale, masterSnapshot.size.width * scale, (masterSnapshot.size.height - splitPoint) * scale));
UIImage * bottomImage = [UIImage imageWithCGImage: bottomImgRef scale: scale orientation: UIImageOrientationUp];
CGImageRelease(bottomImgRef);
// create views for the top and bottom parts of the master view
UIImageView * masterTopView = [
[UIImageView alloc] initWithImage: topImage
];
UIImageView * masterBottomView = [
[UIImageView alloc] initWithImage: bottomImage
];
CGRect bottomFrame = masterBottomView.frame;
bottomFrame.origin.y = splitPoint;
masterBottomView.frame = bottomFrame;
// setup the inital and final frames for the master view top and bottom
// views depending on whether we're doing a push or a pop transition
CGRect masterTopEndFrame = masterTopView.frame;
CGRect masterBottomEndFrame = masterBottomView.frame;
CGRect masterTopStartFrame = masterTopView.frame;
masterTopStartFrame.origin.y = -(masterTopStartFrame.size.height - sourceRect.size.height);
masterTopView.frame = masterTopStartFrame;
CGRect masterBottomStartFrame = masterBottomView.frame;
masterBottomStartFrame.origin.y += masterBottomStartFrame.size.height;
masterBottomView.frame = masterBottomStartFrame;
CGFloat initialAlpha = 1.0;
CGFloat finalAlpha = .0;
// create views to cover the master top and bottom views so that
// we can fade them in / out
UIView * masterTopFadeView = [
[UIView alloc] initWithFrame: masterTopView.frame
];
masterTopFadeView.backgroundColor = masterView.backgroundColor;
masterTopFadeView.alpha = initialAlpha;
UIView * masterBottomFadeView = [
[UIView alloc] initWithFrame: masterBottomView.frame
];
masterBottomFadeView.backgroundColor = masterView.backgroundColor;
masterBottomFadeView.alpha = initialAlpha;
// create snapshot view of the to view
UIImageView * detailSmokeScreenView = [
[UIImageView alloc] initWithImage: detailSnapshot
];
// create a background view so that we don't see the actual VC
// views anywhere - start with a blank canvas.
UIView * backgroundView = [
[UIView alloc] initWithFrame: inView.frame
];
backgroundView.backgroundColor = [UIColor lightGrayColor];
// add all the views to the transition view
[inView addSubview: backgroundView];
[inView addSubview: detailSmokeScreenView];
[inView addSubview: masterTopView];
[inView addSubview: masterTopFadeView];
[inView addSubview: masterBottomView];
[inView addSubview: masterBottomFadeView];
NSTimeInterval totalDuration = [self transitionDuration: transitionContext];
[UIView animateKeyframesWithDuration: totalDuration
delay: 0
options: UIViewKeyframeAnimationOptionCalculationModeLinear
animations: ^ {
// move the master view top and bottom views (and their
// respective fade views) to where we wna them to end up
masterTopView.frame = masterTopEndFrame;
masterTopFadeView.frame = masterTopEndFrame;
masterBottomView.frame = masterBottomEndFrame;
masterBottomFadeView.frame = masterBottomEndFrame;
detailSmokeScreenView.layer.transform = CATransform3DMakeAffineTransform(CGAffineTransformMakeScale(.1, .1));
// fade out (or in) the master view top and bottom views
// want the fade out animation to happen near the end of the transition
// and the fade in animation to happen at the start of the transition
CGFloat fadeStartTime = .0;
[UIView addKeyframeWithRelativeStartTime: fadeStartTime relativeDuration: .5 animations: ^ {
masterTopFadeView.alpha = finalAlpha;
masterBottomFadeView.alpha = finalAlpha;
}];
}
completion: ^ (BOOL finished) {
// remove all the intermediate views from the heirarchy
[backgroundView removeFromSuperview];
[detailSmokeScreenView removeFromSuperview];
[masterTopView removeFromSuperview];
[masterTopFadeView removeFromSuperview];
[masterBottomView removeFromSuperview];
[masterBottomFadeView removeFromSuperview];
[transitionContext completeTransition: YES];
}
];
}
If I comment out this line : [transitionContext completeTransition: YES]; then the black view does not appear but the master is unresponsive. The idea was adapted from https://github.com/mluisbrown/LCZoomTransition
If there are any problems, please let me know, I hope I provided enough.
Update
import "GCSplitPresentTransition.h"
#implementation GCSplitPresentTransition
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *inView = [transitionContext containerView];
UIView *masterView = fromVC.view;
UIView *detailView = toVC.view;
detailView.frame = [transitionContext finalFrameForViewController:toVC];
// add the to VC's view to the intermediate view (where it has to be at the
// end of the transition anyway). We'll hide it during the transition with
// a blank view. This ensures that renderInContext of the 'To' view will
// always render correctly
[inView addSubview:toVC.view];
// if the detail view is a UIScrollView (eg a UITableView) then
// get its content offset so we get the snapshot correctly
CGPoint detailContentOffset = CGPointMake(.0, .0);
if ([detailView isKindOfClass:[UIScrollView class]]) {
detailContentOffset = ((UIScrollView *)detailView).contentOffset;
}
// if the master view is a UIScrollView (eg a UITableView) then
// get its content offset so we get the snapshot correctly and
// so we can correctly calculate the split point for the zoom effect
CGPoint masterContentOffset = CGPointMake(.0, .0);
if ([masterView isKindOfClass:[UIScrollView class]]) {
masterContentOffset = ((UIScrollView *) masterView).contentOffset;
}
// Take a snapshot of the detail view
// use renderInContext: instead of the new iOS7 snapshot API as that
// only works for views that are currently visible in the view hierarchy
UIGraphicsBeginImageContextWithOptions(detailView.bounds.size, detailView.opaque, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, 0, -detailContentOffset.y);
[detailView.layer renderInContext:ctx];
UIImage *detailSnapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// take a snapshot of the master view
// use renderInContext: instead of the new iOS7 snapshot API as that
// only works for views that are currently visible in the view hierarchy
UIGraphicsBeginImageContextWithOptions(masterView.bounds.size, masterView.opaque, 0);
ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, 0, -masterContentOffset.y);
[masterView.layer renderInContext:ctx];
UIImage *masterSnapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// get the rect of the source cell in the coords of the from view
CGRect sourceRect = [masterView convertRect:self.sourceView.bounds fromView:self.sourceView];
CGFloat splitPoint = sourceRect.origin.y + sourceRect.size.height - masterContentOffset.y;
CGFloat scale = [UIScreen mainScreen].scale;
// split the master view snapshot into two parts, splitting
// below the master view (usually a UITableViewCell) that originated the transition
CGImageRef masterImgRef = masterSnapshot.CGImage;
CGImageRef topImgRef = CGImageCreateWithImageInRect(masterImgRef, CGRectMake(0, 0, masterSnapshot.size.width * scale, splitPoint * scale));
UIImage *topImage = [UIImage imageWithCGImage:topImgRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(topImgRef);
CGImageRef bottomImgRef = CGImageCreateWithImageInRect(masterImgRef, CGRectMake(0, splitPoint * scale, masterSnapshot.size.width * scale, (masterSnapshot.size.height - splitPoint) * scale));
UIImage *bottomImage = [UIImage imageWithCGImage:bottomImgRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(bottomImgRef);
// create views for the top and bottom parts of the master view
UIImageView *masterTopView = [[UIImageView alloc] initWithImage:topImage];
UIImageView *masterBottomView = [[UIImageView alloc] initWithImage:bottomImage];
CGRect bottomFrame = masterBottomView.frame;
bottomFrame.origin.y = splitPoint;
masterBottomView.frame = bottomFrame;
// setup the inital and final frames for the master view top and bottom
// views depending on whether we're doing a push or a pop transition
CGRect masterTopEndFrame = masterTopView.frame;
CGRect masterBottomEndFrame = masterBottomView.frame;
masterTopEndFrame.origin.y = -(masterTopEndFrame.size.height - sourceRect.size.height);
masterBottomEndFrame.origin.y += masterBottomEndFrame.size.height;
CGFloat initialAlpha = 1.0;
CGFloat finalAlpha = 1.0;
// create views to cover the master top and bottom views so that
// we can fade them in / out
UIView *masterTopFadeView = [[UIView alloc] initWithFrame:masterTopView.frame];
masterTopFadeView.backgroundColor = masterView.backgroundColor;
masterTopFadeView.alpha = initialAlpha;
UIView *masterBottomFadeView = [[UIView alloc] initWithFrame:masterBottomView.frame];
masterBottomFadeView.backgroundColor = masterView.backgroundColor;
masterBottomFadeView.alpha = initialAlpha;
// create snapshot view of the to view
UIImageView *detailSmokeScreenView = [[UIImageView alloc] initWithImage:detailSnapshot];
// for a push transition, make the detail view small, to be zoomed in
// for a pop transition, the detail view will be zoomed out, so it starts without
// a transform
// create a background view so that we don't see the actual VC
// views anywhere - start with a blank canvas.
UIView *backgroundView = [[UIView alloc] initWithFrame:inView.frame];
//backgroundView.backgroundColor = self.transitionBackgroundColor;
// add all the views to the transition view
[inView addSubview:backgroundView];
[inView addSubview:detailSmokeScreenView];
[inView addSubview:masterTopView];
[inView addSubview:masterTopFadeView];
[inView addSubview:masterBottomView];
[inView addSubview:masterBottomFadeView];
NSTimeInterval totalDuration = [self transitionDuration:transitionContext];
[UIView animateKeyframesWithDuration:totalDuration
delay:0
options:UIViewKeyframeAnimationOptionCalculationModeLinear
animations:^{
// move the master view top and bottom views (and their
// respective fade views) to where we wna them to end up
masterTopView.frame = masterTopEndFrame;
masterTopFadeView.frame = masterTopEndFrame;
masterBottomView.frame = masterBottomEndFrame;
masterBottomFadeView.frame = masterBottomEndFrame;
// zoom the detail view in or out, depending on whether we're doing a push
// or pop transition
detailSmokeScreenView.layer.transform = CATransform3DMakeAffineTransform(CGAffineTransformIdentity);
// fade out (or in) the master view top and bottom views
// want the fade out animation to happen near the end of the transition
// and the fade in animation to happen at the start of the transition
CGFloat fadeStartTime = .5 ;
[UIView addKeyframeWithRelativeStartTime:fadeStartTime relativeDuration:.5 animations:^{
masterTopFadeView.alpha = finalAlpha;
masterBottomFadeView.alpha = finalAlpha;
}];
}
completion:^(BOOL finished) {
// remove all the intermediate views from the heirarchy
[backgroundView removeFromSuperview];
[detailSmokeScreenView removeFromSuperview];
[masterTopView removeFromSuperview];
[masterTopFadeView removeFromSuperview];
[masterBottomView removeFromSuperview];
[masterBottomFadeView removeFromSuperview];
if ([transitionContext transitionWasCancelled]) {
// we added this at the start, so we have to remove it
// if the transition is canccelled
[toVC.view removeFromSuperview];
[transitionContext completeTransition:NO];
} else {
[fromVC.view removeFromSuperview];
[transitionContext completeTransition:YES];
}
}];
}
-(NSTimeInterval) transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 0.3;
}
#end
Update 2
This is how I present the transition:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
UITableViewCell * cell = [self.tableView cellForRowAtIndexPath:selectedIndexPath];
self.transition.sourceView=cell;
self.dismissTransition.sourceView=cell;
DetailViewController * detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
detailViewController.modalPresentationStyle = UIModalPresentationCustom;
detailViewController.transitioningDelegate = self;
[self presentViewController:detailViewController animated:YES completion:NULL];
}
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
//return new instance of custom transition
return self.transition;
}
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
return self.dismissTransition;
}
The issue has arisen as you've used the LCZoomTransition code, which was written for a navigation push-pop transition. You require code for a custom modal transition animation. The two are different, have a read of these too articles for a flavour of things.
http://www.objc.io/issue-5/view-controller-transitions.html
and
http://www.objc.io/issue-12/custom-container-view-controller-transitions.html
To fix the issue you are having, you just need to remove the code that adds and removes the master view controllers view. As it is a presenting view controller, it's view should stay in the hierarchy.
In the file GCSplitPresentTransition, remove the line
[fromVC.view removeFromSuperview];
in the animation completion handler.
And in the file GCSplitDismissTransition, remove the line
[inView addSubview: toVC.view];
at the start of the -animateTransition method.
I was seeing the symptoms, but different issue:
in [transitionContext viewControllerForKey:]
I was passing it UITransitionContextToViewKey instead of UITransitionContextToViewControllerKey.
No errors reported at compile or runtime, but resulted in a black screen

change size of a rectangle during runtime in objective C

Hi guys i have a quick question
I am using xcode to build an app and I want to animate the size of a custom rectangle during runtime.
I have a UIView class where i create custom rectangles and draw them on the screen of the ViewController. The problem is, whenever i start the app, it just create all the rectangles and then shows the screen.
What i want it to do is to show the screen and then animate the rectangles (like a "growing-animation")
This is how i create the rectangles in the UIView class
-(void)newRectWithX:(int) x height:(int) height{
_rdmRed = arc4random_uniform(100);
_rdmRed /= 100;
_rdmGreen = arc4random_uniform(100);
_rdmGreen /= 100;
_rdmBlue= arc4random_uniform(100);
_rdmBlue /= 100;
_color = [UIColor colorWithRed:_rdmRed green:_rdmGreen blue:_rdmBlue alpha:0.6];
[UIView animateWithDuration:50 delay:10 options:UIViewAnimationOptionShowHideTransitionViews
animations:^{
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rectangle = CGRectMake(x,0,4,height);
CGContextAddRect(context, rectangle);
CGContextSetFillColorWithColor(context, [_color CGColor]);
CGContextFillRect(context, rectangle);
}
completion:nil];
}
I didn't do anything in the ViewController class yet.
+ animateWithDuration:delay:options:animations:completion:
Animate changes to one or more views using the specified duration, delay, options, and completion handler.
To be able to use that method you will need to animate a view. Here is a chuck of code that will allow you to use that method and CoreGraphics.
#implementation View
{
UIView * view;
}
- (void)runAnimation {
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionShowHideTransitionViews animations:^{
view.frame = CGRectMake(0, 0, 20, 20);
} completion:nil];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rectangle = ((UIView *)[view.layer presentationLayer]).frame;
CGContextAddRect(context, rectangle);
CGContextSetFillColorWithColor(context, [UIColor cyanColor].CGColor);
CGContextFillRect(context, rectangle);
}
#end
I have this to move a rectangle, maybe can help you
if(destination.origin.x > 0){
destination.origin.x = 0;
[self.navigationController.view.superview bringSubviewToFront:self.navigationController.view];
}else{
destination.origin.x += _sideMenu.view.frame.size.width;
}
[UIView animateWithDuration:0.2 animations:^{
self.view.frame = destination;
}completion:^(BOOL finished) {
if(finished){
if (destination.origin.x > 0) {
[self.navigationController.view.superview bringSubviewToFront:_sideMenu.view];
} else {
}
}
}];
change the destination.origin to destination.size, like this
CGRect destination = self.view.frame;
if(destination.size.height > 10){
destination.size.height = 10;
[self.navigationController.view.superview bringSubviewToFront:self.navigationController.view];
}else{
destination.size.height += 10;
}
[UIView animateWithDuration:0.2 animations:^{
self.view.frame = destination;
}completion:^(BOOL finished) {
if(finished){
if (destination.size.height > 10) {
[self.navigationController.view.superview bringSubviewToFront:_sideMenu.view];
} else {
}
}
}];

rectangles in UIView iOS

I have created a rectangle like this in objective -c :
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 440, 320, 30));
}
in the view controller i have created a function which then calls this rectangle and an animation block so it looks like its coming out by the alpha command :
PopUpRectangle *RectangeView = [[PopUpRectangle alloc] initWithFrame:CGRectMake(0, 538, 320, 30)];
RectangeView.alpha = 0;
[self.view addSubview:RectangeView];
[UIView animateWithDuration:1.5
animations:^ {
RectangeView.alpha = 1;
}];
I have a function that is triggered by an event and then I call the above code to create the rectangle on the screen.
but how do i revert the action, would it be best to create another rectangle which brings the alpha back to 0 ?
thanks
Keep a local reference to RectangeView so you when you want you can animate it back out, and remove it from the view.
[UIView animateWithDuration:1.5
animations:^ {
RectangeView.alpha = 0;
} completion:^(BOOL finished) {
[RectangleView removeFromSuperview];
}];
You could also move your animation block to it's own function.
Like the other comment suggests save a local reference to it so that you can access it later.
- (void)animateBlock{
[UIView animateWithDuration:1.5
animations:^ {
RectangeView.alpha = !RectangleView.alpha; //Reverse the current alpha
} completion:^(BOOL finished) {
[RectangleView removeFromSuperview];
}];
}

iPhone: Animate circle with UIKit

I am using a CircleView class that basically inherits off UIView and implements drawRect to draw a circle. This all works, hurrah!
What I cannot figure out though is how to make it so when I touch it (touch code is implemented) the circle grows or pops. Normally I'd use the UIKit animation framework to do this but given I am basically overriding the drawRect function to directly draw the circle. So how do I animate this?
- (void)drawRect:(CGRect)rect{
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, _Color.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
// Animate?
}
The answers depends on what you mean by "grows or pops". When I hear "pop" I assume that the view scales up over a short period of time ans scales back down again to the original size. Something that "grows" on the other hand would scale up but not down again.
For something that scales up and down again over a short period of time I would use a transform to scale it. Custom drawing or not, UIView has build in support for animating a simple transform. If this is what you are looking for then it's not more then a few lines of code.
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAutoreverse // reverse back to original value
animations:^{
// scale up 10%
yourCircleView.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
// restore the non-scaled state
yourCircleView.transform = CGAffineTransformIdentity;
}];
If on the other hand you want the circle to grow a little bit more on every tap then this won't do it for you since the view is going to look pixelated when it scales up. Making custom animations can be tricky so I would still advice you to use a scaling transform for the actual animation and then redraw the view after the animation.
[UIView animateWithDuration:0.3
animations:^{
// scale up 10%
yourCircleView.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
// restore the non-scaled state
yourCircleView.transform = CGAffineTransformIdentity;
// redraw with new value
yourCircleView.radius = theBiggerRadius;
}];
If you really, really want to do a completely custom animation then I would recommend that you watch Rob Napiers talk on Animating Custom Layer Properties, his example is even the exact thing you are doing (growing a circle).
If you want an animation that expands the ellipse from the centre, try this. In the header, define 3 variables:
BOOL _isAnimating;
float _time;
CGRect _ellipseFrame;
Then implement these 3 methods in the .m file:
- (void)drawRect:(CGRect)rect; {
[super drawRect:rect];
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, _Color.CGColor);
CGContextFillEllipseInRect(context, _ellipseFrame);
}
- (void)expandOutward; {
if(_isAnimating){
_time += 1.0f / 30.0f;
if(_time >= 1.0f){
_ellipseFrame = self.frame;
_isAnimating = NO;
}
else{
_ellipseFrame = CGRectMake(0.0f, 0.0f, self.frame.size.width * _time, self.frame.size.height * _time);
_ellipseFrame.center = CGPointMake(self.frame.size.width / 2.0f, self.frame.size.height / 2.0f);
[self setNeedsDisplay];
[self performSelector:#selector(expandOutward) withObject:nil afterDelay:(1.0f / 30.0f)];
}
}
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer; {
if(_isAnimating == NO){
_time = 0.0f;
_isAnimating = YES;
[self expandOutward];
}
}
This is the most basic way you can animate the circle expanding from the centre. Look into CADisplayLink for a constant sync to the screen if you want more detailed animations. Hope that Helps!

How do I make an expand/contract transition between views on iOS?

I'm trying to make a transition animation in iOS where a view or view controller appears to expand to fill the whole screen, then contract back to its former position when done. I'm not sure what this type of transition is officially called, but you can see an example in the YouTube app for iPad. When you tap one of the search result thumbnails on the grid, it expands from the thumbnail, then contracts back into the thumbnail when you return to the search.
I'm interested in two aspects of this:
How would you make this effect when transitioning between one view and another? In other words, if view A takes up some area of the screen, how would you transition it to view B which takes up the whole screen, and vice versa?
How would you transition to a modal view this way? In other words, if UIViewController C is currently showing and contains view D which takes up part of the screen, how do you make it look like view D is turning into UIViewController E which is presented modally on top of C?
Edit: I'm adding a bounty to see if that gets this question more love.
Edit: I've got some source code that does this, and Anomie's idea works like a charm, with a few refinements. I had first tried animating the modal controller's view (E), but it didn't produce the effect of feeling like you're zooming into the screen, because it wasn't expanding all the stuff around the thumbnail view in (C). So then I tried animating the original controller's view (C), but the redrawing of it made for a jerky animation, and things like background textures did not zoom properly. So what I wound up doing is taking an image of the the original view controller (C) and zooming that inside the modal view (E). This method is substantially more complex than my original one, but it does look nice! I think it's how iOS must do its internal transitions as well. Anyway, here's the code, which I've written as a category on UIViewController.
UIViewController+Transitions.h:
#import <Foundation/Foundation.h>
#interface UIViewController (Transitions)
// make a transition that looks like a modal view
// is expanding from a subview
- (void)expandView:(UIView *)sourceView
toModalViewController:(UIViewController *)modalViewController;
// make a transition that looks like the current modal view
// is shrinking into a subview
- (void)dismissModalViewControllerToView:(UIView *)view;
#end
UIViewController+Transitions.m:
#import "UIViewController+Transitions.h"
#implementation UIViewController (Transitions)
// capture a screen-sized image of the receiver
- (UIImageView *)imageViewFromScreen {
// make a bitmap copy of the screen
UIGraphicsBeginImageContextWithOptions(
[UIScreen mainScreen].bounds.size, YES,
[UIScreen mainScreen].scale);
// get the root layer
CALayer *layer = self.view.layer;
while(layer.superlayer) {
layer = layer.superlayer;
}
// render it into the bitmap
[layer renderInContext:UIGraphicsGetCurrentContext()];
// get the image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// close the context
UIGraphicsEndImageContext();
// make a view for the image
UIImageView *imageView =
[[[UIImageView alloc] initWithImage:image]
autorelease];
return(imageView);
}
// make a transform that causes the given subview to fill the screen
// (when applied to an image of the screen)
- (CATransform3D)transformToFillScreenWithSubview:(UIView *)sourceView {
// get the root view
UIView *rootView = sourceView;
while (rootView.superview) rootView = rootView.superview;
// convert the source view's center and size into the coordinate
// system of the root view
CGRect sourceRect = [sourceView convertRect:sourceView.bounds toView:rootView];
CGPoint sourceCenter = CGPointMake(
CGRectGetMidX(sourceRect), CGRectGetMidY(sourceRect));
CGSize sourceSize = sourceRect.size;
// get the size and position we're expanding it to
CGRect screenBounds = [UIScreen mainScreen].bounds;
CGPoint targetCenter = CGPointMake(
CGRectGetMidX(screenBounds),
CGRectGetMidY(screenBounds));
CGSize targetSize = screenBounds.size;
// scale so that the view fills the screen
CATransform3D t = CATransform3DIdentity;
CGFloat sourceAspect = sourceSize.width / sourceSize.height;
CGFloat targetAspect = targetSize.width / targetSize.height;
CGFloat scale = 1.0;
if (sourceAspect > targetAspect)
scale = targetSize.width / sourceSize.width;
else
scale = targetSize.height / sourceSize.height;
t = CATransform3DScale(t, scale, scale, 1.0);
// compensate for the status bar in the screen image
CGFloat statusBarAdjustment =
(([UIApplication sharedApplication].statusBarFrame.size.height / 2.0)
/ scale);
// transform to center the view
t = CATransform3DTranslate(t,
(targetCenter.x - sourceCenter.x),
(targetCenter.y - sourceCenter.y) + statusBarAdjustment,
0.0);
return(t);
}
- (void)expandView:(UIView *)sourceView
toModalViewController:(UIViewController *)modalViewController {
// get an image of the screen
UIImageView *imageView = [self imageViewFromScreen];
// insert it into the modal view's hierarchy
[self presentModalViewController:modalViewController animated:NO];
UIView *rootView = modalViewController.view;
while (rootView.superview) rootView = rootView.superview;
[rootView addSubview:imageView];
// make a transform that makes the source view fill the screen
CATransform3D t = [self transformToFillScreenWithSubview:sourceView];
// animate the transform
[UIView animateWithDuration:0.4
animations:^(void) {
imageView.layer.transform = t;
} completion:^(BOOL finished) {
[imageView removeFromSuperview];
}];
}
- (void)dismissModalViewControllerToView:(UIView *)view {
// take a snapshot of the current screen
UIImageView *imageView = [self imageViewFromScreen];
// insert it into the root view
UIView *rootView = self.view;
while (rootView.superview) rootView = rootView.superview;
[rootView addSubview:imageView];
// make the subview initially fill the screen
imageView.layer.transform = [self transformToFillScreenWithSubview:view];
// remove the modal view
[self dismissModalViewControllerAnimated:NO];
// animate the screen shrinking back to normal
[UIView animateWithDuration:0.4
animations:^(void) {
imageView.layer.transform = CATransform3DIdentity;
}
completion:^(BOOL finished) {
[imageView removeFromSuperview];
}];
}
#end
You might use it something like this in a UIViewController subclass:
#import "UIViewController+Transitions.h"
...
- (void)userDidTapThumbnail {
DetailViewController *detail =
[[DetailViewController alloc]
initWithNibName:nil bundle:nil];
[self expandView:thumbnailView toModalViewController:detail];
[detail release];
}
- (void)dismissModalViewControllerAnimated:(BOOL)animated {
if (([self.modalViewController isKindOfClass:[DetailViewController class]]) &&
(animated)) {
[self dismissModalViewControllerToView:thumbnailView];
}
else {
[super dismissModalViewControllerAnimated:animated];
}
}
Edit: Well, it turns out that doesn't really handle interface orientations other than portrait. So I had to switch to animating the transition in a UIWindow using a view controller to pass along the rotation. See the much more complicated version below:
UIViewController+Transitions.m:
#interface ContainerViewController : UIViewController { }
#end
#implementation ContainerViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation {
return(YES);
}
#end
...
// get the screen size, compensating for orientation
- (CGSize)screenSize {
// get the size of the screen (swapping dimensions for other orientations)
CGSize size = [UIScreen mainScreen].bounds.size;
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
CGFloat width = size.width;
size.width = size.height;
size.height = width;
}
return(size);
}
// capture a screen-sized image of the receiver
- (UIImageView *)imageViewFromScreen {
// get the root layer
CALayer *layer = self.view.layer;
while(layer.superlayer) {
layer = layer.superlayer;
}
// get the size of the bitmap
CGSize size = [self screenSize];
// make a bitmap to copy the screen into
UIGraphicsBeginImageContextWithOptions(
size, YES,
[UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
// compensate for orientation
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
CGContextTranslateCTM(context, size.width, 0);
CGContextRotateCTM(context, M_PI_2);
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
CGContextTranslateCTM(context, 0, size.height);
CGContextRotateCTM(context, - M_PI_2);
}
else if (self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
CGContextTranslateCTM(context, size.width, size.height);
CGContextRotateCTM(context, M_PI);
}
// render the layer into the bitmap
[layer renderInContext:context];
// get the image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// close the context
UIGraphicsEndImageContext();
// make a view for the image
UIImageView *imageView =
[[[UIImageView alloc] initWithImage:image]
autorelease];
// done
return(imageView);
}
// make a transform that causes the given subview to fill the screen
// (when applied to an image of the screen)
- (CATransform3D)transformToFillScreenWithSubview:(UIView *)sourceView
includeStatusBar:(BOOL)includeStatusBar {
// get the root view
UIView *rootView = sourceView;
while (rootView.superview) rootView = rootView.superview;
// by default, zoom from the view's bounds
CGRect sourceRect = sourceView.bounds;
// convert the source view's center and size into the coordinate
// system of the root view
sourceRect = [sourceView convertRect:sourceRect toView:rootView];
CGPoint sourceCenter = CGPointMake(
CGRectGetMidX(sourceRect), CGRectGetMidY(sourceRect));
CGSize sourceSize = sourceRect.size;
// get the size and position we're expanding it to
CGSize targetSize = [self screenSize];
CGPoint targetCenter = CGPointMake(
targetSize.width / 2.0,
targetSize.height / 2.0);
// scale so that the view fills the screen
CATransform3D t = CATransform3DIdentity;
CGFloat sourceAspect = sourceSize.width / sourceSize.height;
CGFloat targetAspect = targetSize.width / targetSize.height;
CGFloat scale = 1.0;
if (sourceAspect > targetAspect)
scale = targetSize.width / sourceSize.width;
else
scale = targetSize.height / sourceSize.height;
t = CATransform3DScale(t, scale, scale, 1.0);
// compensate for the status bar in the screen image
CGFloat statusBarAdjustment = includeStatusBar ?
(([UIApplication sharedApplication].statusBarFrame.size.height / 2.0)
/ scale) : 0.0;
// transform to center the view
t = CATransform3DTranslate(t,
(targetCenter.x - sourceCenter.x),
(targetCenter.y - sourceCenter.y) + statusBarAdjustment,
0.0);
return(t);
}
- (void)expandView:(UIView *)sourceView
toModalViewController:(UIViewController *)modalViewController {
// get an image of the screen
UIImageView *imageView = [self imageViewFromScreen];
// show the modal view
[self presentModalViewController:modalViewController animated:NO];
// make a window to display the transition on top of everything else
UIWindow *window =
[[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.hidden = NO;
window.backgroundColor = [UIColor blackColor];
// make a view controller to display the image in
ContainerViewController *vc = [[ContainerViewController alloc] init];
vc.wantsFullScreenLayout = YES;
// show the window
[window setRootViewController:vc];
[window makeKeyAndVisible];
// add the image to the window
[vc.view addSubview:imageView];
// make a transform that makes the source view fill the screen
CATransform3D t = [self
transformToFillScreenWithSubview:sourceView
includeStatusBar:(! modalViewController.wantsFullScreenLayout)];
// animate the transform
[UIView animateWithDuration:0.4
animations:^(void) {
imageView.layer.transform = t;
} completion:^(BOOL finished) {
// we're going to crossfade, so change the background to clear
window.backgroundColor = [UIColor clearColor];
// do a little crossfade
[UIView animateWithDuration:0.25
animations:^(void) {
imageView.alpha = 0.0;
}
completion:^(BOOL finished) {
window.hidden = YES;
[window release];
[vc release];
}];
}];
}
- (void)dismissModalViewControllerToView:(UIView *)view {
// temporarily remove the modal dialog so we can get an accurate screenshot
// with orientation applied
UIViewController *modalViewController = [self.modalViewController retain];
[self dismissModalViewControllerAnimated:NO];
// capture the screen
UIImageView *imageView = [self imageViewFromScreen];
// put the modal view controller back
[self presentModalViewController:modalViewController animated:NO];
[modalViewController release];
// make a window to display the transition on top of everything else
UIWindow *window =
[[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.hidden = NO;
window.backgroundColor = [UIColor clearColor];
// make a view controller to display the image in
ContainerViewController *vc = [[ContainerViewController alloc] init];
vc.wantsFullScreenLayout = YES;
// show the window
[window setRootViewController:vc];
[window makeKeyAndVisible];
// add the image to the window
[vc.view addSubview:imageView];
// make the subview initially fill the screen
imageView.layer.transform = [self
transformToFillScreenWithSubview:view
includeStatusBar:(! self.modalViewController.wantsFullScreenLayout)];
// animate a little crossfade
imageView.alpha = 0.0;
[UIView animateWithDuration:0.15
animations:^(void) {
imageView.alpha = 1.0;
}
completion:^(BOOL finished) {
// remove the modal view
[self dismissModalViewControllerAnimated:NO];
// set the background so the real screen won't show through
window.backgroundColor = [UIColor blackColor];
// animate the screen shrinking back to normal
[UIView animateWithDuration:0.4
animations:^(void) {
imageView.layer.transform = CATransform3DIdentity;
}
completion:^(BOOL finished) {
// hide the transition stuff
window.hidden = YES;
[window release];
[vc release];
}];
}];
}
Whew! But now it looks just about like Apple's version without using any restricted APIs. Also, it works even if the orientation changes while the modal view is in front.
Making the effect is simple. You take the full-sized view, initialize its transform and center to position it on top of the thumbnail, add it to the appropriate superview, and then in an animation block reset the transform and center to position it in the final position. To dismiss the view, just do the opposite: in an animation block set transform and center to position it on top of the thumbnail, and then remove it completely in the completion block.
Note that trying to zoom from a point (i.e. a rectangle with 0 width and 0 height) will screw things up. If you're wanting to do that, zoom from a rectangle with width/height something like 0.00001 instead.
One way would be to do the same as in #1, and then call presentModalViewController:animated: with animated NO to present the actual view controller when the animation is complete (which, if done right, would result in no visible difference due to the presentModalViewController:animated: call). And dismissModalViewControllerAnimated: with NO followed by the same as in #1 to dismiss.
Or you could manipulate the modal view controller's view directly as in #1, and accept that parentViewController, interfaceOrientation, and some other stuff just won't work right in the modal view controller since Apple doesn't support us creating our own container view controllers.
After watching the Youtube iPad animation, I figured out that it's just an illusion. Let's say that there's a SearchViewController for the search results, and a DetailViewController for the video itself, and the additional info of the video.
DetailViewController has a method like - (id)initWithFullscreen which starts the view controller using the full screen space with the video.
So the sequence goes like this:
SearchViewController presents its results.
User clicks on a video.
DetailViewController is created with initWithFullscreen, but not presented
The "Zoom in" animation begins. (Notice that we are still on the SearchViewController, and this animation is just a simple View animation)
The "Zoom in" animation ends, presents the DetailViewController with animated:NO (as Anomie mentioned).
The DetailViewController is now presented, and using full space.
It doesn't seem that the youtube app is doing anything fancier, the give-away was that the "Zoom in" animation zooms to a black square, before presenting the full video.

Resources