我想在iOS程序中滑动一些视图。现在,我正在使用模态样式在它们之间滑动,并使用交叉溶解动画。但是,我希望有一个像你在主屏幕上看到的滑动/滑动动画等。我不知道如何编写这样的转换,并且动画样式不是可用的模态转换样式。谁能给我一个代码示例?它不需要是模态模型或任何东西,我只是发现最简单。
答案 0 :(得分:36)
从iOS 7开始,如果要为两个视图控制器之间的过渡设置动画,则可以使用自定义过渡,如WWDC 2013视频Custom Transitions Using View Controllers中所述。例如,要自定义新视图控制器的显示,您将:
目标视图控制器会为演示动画指定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
或者,如果您希望此手势具有互动性,您还可以:
创建交互控制器(通常为UIPercentDrivenInteractiveTransition
);
让UIViewControllerAnimatedTransitioning
同时实施interactionControllerForPresentation
,这显然会返回上述互动控制器;
有一个更新interactionController
的手势(或有你的手势)
这些都在上述Custom Transitions Using View Controllers中描述。
例如,自定义导航控制器推/弹,请参阅Navigation controller custom transition animation
下面,请查找原始答案的副本,该答案早于自定义过渡。
@ sooper的回答是正确的,CATransition可以产生你正在寻找的效果。
但是,顺便说一句,如果你的背景不是白色,kCATransitionPush
的{{1}}会有一个奇怪的淡入淡出并在过渡结束时消失,这可能会分散注意力(导航时)在图像之间,特别是,它使它具有轻微的闪烁效果)。如果你受此影响,我发现这个简单的转换非常优雅:你可以准备你的“下一个视图”就在屏幕右边,然后动画当前视图从屏幕向左移动同时你同时动画动画下一个视图以移动到当前视图的位置。请注意,在我的示例中,我在单个视图控制器中将主视图内外的子视图设置为动画,但您可能会理解:
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")
}];
示例都不像SpringBoard主屏幕动画(你谈过),这是连续的(即如果你在滑动的一半) ,你可以停下来回去等等。这些转变是曾经发起它们的转变,它们恰好发生了。如果您需要实时交互,也可以这样做,但它有所不同。
<强>更新强>
如果您想使用跟踪用户手指的连续手势,您可以使用CATransition
而不是UIPanGestureRecognizer
,我认为UISwipeGestureRecognizer
优于animateWithDuration
案件。我修改了CATransition
以更改handlePanGesture
坐标以与用户的手势进行协调,然后我修改了上面的代码,以便在用户放手时完成动画。效果很好。我不认为你可以很容易地使用frame
来做到这一点。
例如,您可以在控制器的主视图上创建一个手势处理程序:
CATransition
处理程序可能如下所示:
[self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]];
使用这些简单的- (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
你的具体实施无疑会有所不同,但希望这说明了这个想法。
说明了所有这些(补充和澄清这个旧答案),我应该指出我不再使用这种技术了。如今,我通常使用- (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);
}
(打开“分页”)或(在iOS 6中)UIScrollView
。这使您无法编写此类手势处理程序(并享受滚动条,弹跳等额外功能)。在UIPageViewController
实现中,我只是回复UIScrollView
事件,以确保我懒得加载必要的子视图。
答案 1 :(得分:9)
您可以创建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"];
答案 2 :(得分:8)
如果您想要切换页面时跳板有相同的页面滚动/滑动效果,那么为什么不使用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;
然后使用UIPageControl
获取这些点。 :)