2011-08-18 5 views
35

전 화면을 채우기 위해 뷰 또는 뷰 컨트롤러가 확장 된 것으로 보이는 iOS에서 트랜지션 애니메이션을 만들려고하고 있습니다. 그런 다음 완료되면 다시 이전 위치로 축소됩니다. 이러한 유형의 전환이 공식적으로 불려지는지 확실하지 않지만 iPad 용 YouTube 앱에서 예를 볼 수 있습니다. 표에서 검색 결과 축소판 중 하나를 누르면 축소판에서 확장 된 다음 검색으로 돌아 가면 축소판으로 축소됩니다.iOS의보기간에 확장/계약 전환을 어떻게 만들 수 있습니까?

나는이 두 가지 측면에서 관심이 있어요 : 하나의 뷰와 다른 사이에 전환 할 때

  1. 방법이 효과를 만들 것? 즉,보기 A가 화면의 일부 영역을 차지하는 경우 전체 화면을 차지하는 B보기로 전환하려면 어떻게해야합니까?

  2. 어떻게하면이 방식으로 모달보기로 전환하겠습니까? 다시 말해서, UIViewController C가 현재 표시되어 있고 화면의 일부를 차지하는 뷰 D를 포함하고 있다면 뷰 D가 UI 상단에 모달로 나타나는 UIViewController E로 바뀌는 것처럼 보이게 할 수 있습니까?

편집 :는 나는이 질문에 더 많은 사랑을 얻을 수 있는지 확인하기 위해 현상금을 추가 해요.

