问题描述
我想在iOS程序中滑动一些视图。现在,我正在使用模态样式在它们之间滑动,并使用交叉溶解动画。但是,我想要在主屏幕上看到一个滑动/滑动动画等。我不知道如何编写这样的过渡,并且动画样式不是可用的模态过渡样式。谁能给我一个代码示例?它不需要是模态模型或任何东西,我只是发现最简单。
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 7开始,如果要为两个视图控制器之间的过渡设置动画,则可以使用自定义过渡,如WWDC 2013视频。例如,要自定义新视图控制器的显示,您将:
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:
-
目标视图控制器将指定<$ c演示动画的$ c> 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];
}
您将实现一个能够执行所需动画的动画师: / p>
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:
-
创建一个交互控制器(通常是
UIPercentDrivenInteractiveTransition
);
让你的 UIViewControllerAnimatedTransitioning
也实现 interactionControllerForPresentation
,显然会返回前面提到的交互控制器;
Have your UIViewControllerAnimatedTransitioning
also implement interactionControllerForPresentation
, which obviously would return the aforementioned interaction controller;
有一个手势(或你有什么)更新 interactionController
Have a gesture (or what have you) that updates the interactionController
上述
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的答案是正确的,CATransition可以产生你正在寻找的效果对于。
@sooper's answer is correct, that CATransition can yield the effect you're looking for.
但顺便说一下,如果你的背景不是白色, kCATransitionPush
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:
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.
一个警告:首先,这个例子,也不是 CATransition
例子,就像SpringBoard主屏幕动画(你谈过),它是连续的(即如果你在滑动的一半,你可以停下来回去或随你)。这些转变是曾经发起它们的转变,它们恰好发生了。如果您需要实时互动,也可以这样做,但也有所不同。
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.
更新:
如果您想使用跟踪用户手指的连续手势,您可以使用 UIPanGestureRecognizer
而不是 UISwipeGestureRecognizer
,我认为 animateWithDuration
在这种情况下优于 CATransition
。我修改了 handlePanGesture
来更改 frame
坐标以协调用户的手势,然后我将上面的代码修改为只需在用户放手时完成动画。效果很好。我不认为你可以很容易地用 CATransition
做到这一点。
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.
例如,你可能会创建一个手势控制器主视图上的处理程序:
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];
}
}
}
使用这些简单的 frame
您可能为所需的UX定义的方法:
That uses these simple frame
methods that you'd presumably define for your desired 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.
说明了所有这些(补充和澄清这个旧答案)后,我应该指出我不再使用这种技术了。如今,我通常使用 UIScrollView
(打开分页)或(在iOS 6中) UIPageViewController
。这使您无法编写此类手势处理程序(并享受滚动条,弹跳等额外功能)。在 UIScrollView
实现中,我只是回应 scrollViewDidScroll
事件,以确保我懒得加载必要的子视图。
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.
这篇关于如何在视图之间实现滑动/滑动动画?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!