如何在iOS上的视图之间进行展开/合同转换?

我正在尝试在iOS中创build一个视图或视图控制器的过渡animation,使其展开以填充整个屏幕,然后在完成时恢复到以前的位置。 我不确定这种types的转换是正式调用的,但是您可以在iPad的YouTube应用程序中看到一个示例。 当您点击网格上的某个search结果缩略图时,它会从缩略图展开,然后在您返回search时缩回到缩略图中。

我对这两方面感兴趣:

  1. 在一个视图和另一个视图之间转换时,你会如何做到这一点? 换句话说,如果视图A占据屏幕的某个区域,那么如何将它转换到占据整个屏幕的视图B,反之亦然?

  2. 你将如何过渡到这种模式的观点? 换句话说,如果UIViewController C当前正在显示并包含视图D,它占据了屏幕的一部分,那么如何使它看起来像视图D正在变成UIViewController E,它被模态地显示在C的顶部?

编辑:我添加一个赏金,看看是否得到这个问题更爱。

编辑:我有一些源代码可以做到这一点,Anomie的想法就像一个魅力,有一些改进。 我首先试着对模态控制器的视图(E)进行animation处理,但没有产生像放大屏幕一样的效果,因为它并没有扩大(C)中缩略图视图周围的所有东西。 所以然后我尝试了原始控制器的视图(C)的animation,但重绘它为一个生涩的animation,像背景纹理的东西没有正确放大。 所以我所做的是拍摄原始视图控制器(C)的图像,并将其放大到模态视图(E)中。 这种方法比我原来的方法要复杂得多,但看起来不错! 我认为这也是iOS如何进行内部转换的原因。 无论如何,这里的代码,我已经写在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 

你可以在UIViewController子类中使用这样的东西:

 #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]; } } 

编辑:嗯,事实certificate,并没有真正处理除肖像以外的界面方向。 所以我不得不使用视图控制器来切换UIWindow中的转换animation来传递旋转。 看下面更复杂的版本:

的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]; }]; }]; } 

呼! 但现在看起来就像苹果公司的版本没有使用任何限制的API。 此外,即使在模态视图位于前面的情况下,方向也会变化。

  1. 使效果很简单。 您将全尺寸的视图,初始化其transformcenter ,将其放置在缩略图的顶部,将其添加到相应的超级视图,然后在animation块中重置transformcenter以将其定位在最终位置。 要消除该视图,只需做相反的事情:在一个animation块中设置transformcenter将其放置在缩略图顶部,然后在完成块中完全删除它。

    请注意,试图从一个点(即一个0宽度和0高度的矩形)缩放将搞砸了。 如果你想要做到这一点,从一个类似0.00001的宽度/高度的矩形,而不是像0.00001。

  2. 一种方法是在#1中执行相同的操作,然后调用presentModalViewController:animated:带有animation的NO来在animation完成时显示实际的视图控制器(如果正确的话,会导致由于presentModalViewController:animated: call)。 并dismissModalViewControllerAnimated:跟在#1相同的解雇。

    或者你可以直接在#1中操作模式视图控制器的视图,并接受parentViewControllerinterfaceOrientation和其他一些东西在模式视图控制器不能正常工作,因为苹果不支持我们创build自己的容器视图控制器。

看完Youtube iPadanimation之后,我发现这只是一个幻想。 假设有一个SearchViewController用于search结果,还有一个用于video本身的DetailViewController以及video的附加信息。

DetailViewController有一个像- (id)initWithFullscreen这样的方法,使用video的全屏幕空间启动视图控制器。

所以顺序是这样的:

  1. SearchViewController显示其结果。
  2. 用户点击一个video。
  3. DetailViewController是用initWithFullscreen创build的,但是没有提供
  4. “放大”animation开始。 (请注意,我们仍然在SearchViewController上,而这个animation只是一个简单的Viewanimation)
  5. “放大”animation结束,呈现DetailViewController animated:NO (如Anomie提到)。
  6. DetailViewController现在呈现,并使用完整的空间。

看起来YouTube的应用程序似乎并没有做任何更有趣的事情,放弃的原因是在展示完整的video之前,“放大”animation会变成黑色方块。