iPhone: 如何使用动画切换选项卡?

108

我正在使用UITabBarController.selectedIndex在一个选项卡驱动的应用程序中以编程方式切换选项卡。 我试图解决的问题是如何在视图之间进行过渡动画,即从当前选项卡的视图到所选选项卡的视图。

最初的想法是利用UITabBarControllerDelegate,但是当以编程方式切换选项卡时,似乎不会调用它。现在我考虑使用UITabBarDelegate.didSelectItem作为可能的挂钩来设置转换动画。

是否有人成功地实现了这些过渡动画?如果是,怎么实现的呢?


1
值得一提的是,许多这些得票最高的答案都早于Runo的回答Heberti的回答中概述的自定义转换。这些是解决这些自定义动画的正确方法。请参阅WWDC 2013视频使用视图控制器进行自定义转换 - Rob
20个回答

2

这个问题可以通过两种方式解决。

1 - 只需在AppDelegate.m文件中写入以下代码即可。请记得在AppDelegate.h文件中使用<>来包含UITabBarControllerDelegate。

-(void)tabBarController:(UITabBarController *)tabBarControllerThis didSelectViewController:(UIViewController *)viewController
{
    [UIView transitionWithView:viewController.view
                      duration:0.1
                       options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionTransitionCrossDissolve
                    animations:^(void){
                    } completion:^(BOOL finished){
                        [UIView beginAnimations:@"animation" context:nil];
                        [UIView setAnimationDuration:0.7];
                        [UIView setAnimationBeginsFromCurrentState:YES];
                        [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
                                               forView:viewController.view
                                                 cache:NO];
                        [UIView commitAnimations];
                    }];
}

2 - 在每个ViewController.m文件中写入以下内容

-(void)viewWillAppear:(BOOL)animated
{
    [UIView transitionWithView:self.view
                      duration:1.0
                       options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionTransitionCrossDissolve
                    animations:^(void){
                        [super viewWillAppear:YES];
                    } completion:^(BOOL finished){
                    }];
}

希望这可以帮助你!

1
我该如何在导航控制器之间实现动画过渡?tabBarControllerDelegate仅适用于视图控制器。 - saeppi
我尝试了两种方法,第一种先显示新视图,然后再进行动画处理,看起来很奇怪。第二种似乎没有任何效果。我进入与tab2相关联的视图中,在viewWillAppear中添加了代码并进行了测试,但在选项卡之间没有可见的动画效果。 - Shannon Cole
尝试过使用默认的Xcode TabBarController项目,但无论是1还是2都没有成功。我真的很希望它们能够正常工作。:) 我是不是漏掉了什么? - Andrew Duncan
我也是,没什么运气...有什么想法吗? - Jon

2

修复跳动的动画...

UIView * fromView = self.view.superview;


1
的答案真的很棒。我稍微调整了滚动转换,使动画看起来更像苹果的推动动画。在第一个动画完成后,我添加了额外的动画以使滑动效果看起来更好。
// Disable interaction during animation to avoids bugs.
self.tabBarController.view.userInteractionEnabled = NO;

// Get the views.
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];

// Get the size of the view area.
CGRect viewSize = fromView.frame;
BOOL scrollRight = controllerIndex > tabBarController.selectedIndex;

// Add the to view to the tab bar view.
[fromView.superview addSubview:toView];
[fromView.superview addSubview:fromView];

self.tabBarController.selectedIndex = 0;

// Position it off screen.
toView.frame = CGRectMake((scrollRight ? (viewSize.size.width *.25) : -(viewSize.size.width * .25 )), viewSize.origin.y, viewSize.size.width, viewSize.size.height);

