私はiOSでトランジションアニメーションを作成しようとしています。この場合、ビューまたはビューコントローラーが画面全体に拡大表示され、終了すると元の位置に戻ります。この種類の移行の正式名称はわかりませんが、iPad用のYouTubeアプリで例を確認できます。グリッドで検索結果のサムネイルの1つをタップすると、サムネイルから展開され、検索に戻ると縮小してサムネイルに戻ります。
これには2つの側面があります。
あるビューと別のビューの間で遷移するとき、どのようにこの効果をもたらしますか?言い換えれば、ビューAが画面の一部の領域を占める場合、それをどのようにして画面全体を占めるビューBに移行しますか?
この方法でモーダルビューにどのように移行しますか?つまり、UIViewController Cが現在表示されていて、画面の一部を占めるビューDが含まれている場合、ビューDがCの上にモーダルに表示されるUIViewController Eに変わっているようにするにはどうすればよいですか?
編集:賞金を追加して、この質問がもっと愛されるかどうかを確認します。
編集:これを行うソースコードをいくつか持っています。アノミーのアイデアは、いくつかの改良を加えた魅力的なものです。私は最初にモーダルコントローラーのビューをアニメーション化してみました(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"
@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];
}
}
編集:ええと、ポートレート以外のインターフェイスの向きは実際には処理しないことがわかりました。そのため、回転に沿って渡すために、ビューコントローラーを使用して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];
}];
}];
}
ふew!しかし、制限されたAPIを使用しないAppleのバージョンのように見えます。また、モーダルビューが正面にあるときに方向が変わっても機能します。
効果を出すのは簡単です。フルサイズのビューを取得し、そのtransform
およびcenter
を初期化してサムネイルの上に配置し、適切なスーパービューに追加してから、アニメーションブロックでtransform
とcenter
を使用して、最終位置に配置します。ビューを閉じるには、反対の操作を行います。アニメーションブロックでtransform
とcenter
を設定してサムネイルの上に配置し、完了ブロックで完全に削除します。
ポイント(つまり、幅0と高さ0の長方形)からズームしようとすると、問題が発生することに注意してください。これを行いたい場合は、代わりに幅/高さが0.00001のような長方形からズームします。
1つの方法は#1と同じことを行い、次にpresentModalViewController:animated:
をアニメーションNOで呼び出して、アニメーションが完了したときに実際のビューコントローラーを表示します(正しく実行すると、 presentModalViewController:animated:
呼び出し)。そしてdismissModalViewControllerAnimated:
のNOに続いて#1と同じようにして却下します。
または、#1のようにモーダルビューコントローラーのビューを直接操作して、そのparentViewController
、interfaceOrientation
を受け入れることもできます。他のいくつかのものは、モーダルビューコントローラーでは正しく機能しませんAppleは、独自のコンテナビューコントローラの作成をサポートしていません。
YouTubeのiPadアニメーションを見て、それが単なる幻想であることがわかりました。検索結果のSearchViewController
と、ビデオ自体のDetailViewController
、およびビデオの追加情報があるとします。
DetailViewControllerには、- (id)initWithFullscreen
のようなメソッドがあり、ビデオの全画面スペースを使用してビューコントローラを起動します。
したがって、シーケンスは次のようになります。
initWithFullscreen
で作成されますが、表示されませんanimated:NO
が表示されます(Anomieが述べたように)。Youtubeアプリがもっとおかしなことをしているようには見えませんが、完全なビデオを表示する前に、「ズームイン」アニメーションが黒い四角にズームするというのがプレゼントです。