ios 如何在视图之间实现滑动/滑动动画?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10400709/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I implement a swiping/sliding animation between views?
提问by user1368136
I have a few views between which I want to swipe in an iOS program. Right now, I'm swiping between them using a modal style, with a cross dissolve animation. However, I want to have a swiping/sliding animation like you see on the home screen and such. I have no idea how to code such a transition, and the animation style isn't an available modal transition style. Can anyone give me an example of the code? It doesn't need to be a modal model or anything, I just found that easiest.
我有几个视图,我想在 iOS 程序中滑动它们。现在,我正在使用模态样式在它们之间滑动,并带有交叉溶解动画。但是,我想要像您在主屏幕上看到的那样滑动/滑动动画。我不知道如何编码这样的过渡,并且动画样式不是可用的模态过渡样式。谁能给我一个代码的例子?它不需要是模态模型或任何东西,我只是发现最简单。
回答by Rob
Since iOS 7, if you want to animate the transition between two view controllers, you would use custom transitions, as discussed in WWDC 2013 video Custom Transitions Using View Controllers. For example, to customize the presentation of a new view controller you would:
从 iOS 7 开始,如果您想为两个视图控制器之间的过渡设置动画,您将使用自定义过渡,如 WWDC 2013 视频使用视图控制器自定义过渡中所述。例如,定制你将一个新的视图控制器的介绍:
The destination view controller would specify the
self.modalPresentationStyle
andtransitioningDelegate
for the presentation animation:- (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { self.modalPresentationStyle = UIModalPresentationCustom; self.transitioningDelegate = self; } return self; }
This delegate (in this example, the view controller itself) would conform to
UIViewControllerTransitioningDelegate
and implement:- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[PresentAnimator alloc] init]; } // in iOS 8 and later, you'd also specify a presentation controller - (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source { return [[PresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting]; }
You would implement an animator that would perform the desired animation:
@interface PresentAnimator : NSObject <UIViewControllerAnimatedTransitioning> @end @implementation PresentAnimator - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } // do whatever animation you want below - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; CGFloat width = fromViewController.view.frame.size.width; CGRect originalFrame = fromViewController.view.frame; CGRect rightFrame = originalFrame; rightFrame.origin.x += width; CGRect leftFrame = originalFrame; leftFrame.origin.x -= width / 2.0; toViewController.view.frame = rightFrame; toViewController.view.layer.shadowColor = [[UIColor blackColor] CGColor]; toViewController.view.layer.shadowRadius = 10.0; toViewController.view.layer.shadowOpacity = 0.5; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.frame = leftFrame; toViewController.view.frame = originalFrame; toViewController.view.layer.shadowOpacity = 0.5; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end
You'd also implement a presentation controller that takes care of cleaning up the view hierarchy for you. In this case, since we're completely overlaying the presenting view, we can remove it from the hierarchy when the transition is done:
@interface PresentationController: UIPresentationController @end @implementation PresentationController - (BOOL)shouldRemovePresentersView { return true; } @end
Optionally, if you wanted this gesture to be interactive, you would also:
Create an interaction controller (typically a
UIPercentDrivenInteractiveTransition
);Have your
UIViewControllerAnimatedTransitioning
also implementinteractionControllerForPresentation
, which obviously would return the aforementioned interaction controller;Have a gesture (or what have you) that updates the
interactionController
目标视图控制器将为演示动画指定
self.modalPresentationStyle
和transitioningDelegate
:- (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { self.modalPresentationStyle = UIModalPresentationCustom; self.transitioningDelegate = self; } return self; }
这个委托(在这个例子中,视图控制器本身)将符合
UIViewControllerTransitioningDelegate
并实现:- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[PresentAnimator alloc] init]; } // in iOS 8 and later, you'd also specify a presentation controller - (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source { return [[PresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting]; }
您将实现一个动画师来执行所需的动画:
@interface PresentAnimator : NSObject <UIViewControllerAnimatedTransitioning> @end @implementation PresentAnimator - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } // do whatever animation you want below - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; CGFloat width = fromViewController.view.frame.size.width; CGRect originalFrame = fromViewController.view.frame; CGRect rightFrame = originalFrame; rightFrame.origin.x += width; CGRect leftFrame = originalFrame; leftFrame.origin.x -= width / 2.0; toViewController.view.frame = rightFrame; toViewController.view.layer.shadowColor = [[UIColor blackColor] CGColor]; toViewController.view.layer.shadowRadius = 10.0; toViewController.view.layer.shadowOpacity = 0.5; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.frame = leftFrame; toViewController.view.frame = originalFrame; toViewController.view.layer.shadowOpacity = 0.5; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end
您还将实现一个表示控制器,负责为您清理视图层次结构。在这种情况下,由于我们完全覆盖了呈现视图,我们可以在转换完成后将其从层次结构中删除:
@interface PresentationController: UIPresentationController @end @implementation PresentationController - (BOOL)shouldRemovePresentersView { return true; } @end
或者,如果您希望此手势具有交互性,您还可以:
创建一个交互控制器(通常是 a
UIPercentDrivenInteractiveTransition
);让你
UIViewControllerAnimatedTransitioning
也实现interactionControllerForPresentation
,这显然会返回前面提到的交互控制器;有一个手势(或你有什么)更新
interactionController
This is all described in the aforementioned Custom Transitions Using View Controllers.
这在前面提到的使用视图控制器的自定义转换中都有描述。
For example of customizing navigation controller push/pop, see Navigation controller custom transition animation
例如自定义导航控制器推送/弹出,请参阅导航控制器自定义过渡动画
Below, please find a copy of my original answer, which predates custom transitions.
请在下面找到我的原始答案的副本,该答案早于自定义转换。
@sooper's answer is correct, that CATransition can yield the effect you're looking for.
@sooper 的答案是正确的,CATransition 可以产生您正在寻找的效果。
But, by the way, if your background isn't white, the kCATransitionPush
of CATransition
has an weird fade in and fade out at the end of the transition that can be distracting (when navigating between images, especially, it lends it a slightly flickering effect). If you suffer from this, I found this simple transition to be very graceful: You can prepare your "next view" to be just off screen to the right, and then animate the moving of the current view off screen to the left while you simultaneously animate the next view to move to where the current view was. Note, in my examples, I'm animating subviews in and out of the main view within a single view controller, but you probably get the idea:
但是,顺便说一下,如果你的背景不是白色的,在过渡结束kCATransitionPush
时CATransition
会有奇怪的淡入和淡出,这可能会分散注意力(尤其是在图像之间导航时,它会产生轻微的闪烁效果) . 如果您遇到这种情况,我发现这个简单的过渡非常优雅:您可以将“下一个视图”准备在屏幕右侧,然后将当前视图移动到屏幕左侧的动画同时进行为下一个视图设置动画以移动到当前视图所在的位置。请注意,在我的示例中,我在单个视图控制器中对进出主视图的子视图进行动画处理,但您可能会明白:
float width = self.view.frame.size.width;
float height = self.view.frame.size.height;
// my nextView hasn't been added to the main view yet, so set the frame to be off-screen
[nextView setFrame:CGRectMake(width, 0.0, width, height)];
// then add it to the main view
[self.view addSubview:nextView];
// now animate moving the current view off to the left while the next view is moved into place
[UIView animateWithDuration:0.33f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
[nextView setFrame:currView.frame];
[currView setFrame:CGRectMake(-width, 0.0, width, height)];
}
completion:^(BOOL finished){
// do whatever post processing you want (such as resetting what is "current" and what is "next")
}];
Clearly, you'd have to tweak this for how you've got your controls all set up, but this yields a very simple transition, no fading or anything like that, just a nice smooth transition.
很显然,你必须调整这个对你是如何得到你的控件全部成立,但这个收益率很简单的过渡,不褪色或类似的东西,只是一个不错的平稳过渡。
A caveat: First, neither this example, nor the CATransition
example, are quite like the SpringBoard home screen animation (that you talked about), which is continuous (i.e. if you're half way through a swipe, you can stop and go back or whatever). These transitions are ones that once to initiate them, they just happen. If you need that realtime interaction, that can be done, too, but it's different.
警告:首先,这个例子和CATransition
例子都不像 SpringBoard 主屏幕动画(你谈到的),它是连续的(即,如果你滑动到一半,你可以停下来返回或任何)。这些转换一旦启动它们,它们就会发生。如果您需要实时交互,那也可以做到,但它是不同的。
Update:
更新:
If you want to use a continuous gesture that tracks the user's finger you can use UIPanGestureRecognizer
rather than UISwipeGestureRecognizer
, and I think animateWithDuration
is better than CATransition
in that case. I modified my handlePanGesture
to change the frame
coordinates to coordinate with the user's gesture, and then I modified the above code to just complete the animation when the user let go. Works pretty well. I don't think you can do that with CATransition
very easily.
如果您想使用跟踪用户手指的连续手势,您可以使用UIPanGestureRecognizer
而不是UISwipeGestureRecognizer
,我认为animateWithDuration
比CATransition
这种情况更好。我修改了我handlePanGesture
的改变frame
坐标来配合用户的手势,然后我修改了上面的代码,当用户放手时才完成动画。工作得很好。我不认为你可以CATransition
很容易地做到这一点。
For example, you might create a gesture handler on the controller's main view:
例如,您可以在控制器的主视图上创建一个手势处理程序:
[self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]];
And the handler might look like:
处理程序可能如下所示:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
// transform the three views by the amount of the x translation
CGPoint translate = [gesture translationInView:gesture.view];
translate.y = 0.0; // I'm just doing horizontal scrolling
prevView.frame = [self frameForPreviousViewWithTranslate:translate];
currView.frame = [self frameForCurrentViewWithTranslate:translate];
nextView.frame = [self frameForNextViewWithTranslate:translate];
// if we're done with gesture, animate frames to new locations
if (gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateFailed)
{
// figure out if we've moved (or flicked) more than 50% the way across
CGPoint velocity = [gesture velocityInView:gesture.view];
if (translate.x > 0.0 && (translate.x + velocity.x * 0.25) > (gesture.view.bounds.size.width / 2.0) && prevView)
{
// moving right (and/or flicked right)
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
prevView.frame = [self frameForCurrentViewWithTranslate:CGPointZero];
currView.frame = [self frameForNextViewWithTranslate:CGPointZero];
}
completion:^(BOOL finished) {
// do whatever you want upon completion to reflect that everything has slid to the right
// this redefines "next" to be the old "current",
// "current" to be the old "previous", and recycles
// the old "next" to be the new "previous" (you'd presumably.
// want to update the content for the new "previous" to reflect whatever should be there
UIView *tempView = nextView;
nextView = currView;
currView = prevView;
prevView = tempView;
prevView.frame = [self frameForPreviousViewWithTranslate:CGPointZero];
}];
}
else if (translate.x < 0.0 && (translate.x + velocity.x * 0.25) < -(gesture.view.frame.size.width / 2.0) && nextView)
{
// moving left (and/or flicked left)
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
nextView.frame = [self frameForCurrentViewWithTranslate:CGPointZero];
currView.frame = [self frameForPreviousViewWithTranslate:CGPointZero];
}
completion:^(BOOL finished) {
// do whatever you want upon completion to reflect that everything has slid to the left
// this redefines "previous" to be the old "current",
// "current" to be the old "next", and recycles
// the old "previous" to be the new "next". (You'd presumably.
// want to update the content for the new "next" to reflect whatever should be there
UIView *tempView = prevView;
prevView = currView;
currView = nextView;
nextView = tempView;
nextView.frame = [self frameForNextViewWithTranslate:CGPointZero];
}];
}
else
{
// return to original location
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
prevView.frame = [self frameForPreviousViewWithTranslate:CGPointZero];
currView.frame = [self frameForCurrentViewWithTranslate:CGPointZero];
nextView.frame = [self frameForNextViewWithTranslate:CGPointZero];
}
completion:NULL];
}
}
}
That uses these simple frame
methods that you'd presumably define for your desired UX:
这使用frame
了您可能为所需的 UX 定义的这些简单方法:
- (CGRect)frameForPreviousViewWithTranslate:(CGPoint)translate
{
return CGRectMake(-self.view.bounds.size.width + translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height);
}
- (CGRect)frameForCurrentViewWithTranslate:(CGPoint)translate
{
return CGRectMake(translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height);
}
- (CGRect)frameForNextViewWithTranslate:(CGPoint)translate
{
return CGRectMake(self.view.bounds.size.width + translate.x, translate.y, self.view.bounds.size.width, self.view.bounds.size.height);
}
Your particular implementation will undoubtedly vary, but hopefully this illustrates the idea.
您的特定实现无疑会有所不同,但希望这说明了这个想法。
Having illustrated all of this (supplementing and clarifying this old answer), I should point out that I don't use this technique any more. Nowadays, I generally use a UIScrollView
(with "paging" turned on) or (in iOS 6) a UIPageViewController
. This gets you out of the business of writing this sort of gesture handler (and enjoying extra functionality like scroll bars, bouncing, etc.). In the UIScrollView
implementation, I just respond to the scrollViewDidScroll
event to make sure that I'm lazily loading the necessary subview.
在说明了所有这些(补充和澄清了这个旧答案)之后,我应该指出我不再使用这种技术。现在,我通常使用 a UIScrollView
(打开“分页”)或(在 iOS 6 中) a UIPageViewController
。这让您无需编写此类手势处理程序(并享受额外的功能,如滚动条、弹跳等)。在UIScrollView
实现中,我只是响应scrollViewDidScroll
事件以确保我正在延迟加载必要的子视图。
回答by sooper
You could create a CATransition
animation. Here's an example of how you can slide a second view (from left) into the screen whilst pushing the current view out:
你可以创建一个CATransition
动画。这是一个示例,说明如何将第二个视图(从左侧)滑入屏幕,同时将当前视图推出:
UIView *theParentView = [self.view superview];
CATransition *animation = [CATransition animation];
[animation setDuration:0.3];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[theParentView addSubview:yourSecondViewController.view];
[self.view removeFromSuperview];
[[theParentView layer] addAnimation:animation forKey:@"showSecondViewController"];
回答by Leonard Pauli
If you want the same page scroll/swipe effect as the springboard have when switching page, then why not just use a UIScrollView
?
如果你想在同一个页面滚动/滑动效果的跳板已切换页面时,那么为什么不直接使用UIScrollView
?
CGFloat width = 320;
CGFloat height = 400;
NSInteger pages = 3;
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,width,height)];
scrollView.contentSize = CGSizeMake(width*pages, height);
scrollView.pagingEnabled = YES;
And then use UIPageControl
to get these dots. :)
然后用UIPageControl
得到这些点。:)