[UIView animateWithDuration:0.25 
             animations: ^{
                 // Animate the views on and off the screen.
                 [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                 fromView.frame = CGRectMake(viewSize.size.width * .95, viewSize.origin.y, viewSize.size.width, viewSize.size.height);
                 toView.frame = CGRectMake((viewSize.origin.x * .90), viewSize.origin.y, viewSize.size.width, viewSize.size.height);
             }

             completion:^(BOOL finished) {
                 if (finished) {
                     // Being new animation.
                     [UIView animateWithDuration:0.2
                                          animations: ^{
                                              [UIView setAnimationCurve:UIViewAnimationCurveLinear];
                                              fromView.frame = CGRectMake(viewSize.size.width, viewSize.origin.y, viewSize.size.width, viewSize.size.height);
                                              toView.frame = CGRectMake((viewSize.origin.x), viewSize.origin.y, viewSize.size.width, viewSize.size.height);
                                          }
                                          completion:^(BOOL finished) {
                                              if (finished) {
                                                  // Remove the old view from the tabbar view.
                                                  [fromView removeFromSuperview];
                                                  // Restore interaction.
                                                  self.tabBarController.view.userInteractionEnabled = YES;
                                              }
                                          }];
                 }
             }];

0
这是我的工作代码(适用于3个选项卡,没有在更多选项卡上尝试过!!),用于在选项卡之间实现动画过渡。它主要基于drekka的解决方案,但已经在选项卡栏的委托方法中实现,因此如果您只需复制/粘贴它,它应该能够完成工作..(你永远不知道!)
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {

// Important! We validate that the selected tab is not the current tab, to avoid misplacing views
if (tabBarController.selectedViewController == viewController) {
    return NO;
}

// Find the selected view's index
NSUInteger controllerIndex = 0;
for (UIViewController *vc in tabBarController.viewControllers) {
    if (vc == viewController) {
        controllerIndex = [tabBarController.viewControllers indexOfObject:vc];
    }
}

CGFloat screenWidth = SCREEN_SIZE.width;

// Note: We must invert the views according to the direction of the scrolling ( FROM Left TO right or FROM right TO left )
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = viewController.view;

[fromView.superview addSubview:toView];
CGRect fromViewInitialFrame = fromView.frame;
CGRect fromViewNewframe = fromView.frame;

CGRect toViewInitialFrame = toView.frame;

if ( controllerIndex > tabBarController.selectedIndex ) {
// FROM left TO right ( tab0 to tab1 or tab2 )

    // The final frame for the current view. It will be displaced to the left
    fromViewNewframe.origin.x = -screenWidth;
    // The initial frame for the new view. It will be displaced to the left
    toViewInitialFrame.origin.x = screenWidth;
    toView.frame = toViewInitialFrame;

} else {
// FROM right TO left ( tab2 to tab1 or tab0 )

    // The final frame for the current view. It will be displaced to the right
    fromViewNewframe.origin.x = screenWidth;
    // The initial frame for the new view. It will be displaced to the right
    toViewInitialFrame.origin.x = -screenWidth;
    toView.frame = toViewInitialFrame;
}

[UIView animateWithDuration:0.2 animations:^{
    // The new view will be placed where the initial view was placed
    toView.frame = fromViewInitialFrame;
    // The initial view will be place outside the screen bounds
    fromView.frame = fromViewNewframe;

    tabBarController.selectedIndex = controllerIndex;

    // To prevent user interaction during the animation
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];

} completion:^(BOOL finished) {

    // Before removing the initial view, we adjust its frame to avoid visual lags
    fromView.frame = CGRectMake(0, 0, fromView.frame.size.width, fromView.frame.size.height);
    [fromView removeFromSuperview];

    [[UIApplication sharedApplication] endIgnoringInteractionEvents];
}];

return NO;

}


虽然这段代码片段可能解决了问题,但包括解释真的有助于提高您的帖子质量。请记住,您正在为未来的读者回答问题,而这些人可能不知道您的代码建议原因。 - Ferrybig
感谢您的提示,Ferrybig!我尽可能地记录了代码,以使其更易于理解,希望能有所帮助。 - Nahuel Roldan

0

@samwize 的回答已翻译成Swift 3- 这个不错,可以创建从左到右的页面效果:

