自定义转换导致黑屏或屏幕无响应

Custom transition results in black screen or unresponsive screen

我创建了一个自定义 UIViewControllerAnimatedTransition(代码如下所示)。关闭视图时,过渡将仅关注动画。当我关闭视图时出现黑屏, master 在消失之前会短暂显示。请看代码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];

    }
  ];
}

如果我注释掉这一行:[transitionContext completeTransition: YES]; 那么黑色视图不会出现,但是 master 没有响应。这个想法改编自 https://github.com/mluisbrown/LCZoomTransition

如果有什么问题,请告诉我,希望我提供的足够多。

更新

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

更新 2

这就是我介绍过渡的方式:

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

问题出现是因为您使用了 LCZoomTransition 代码,该代码是为导航 push-pop 转换而编写的。您需要自定义模式转换动画的代码。两者不同,多看看这些文章,感受一下。

http://www.objc.io/issue-5/view-controller-transitions.html

http://www.objc.io/issue-12/custom-container-view-controller-transitions.html

要解决您遇到的问题,您只需删除添加和删除主视图控制器视图的代码。因为它是一个呈现视图控制器,它的视图应该留在层次结构中。

在文件 GCSplitPresentTransition 中,删除行

[fromVC.view removeFromSuperview];

在动画完成处理程序中。

并在文件 GCSplitDismissTransition 中删除行

[inView addSubview: toVC.view];

-animateTransition 方法的开头。

我看到了症状,但问题不同:
在 [transitionContext viewControllerForKey:]

我传递的是 UITransitionContextToViewKey 而不是 UITransitionContextToViewControllerKey。

在编译或运行时没有报错,但导致黑屏