如何在iOS上的视图之间进行扩展/合同转换? [英] How do I make an expand/contract transition between views on iOS?

查看:81
本文介绍了如何在iOS上的视图之间进行扩展/合同转换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在iOS中制作过渡动画,其中视图或视图控制器似乎会扩展以填充整个屏幕,然后在完成时缩回到其以前的位置。我不确定这种类型的转换是否正式调用,但您可以在YouTube的YouTube应用中看到一个示例。当您点击网格上的某个搜索结果缩略图时,它会从缩略图中展开,然后在您返回搜索时缩回缩略图。

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:


  1. 在一个视图和另一个视图之间转换时,如何产生这种效果?换句话说,如果视图A占据屏幕的某个区域,您将如何将其转换为占据整个屏幕的视图B,反之亦然?

  1. 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?

您将如何以这种方式过渡到模态视图?换句话说,如果UIViewController C当前正在显示并包含占据屏幕一部分的视图D,那么如何使它看起来像视图D变成UIViewController E,它以模态方式呈现在C之上?

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?

编辑:我正在添加一笔赏金,看看是否能让这个问题更受关注。

I'm adding a bounty to see if that gets this question more love.

编辑:我有一些源代码可以做到这一点,而Anomie的想法就像一个魅力,有一些改进。我首先尝试动画模态控制器的视图(E),但它没有产生感觉像你正在放大屏幕的效果,因为它没有扩展(C)中缩略图视图周围的所有东西。然后我尝试动画原始控制器的视图(C),但重绘它为一个生涩的动画,而像背景纹理之类的东西没有正确缩放。所以我最后做的是拍摄原始视图控制器(C)的图像并在模态视图(E)内部进行缩放。这种方法比我原来的方法复杂得多,但看起来确实不错!我认为这也是iOS必须进行内部转换的方式。无论如何,这是我在UIViewController上作为类别编写的代码。

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:

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:

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子类中:

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

编辑嗯,事实证明,除了肖像之外,它并没有真正处理界面方向。所以我不得不切换到UIWindow中的过渡动画,使用视图控制器来传递旋转。请参阅下面更复杂的版本:

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:

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

}

哇!但现在它看起来就像Apple的版本没有使用任何受限制的API。此外,即使在模态视图位于前面时方向发生变化,它仍然有效。

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.

推荐答案


  1. 使效果很简单。你拿全尺寸视图,初始化它的转换中心将它放在缩略图的顶部,添加它到适当的超视图,然后在动画块中重置转换中心,将其置于最终位置。要取消视图,只需执行相反的操作:在动画块集转换中心中将其置于顶部缩略图,然后在完成块中完全删除它。

  1. 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.

请注意,尝试从一个点(即一个宽度为0和高度为0的矩形)进行缩放会使事物变得棘手向上。如果你想这样做,请从宽度/高度变为0.00001的矩形进行缩放。

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.

一种方法是做同样的事情。 #1,然后调用 presentModalViewController:animated:,动画为NO,以在动画完成时显示实际的视图控制器(如果操作正确,将导致没有明显差异由于 presentModalViewController:animated:调用)。并且 dismissModalViewControllerAnimated:使用NO后跟#1中的相同以解除。

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.

或者你可以操纵模态直接在#1中查看控制器的视图,并接受 parentViewController interfaceOrientation ,以及其他一些东西不会因为Apple不支持我们创建自己的容器视图控制器,所以在模态视图控制器中工作。

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.

这篇关于如何在iOS上的视图之间进行扩展/合同转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