我目前正在测试新的iOS 7视图控制器转换。 我想要的是一个自定义模式呈现过渡,将您的下一个视图从顶部屏幕切割成几个条带。每个条带应在增量延迟后出现,以产生所需的效果。
所以我的代码看起来像这样:
- (void)presentModalWithContext:(id<UIViewControllerContextTransitioning>)context
{
UIView *inView = [context containerView];
UIView *fromView = [context viewControllerForKey:UITransitionContextFromViewControllerKey].view;
UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;
NSTimeInterval stripTime = 1.0;
NSTimeInterval stripDelay = 1.0;
NSInteger stripCount = 10;
CGFloat stripHeight = toView.frame.size.height / stripCount;
for (NSInteger i = 0; i < stripCount; i++)
{
CGFloat offsetY = i*stripHeight;
CGRect snapRect = CGRectMake(0, offsetY, toView.frame.size.width, stripHeight);
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
view.frame = stripRect;
[inView insertSubview:view aboveSubview:fromView];
NSTimeInterval interval = stripDelay*(stripCount-i);
[UIView animateWithDuration:stripTime delay:interval options:0 animations:^{
CGPoint center = view.center;
center.y += stripCount*stripHeight;
view.center = center;
} completion:^(BOOL finished) {
NSLog(@"complete");
if (i == stripCount-1)
[context completeTransition:YES];
}];
}
}
我已经检查了每个条带的初始和最终位置,并且已经可以了。我的interval
变量也在每个循环中正确设置。
但似乎这根本没有延迟。所有条带都在一起移动,给人的印象是整个视图都在移动。
快速查看基本日志显示所有动画同时执行:
2013-09-20 01:11:32.908 test_transition[7451:a0b] complete
2013-09-20 01:11:32.909 test_transition[7451:a0b] complete
2013-09-20 01:11:32.910 test_transition[7451:a0b] complete
2013-09-20 01:11:32.910 test_transition[7451:a0b] complete
2013-09-20 01:11:32.911 test_transition[7451:a0b] complete
2013-09-20 01:11:32.911 test_transition[7451:a0b] complete
2013-09-20 01:11:32.912 test_transition[7451:a0b] complete
2013-09-20 01:11:32.912 test_transition[7451:a0b] complete
2013-09-20 01:11:32.913 test_transition[7451:a0b] complete
2013-09-20 01:11:32.913 test_transition[7451:a0b] complete
有人能够发现这里的错误吗?
编辑:
似乎这是取消任何动画延迟的以下行,即使这些动画与正在拍摄的视图无关:
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
如果我将参数afterScreenUpdates
设置为NO
,则视图快照为null
,我会收到以下错误日志:
Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.
如何在快照之前渲染视图?我试过[toView setNeedsDisplay]
但没有成功......
答案 0 :(得分:1)
在处理了一些代码之后,将它与我的比较,延迟参数得到正确验证,我仍然无法弄清楚为什么你的代码不起作用。无论如何,我找到了另一种方法。我将动画分为两部分。在第一部分中,我使用您的代码创建视图切片,将它们添加到inView,还添加到可变数组。在第二部分中,我递归地调用动画块,没有延迟,直到显示最后一个条带。这种方法的一个限制是每个条带动画必须在下一个开始之前完成(因为下一个从完成块调用),因此您无法独立控制持续时间和延迟。无论如何,这就是我所做的。在呈现视图控制器中,我只是这样做:
-(IBAction)presntBlue:(id)sender {
BlueViewController *blue = [self.storyboard instantiateViewControllerWithIdentifier:@"Blue"];
blue.modalTransitionStyle = UIModalPresentationCustom;
blue.transitioningDelegate = self;
[self presentViewController:blue animated:YES completion:nil];
}
-(id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
RDPresentationAnimator *animator = [RDPresentationAnimator new];
animator.isPresenting = YES;
return animator;
}
在RDPresentationAnimator课程中,我有这个:
@interface RDPresentationAnimator () {
NSInteger stripCount;
CGFloat stripHeight;
NSMutableArray *stripArray;
}
@end
@implementation RDPresentationAnimator
#define ANIMATION_TIME .3
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return ANIMATION_TIME;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context {
UIView *inView = [context containerView];
UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;
stripCount = 10;
stripHeight = toView.frame.size.height / stripCount;
stripArray = [NSMutableArray new];
for (NSInteger i = 0; i < stripCount; i++)
{
CGFloat offsetY = i*stripHeight;
CGRect snapRect = CGRectMake(0, offsetY, toView.frame.size.width, stripHeight);
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
view.frame = stripRect;
[inView addSubview:view];
[stripArray addObject:view];
}
[self animateStrip:stripCount - 1 withContext:context];
}
-(void)animateStrip:(NSInteger) index withContext:(id <UIViewControllerContextTransitioning>) context{
[UIView animateWithDuration:ANIMATION_TIME animations:^{
UIView *view = stripArray[index];
CGPoint center = view.center;
center.y += stripCount*stripHeight;
view.center = center;
} completion:^(BOOL finished) {
if (index >0) {
[self animateStrip:index - 1 withContext:context];
}else{
[context completeTransition:YES];
};
}];
}
答案 1 :(得分:1)
我以为我会添加另一个答案,它可以让你独立控制stripTime和stripDelay。我从未找到使用新的UIViewControllerContextTransitioning方法使其工作的方法。这种方式使用普通的UIView动画,然后是no animation presentViewController。此方法应该在纵向或横向方向上正常工作(请注意,我使用self.view.bounds来计算stripHeight和snapRect,以便这些值对于任一方向都是正确的。)
@interface ViewController () {
NSInteger stripCount;
CGFloat stripHeight;
NSMutableArray *stripArray;
}
@end
@implementation ViewController
-(IBAction)presntBlue:(id)sender {
BlueViewController *blue = [self.storyboard instantiateViewControllerWithIdentifier:@"Blue"];
[self animateView:blue];
}
-(void)animateView:(UIViewController *) toVC; {
UIView *toView = toVC.view;
toView.frame = self.view.bounds;
[self.view addSubview:toView];
NSTimeInterval stripDelay = 0.2;
NSTimeInterval stripTime = 1.0;
stripCount = 10;
stripHeight = self.view.bounds.size.height / stripCount;
stripArray = [NSMutableArray new];
for (NSInteger i = 0; i < stripCount; i++) {
CGFloat offsetY = i*stripHeight;
CGRect snapRect = CGRectMake(0, offsetY, self.view.bounds.size.width, stripHeight);
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
view.frame = stripRect;
[self.view addSubview:view];
[stripArray addObject:view];
}
[toView removeFromSuperview];
for (NSInteger i = 0; i < stripCount; i++) {
NSTimeInterval interval = stripDelay*(stripCount-i);
UIView *view = stripArray[i];
[UIView animateWithDuration:stripTime delay:interval options:0 animations:^{
CGPoint center = view.center;
center.y += stripCount*stripHeight;
view.center = center;
} completion:^(BOOL finished) {
if (i == 0){
[self presentViewController:toVC animated:NO completion:nil];
}
}];
}
}
补充说明:
在animateView:方法中,我将toView添加到self.view,然后在制作条带后将其删除。我这样做是为了确保它在纵向和横向上正常工作 - 如果我省略这两个陈述,动画结束时景观动画中会出现轻微的故障。如果我有这两行,我偶尔会遇到一个小故障,你可以看到整个toView的短暂闪光。我不知道为什么这只会偶尔发生,我还没有更新我的手机,所以我不知道这是否也会在设备上发生。
答案 2 :(得分:0)
这是一个解决方案。
虽然这个问题是2年之久,但它仍然是一个相关的问题,因为它仍然存在于iOS9上。我意识到这对于提问者来说并没有那么多的帮助,因为它已经有2年了,但我只是遇到了这个问题。所以这是一个解决方案。
当你想在视图控制器之间转换时,你可能会使用Animator对象来运行你的自定义UIView块动画代码。这可能是复杂的多个动画块,有些使用延迟值。但是,如果在转换过程中您想要捕获或截取某些内容(无论是UIView.drawViewHierarchyInRect,UIView.snapshotViewAfterScreenUpdates还是UIView.resizableSnapshotViewFromRect),使用这些方法中的任何一种都可以解除动画中的延迟。对于最后两种方法,如果你为afterScreenUpdates传递false,那么它不会解除延迟,但它也不会捕获任何东西;捕捉某些东西必须是真实的,但将其设置为真会使你的延迟脱离。
使用这些方法中的任何一种都可以解除动画块中的延迟,并且通常会搞砸。如果你告诉UIKit转换将是3秒并且你有一个2秒延迟和1秒动画的动画块(UIView.animateWithDuration ...),如果你的延迟被解除,那么动画会立即运行并且过渡持续只用了1秒,这让UIKit失去了同步,而且一般都搞砸了,因为它预计会持续3秒。
这是一个解决方案: 假设你从视图控制器A转换到视图控制器B.在Animator对象的代码文件(一个继承自NSObject并符合UIViewControllerAnimatedTransitioning协议的对象)中,将动画代码放在animateTransition中(transitionContext) :UIViewControllerContextTransitioning){...}委托方法。如果您正在从VC A转换到VC B,请说您希望从VC B中截取某些内容,并在转换期间显示它并对其执行某些操作。一种完美的方法是使用View Controller B的viewDidLoad方法中的drawViewHierarchyInRect方法捕获并存储图像(或从该图像创建UIImageView并存储UIImageView)在View Controller B的实例属性中你需要使用drawViewHierarchyInRect方法,而不是其他两个方法,因为那些要求内容在屏幕上可见(即已经渲染); drawViewHiearchyInRect方法捕获offScreen内容,即使它没有添加到视图中。
一旦开始过渡,就可以查看控制器&#39;初始化并调用它的viewDidLoad。因此,在过渡动画代码中,您可以通过参考视图控制器的属性来获取截屏图像(或图像视图),并在转换过程中随意执行任何操作。您的延误不会被解除。
要点:不要在转换期间截取内容。相反,将屏幕截图代码放在视图控制器的viewDidLoad中,并将其输出存储在实例变量中,并在转换中引用它。
希望这会有所帮助,我今天才遇到这个问题并且刚刚遇到了解决方案。