편집 :이 작업을 수행하는 소스 코드가 있으며, Anomie의 아이디어는 몇 가지 개선 사항과 함께 매력적으로 작동합니다. 처음에 모달 컨트롤러보기 (E)에 애니메이션을 적용 해 보았지만 (C)의 축소판보기를 중심으로 모든 항목을 확장하지 않았기 때문에 사용자가 화면을 확대하는 것처럼 느껴지는 효과를 내지 않았습니다. 그래서 원래 컨트롤러의 뷰 (C)에 애니메이션을 적용 해 보았습니다. 그러나 재구성 된 애니메이션을 위해 다시 그릴 때 배경 텍스처와 같은 것들이 제대로 확대되지 않았습니다. 따라서 내가 수행 한 작업은 원래보기 컨트롤러 (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" 

... 

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

:

#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 하위에이 같은 것을 사용할 수 있습니다 편집 : 음, 그게 밝혀 지 않습니다 초상화 이외의 인터페이스 방향을 실제로 처리하지 못합니다. 그래서 뷰 ​​컨트롤러를 사용하여 회전을 전달하는 UIWindow에서 전환을 애니메이션으로 전환해야했습니다.

의 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를 사용하지 않고 Apple 버전과 거의 비슷하게 보입니다. 또한 모달 뷰가 정면에있는 동안 방향이 변경 되더라도 작동합니다.

답변

12
  1. 효과가 간단합니다.전체 크기의 뷰를 가져 와서 transformcenter을 초기화하여 축소판 위에 배치하고 해당 슈퍼 뷰에 추가 한 다음 애니메이션 블록에서 transformcenter을 재설정하여 최종 위치에 배치합니다. 보기를 닫으려면 애니메이션 블록에서 transformcenter을 썸네일 상단에 배치 한 다음 완료 블록에서 완전히 제거합니다.

    점 (즉 너비 0과 높이 0 인 직사각형)에서 확대/축소하려고하면 사물이 엉망이됩니다. 그렇게하고 싶다면 대신 너비/높이가 0.00001 인 직사각형에서 확대/축소하십시오.

  2. # 1에서와 동일하게 수행 한 다음 애니메이션이 완료되면 실제보기 컨트롤러를 표시하려면 presentModalViewController:animated:을 애니메이션 NO로 호출하십시오 (올바르게 수행하면 눈에 띄는 차이가 발생하지 않음). presentModalViewController:animated: 호출). 그리고 dismissModalViewControllerAnimated:에 NO가 붙고 # 1에서와 같이 끝납니다.

    # 1에서와 같이 모달 뷰 컨트롤러의 뷰를 직접 조작하고 parentViewController, interfaceOrientation을 받아 들일 수 있습니다. 일부 항목은 모달 뷰 컨트롤러에서 제대로 작동하지 않습니다. Apple이 우리를 자신의 컨테이너 뷰 컨트롤러.

+0

감사합니다. 우리가 부분 2에서 애니메이션을 적용 할 것인지에 대해서는 분명하지 않다. 모달 뷰 컨트롤러가 뷰에 모달 표시되기 전에 애니메이션을 적용 할 수 있습니까? 이 경우 현재보기 컨트롤러보기의 하위보기로 추가해야합니다. 나는 이것을위한 약간의 소스 코드를 보는 것에 매우 흥미가있을 것이다. –

+0

@ JesseCrossen : # 2에서 첫 번째 방법은 모달 뷰 컨트롤러의 뷰와 같은 일부 뷰를 애니메이션으로 만들 것입니다. CALayer의'renderInContext :'를 사용하여 만든 "스크린 샷"일 수 있습니다. 두 번째 방법은 뷰 컨트롤러의 뷰를 직접 조작하는 것입니다. – Anomie

+0

설명해 주셔서 감사합니다. 나는 뷰 컨트롤러의 비표준 조작이 실제로 실용적 일 수 있다는 것을 발견했다. 그래서 나는 소스 코드에 대해 물었다.어떻게 작동하는지 알려주고 알려 드리겠습니다. –

9

유튜브 (Youtube) iPad 애니메이션을 본 후, 나는 그저 환상 일 뿐이라는 것을 알아 냈습니다. 검색 결과에 SearchViewController, 동영상 자체에 DetailViewController, 동영상의 추가 정보가 있다고 가정 해 보겠습니다.

DetailViewController에는 비디오와 함께 전체 화면 공간을 사용하는보기 컨트롤러를 시작하는 - (id)initWithFullscreen과 같은 메서드가 있습니다.

그래서 순서는 다음과 같이 진행됩니다

  1. SearchViewController 그 결과를 제시한다.
  2. 사용자가 동영상을 클릭합니다.
  3. DetailViewController가 initWithFullscreen으로 생성되었지만 표시되지 않았습니다.
  4. "확대"애니메이션이 시작됩니다.
  5. "확대"애니메이션이 종료되고 DetailViewController에 animated:NO (Anomie 언급)이 표시됩니다.이 애니메이션은 SearchViewController에 있습니다.
  6. 이제 DetailViewController가 제시되고 전체 공간을 사용합니다.

youtube 앱이 더 좋아 보이지 않는 것처럼 보이지만, "확대"애니메이션은 전체 비디오를보기 전에 검은 색 사각형으로 확대됩니다.

+0

좋은 눈! 가로 모드에서는 그렇게 보이지만 세로로 시도하십시오. 엄지 손톱의 텍스트 세부 정보가 더 확장 된 설명으로 전환되어 효과가 매우 부드럽습니다. 가로 모드에서는 블랙 박스가 확장되어 모달 뷰로 바뀌고, 세로 모드에서는 썸네일이 상위 뷰의 경계를 채우기 위해 확장 된 다음 세부 뷰는 다음과 같은 두 가지 다른 기술을 사용하고있을 수 있습니다. 애니메이션없이 탐색 스택에 푸시됩니다 (또는 단지 작은 크로스 페이드). –

+0

btw,이 대답은 정말로 오래되었습니다. 스냅 샷, 사용자 정의 모달/푸시 전환 또는 사용자 정의 UIStoryboardSegues를 통해 멋진 전체 화면 애니메이션을 모두 얻을 수 있습니다 – Can

관련 문제