func animateToTab(toIndex: Int) {
        let tabViewControllers = viewControllers!
        let fromView = selectedViewController!.view
        let toView = tabViewControllers[toIndex].view
        let fromIndex = tabViewControllers.index(of: selectedViewController!)

        guard fromIndex != toIndex else {return}

        // Add the toView to the tab bar view
        fromView?.superview!.addSubview(toView!)

        // Position toView off screen (to the left/right of fromView)
        let screenWidth = screenSize.width
        let scrollRight = toIndex > fromIndex!
        let offset = (scrollRight ? screenWidth : -screenWidth)
        toView?.center = CGPoint(x: (fromView?.center.x)! + offset, y: (toView?.center.y)!)

        // Disable interaction during animation
        view.isUserInteractionEnabled = false

        UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {

            // Slide the views by -offset
            fromView?.center = CGPoint(x: (fromView?.center.x)! - offset, y: (fromView?.center.y)!);
            toView?.center   = CGPoint(x: (toView?.center.x)! - offset, y: (toView?.center.y)!);

        }, completion: { finished in

            // Remove the old view from the tabbar view.
            fromView?.removeFromSuperview()
            self.selectedIndex = toIndex
            self.view.isUserInteractionEnabled = true
        })
    }

0

我想在按钮按下时在两个子视图控制器之间使用翻转过渡,并通过以下方式实现:

-(IBAction)flipViewControllers:(id)sender{
    NSUInteger index = self.selectedIndex;
    index++;
    if(index >= self.childViewControllers.count){
        index = 0;
    }

    self.selectedIndex = index;

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.75];
    [UIView setAnimationTransition:index % 2 ? UIViewAnimationTransitionFlipFromLeft : UIViewAnimationTransitionFlipFromRight
                           forView:self.view
                             cache:YES];
    [UIView commitAnimations];
}

我还将背景颜色设为黑色,在我的情况下,我通过设置navigationController.view.backgroundColor来实现,但在你的情况下可能是window.backgroundColor,可以在应用程序委托中轻松设置。


0
根据这个 答案,我最终得到了这个结果:

使用 Swift 5.7

创建一个 transitioning 对象类
final class TransitioningObject: NSObject, UIViewControllerAnimatedTransitioning {

    // Arguments are needed to understand in which direction the movement will be
    // They may not be needed in your case
    let fromIndex: Int
    let toIndex: Int
    let trainsitionTime: TimeInterval

    // Calculate direction
    private var isMovingRight: Bool {
        fromIndex < toIndex
    }

    init(fromIndex: Int, toIndex: Int, transitionTime: TimeInterval = 0.4) {
        self.fromIndex = fromIndex
        self.toIndex = toIndex
        self.trainsitionTime = transitionTime
        super.init()
    }

    // This two methods are required to conform to the protocol
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        trainsitionTime
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // Extract the views from transitionContext
        guard
            let fromView = transitionContext.view(forKey: .from),
            let toView = transitionContext.view(forKey: .to)
        else {
            return
        }

        // Add this views to the container
        transitionContext.containerView.addSubview(fromView)
        transitionContext.containerView.addSubview(toView)

        // In my case, I will only move the view from left to right or vice versa so i need to know  a horizontal offset
        var horizontalOffset: CGFloat {
            let containerWidth = transitionContext.containerView.frame.width
            return isMovingRight ? containerWidth : -containerWidth
        }

        // Move the toView of the screen using calculated offset
        toView.frame.origin.x = horizontalOffset

        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [.curveEaseOut]) {
            // Now reset toView origin.x
            toView.frame.origin.x = 0
            // Move fromView to the opposite direction to make seamless effect
            fromView.frame.origin.x = -horizontalOffset
        } completion: { finished in
            // Notify system that the transition is done
            transitionContext.completeTransition(finished)
        }
    }
}

然后使用UITabBarControllerDelegate的方法:
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // In case that we don't have index of target VC return nil and ignore animations
        guard let toIndex = tabBarController.viewControllers?.firstIndex(of: toVC) else { return nil }
        let fromIndex = tabBarController.selectedIndex
        return TransitioningObject(fromIndex: fromIndex, toIndex: toIndex)
    }
}

0

@samwize的答案已更新至Swift 5:

如果您希望所有选项卡更改都具有动画效果,则可以使用UITabBarControllerDelegate并实现此方法:

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
  let tabViewControllers = tabBarController.viewControllers!
  guard let toIndex = tabViewControllers.indexOf(value:viewController) else {
    return false
  }
  animateToTab(toIndex: toIndex, fadeOutFromView: false, fadeInToView: false)
  return true
}

通过调用animateToTab编程方式带动画更改选项卡:

func animateToTab(toIndex: Int, fadeOutFromView: Bool, fadeInToView: Bool) {
  let tabViewControllers = viewControllers!
  let fromView = selectedViewController!.view
  let toView = tabViewControllers[toIndex].view
  let fromIndex = tabViewControllers.indexOf(value:selectedViewController!)
  guard fromIndex != toIndex else {return}

  // Add the toView to the tab bar view
  fromView!.superview!.addSubview(toView!)

  // Position toView off screen (to the left/right of fromView)
  let screenWidth = UIScreen.main.bounds.width
  let scrollRight = toIndex > fromIndex!;
  let offset = (scrollRight ? screenWidth : -screenWidth)
  toView!.center = CGPoint(x: fromView!.center.x + offset, y: toView!.center.y)

  // Disable interaction during animation
  view.isUserInteractionEnabled = false
  if fadeInToView {
    toView!.alpha = 0.1
  }

  UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.curveEaseOut], animations: {

    if fadeOutFromView {
      fromView!.alpha = 0.0
    }

    if fadeInToView {
      toView!.alpha = 1.0
    }

    // Slide the views by -offset
    fromView!.center = CGPoint(x: fromView!.center.x - offset, y: fromView!.center.y);
    toView!.center   = CGPoint(x: toView!.center.x - offset, y: toView!.center.y);

  }, completion: { finished in
    // Remove the old view from the tabbar view.
    fromView!.removeFromSuperview()
    self.selectedIndex = toIndex
    self.view.isUserInteractionEnabled = true
  })
}

0

在 Swift 3 中,这对我有效:

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {

    if let fromView = tabBarController.selectedViewController?.view, let toView = viewController.view {

        if fromView == toView {
            return false
        }

        UIView.transition(from: fromView, to: toView, duration: 0.2, options: .transitionCrossDissolve) { (finished) in
        }
    }

    return true
}

-2

Swift 4+

你的 UITabBarControllerDelegate 方法应该像这样:

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {

    animateToTab(toIndex: (tabBarController.viewControllers?.index(of: viewController))!)
    return true
}

方法是这样的,

func animateToTab(toIndex: Int) {
    let tabViewControllers = viewControllers!
    let fromView = selectedViewController!.view
    let toView = tabViewControllers[toIndex].view
    let fromIndex = tabViewControllers.index(of: selectedViewController!)

    guard fromIndex != toIndex else {return}

    // Add the toView to the tab bar view
    fromView!.superview!.addSubview(toView!)

    // Position toView off screen (to the left/right of fromView)
    let screenWidth = UIScreen.main.bounds.size.width;
    let scrollRight = toIndex > fromIndex!;
    let offset = (scrollRight ? screenWidth : -screenWidth)
    toView!.center = CGPoint(x: fromView!.center.x + offset, y: toView!.center.y)

    // Disable interaction during animation
    view.isUserInteractionEnabled = false

    UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {

        // Slide the views by -offset
        fromView!.center = CGPoint(x: fromView!.center.x - offset, y: fromView!.center.y);
        toView!.center   = CGPoint(x: toView!.center.x - offset, y: toView!.center.y);

    }, completion: { finished in

        // Remove the old view from the tabbar view.
        fromView!.removeFromSuperview()
        self.selectedIndex = toIndex
        self.view.isUserInteractionEnabled = true
    });

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接