iPhone: Как переключать вкладки с анимацией?


106

Я программно переключаю вкладки в приложении, управляемом панелью вкладок, используя UITabBarController.selectedIndex. Проблема, которую я пытаюсь решить, заключается в том, как анимировать переход между представлениями. т.е. из представления текущей вкладки в представление выбранной вкладки.

Первой мыслью было использовать UITabBarControllerDelegate, но похоже, что это не вызывается при программном переключении вкладок. Сейчас я рассматриваю UITabBarDelegate.didSelectItem: как возможный крючок для установки анимации перехода.

Кому-нибудь удалось анимировать переходы? Если да, то как?


1
FWIW, многие из этих высоко оцененных ответов предшествуют пользовательским переходам, изложенным в ответе Руно и Хеберти . Это правильный способ справиться с этой настраиваемой анимацией. См. Видео WWDC 2013 « Пользовательские переходы с использованием контроллеров представления» .
Роб

Ответы:


154

Обновление 04/2016: Джастед хотел обновить это, чтобы поблагодарить всех за все голоса. Также обратите внимание, что это было изначально написано еще когда ... до ARC, до ограничений, до ... много всего! Поэтому, пожалуйста, примите это во внимание при принятии решения об использовании этих методов. Могут быть более современные подходы. Ах да, если найдешь. Пожалуйста, добавьте ответ, чтобы все могли видеть. Спасибо.

Некоторое время спустя ...

После долгих исследований я пришел к двум рабочим решениям. Оба они работали и делали анимацию между вкладками.

Решение 1.Переход из представления (простой)

Это самый простой способ, в котором используется предопределенный метод перехода UIView. С этим решением нам не нужно управлять представлениями, потому что метод делает всю работу за нас.

// Get views. controllerIndex is passed in as the controller we want to go to. 
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];

// Transition using a page curl.
[UIView transitionFromView:fromView 
                    toView:toView 
                  duration:0.5 
                   options:(controllerIndex > tabBarController.selectedIndex ? UIViewAnimationOptionTransitionCurlUp : UIViewAnimationOptionTransitionCurlDown)
                completion:^(BOOL finished) {
                    if (finished) {
                        tabBarController.selectedIndex = controllerIndex;
                    }
                }];

Решение 2: прокрутка (более сложная)

Более сложное решение, но дает вам больше контроля над анимацией. В этом примере мы заставляем представления скользить по экрану. В этом случае нам нужно самостоятельно управлять просмотром.

// 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];

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

[UIView animateWithDuration:0.3 
                 animations: ^{

                     // Animate the views on and off the screen. This will appear to slide.
                     fromView.frame =CGRectMake((scrollRight ? -320 : 320), viewSize.origin.y, 320, viewSize.size.height);
                     toView.frame =CGRectMake(0, viewSize.origin.y, 320, viewSize.size.height);
                 }

                 completion:^(BOOL finished) {
                     if (finished) {

                         // Remove the old view from the tabbar view.
                         [fromView removeFromSuperview];
                         tabBarController.selectedIndex = controllerIndex;                
                     }
                 }];

Это решение в Swift:

extension TabViewController: UITabBarControllerDelegate {
      public func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {

           let fromView: UIView = tabBarController.selectedViewController!.view
           let toView  : UIView = viewController.view
           if fromView == toView {
                 return false
           }

           UIView.transitionFromView(fromView, toView: toView, duration: 0.3, options: UIViewAnimationOptions.TransitionCrossDissolve) { (finished:Bool) in

        }
        return true
   }
}

1
Большое спасибо за ответ, это действительно хорошо работает. Однако я обнаружил одну ошибку в обоих решениях, я не уверен, случается ли это со всеми, но кажется, что при переходе страницы между панелью навигации и строкой состояния есть промежуток, а затем после завершения анимации разрыв закрывается. Это делает финал анимации немного нервным. Вы знаете, почему это происходит?
Enrico Susatyo

Хм, с моим кодом этого не произошло. Это очень похоже на проблему, которую я видел раньше, когда положение нового фрейма представлений неверно по отношению к окну и строке состояния. Попробуйте запустить код, чтобы поменять местами представления, не выполняя переход, и посмотрите, происходит ли это по-прежнему.
drekka

Да, это все еще происходит без перехода. Я попробовал первый способ. Возможно, это расположение кадра, я еще немного поиграю с ним. Я попытался сдвинуть фрейм вверх и попытаться сопоставить фрейм с fromView, но пока мне не повезло ...
Энрико Сусатио

2
@EmileCormier поместил его в метод делегата TabBar shouldSelectViewControllerи вернул NO там
cheesus

2
@drekka у меня не работает. Можете ли вы объяснить, откуда происходит controllerIndex? и почему бы вам просто не использовать [viewController view] из метода tabBarControllerDelegate для 'toView'? Thnaks
шаннога

25

Ниже я пытаюсь использовать кодовую форму drekka в методе делегата (UITabBarControllerDelegate)

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {

    NSArray *tabViewControllers = tabBarController.viewControllers;
    UIView * fromView = tabBarController.selectedViewController.view;
    UIView * toView = viewController.view;
    if (fromView == toView)
        return false;
    NSUInteger fromIndex = [tabViewControllers indexOfObject:tabBarController.selectedViewController];
    NSUInteger toIndex = [tabViewControllers indexOfObject:viewController];

    [UIView transitionFromView:fromView
                        toView:toView
                      duration:0.3
                       options: toIndex > fromIndex ? UIViewAnimationOptionTransitionFlipFromLeft : UIViewAnimationOptionTransitionFlipFromRight
                    completion:^(BOOL finished) {
                        if (finished) {
                            tabBarController.selectedIndex = toIndex;
                        }
                    }];
    return true;
}

2
Вы должны вернуть какое-то значение в соответствии с объявлением метода, но этот подход отлично работает +1
voromax 05

2
Вы можете установить делегата в свой файл реализации UITabController, добавив self.delegate = self; в вашей функции viewDidLoad (). Это позволит вызвать указанную выше функцию.
Крис Фремген

21

Мое решение для iOS7.0 или выше.

Вы можете указать настраиваемый контроллер анимации в делегате панели вкладок.

Реализуйте контроллер анимации следующим образом:

@interface TabSwitchAnimationController : NSObject <UIViewControllerAnimatedTransitioning>

@end

@implementation TabSwitchAnimationController

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
    return 0.2;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController* fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController* toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView* toView = toVC.view;
    UIView* fromView = fromVC.view;

    UIView* containerView = [transitionContext containerView];
    [containerView addSubview:toView];
    toView.frame = [transitionContext finalFrameForViewController:toVC];

    // Animate by fading
    toView.alpha = 0.0;
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         toView.alpha = 1.0;
                     }
                     completion:^(BOOL finished) {
                         toView.alpha = 1.0;
                         [fromView removeFromSuperview];
                         [transitionContext completeTransition:YES];
                     }];
}

@end

Затем используйте его в своем UITabBarControllerDelegate:

- (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC
{
    return [[TabSwitchAnimationController alloc] init];
}

2
И не забудьте подключить своего делегата к выходу делегата TabViewController. Работал красиво. Самое чистое решение здесь.
Эндрю Дункан

Можно ли это сделать с помощью раскадровки и быстро, когда я изучаю эту функцию в IOS 10.x?
mobibob

17

Вместо использования tabBarController:shouldSelectViewController:лучше реализоватьtabBarController:animationControllerForTransitionFromViewController:toViewController:

TransitioningObject.swift

import UIKit

class TransitioningObject: NSObject, UIViewControllerAnimatedTransitioning {

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        let fromView: UIView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let toView: UIView = transitionContext.viewForKey(UITransitionContextToViewKey)!

        transitionContext.containerView().addSubview(fromView)
        transitionContext.containerView().addSubview(toView)

        UIView.transitionFromView(fromView, toView: toView, duration: transitionDuration(transitionContext), options: UIViewAnimationOptions.TransitionCrossDissolve) { finished in
            transitionContext.completeTransition(true)
        }
    }

    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 0.25
    }
}

TabBarViewController.swift

import UIKit

    class TabBarViewController: UITabBarController, UITabBarControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.delegate = self
    }

    // MARK: - Tabbar delegate

    func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return TransitioningObject()
    }
}

3
Кажется, это лучший ответ. Никаких проблем с этим решением.
sabiland 04

15

Я думаю, вы можете легко добиться переходов для UITabBarControlelr с помощью CATransition; Это также решит любые побочные эффекты использования transitionFromView: toView:

Используйте это внутри своего настраиваемого класса TabBarController, расширенного из UITabBarController.

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController (UIViewController*)viewController {

    CATransition *animation = [CATransition animation];
    [animation setType:kCATransitionFade];
    [animation setDuration:0.25];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:
                              kCAMediaTimingFunctionEaseIn]];
    [self.view.window.layer addAnimation:animation forKey:@"fadeTransition"];
}

Надеюсь это поможет :)


1
Я думаю, мы можем использовать shouldSelectViewController вместо didSelectViewController
Райан Ву

13

Я написал сообщение, попробовав различные ответы здесь.

Код находится в Swift, и вы можете программно изменить вкладку с анимацией, вызвав animateToTab.

func animateToTab(toIndex: Int) {
    let tabViewControllers = viewControllers!
    let fromView = selectedViewController!.view
    let toView = tabViewControllers[toIndex].view    
    let fromIndex = tabViewControllers.indexOf(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.mainScreen().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.userInteractionEnabled = false

    UIView.animateWithDuration(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.userInteractionEnabled = true
        })
}

Если вы хотите, чтобы при смене всех вкладок отображалась анимация, подключите ее UITabBarControllerDelegateкак таковую:

func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
    let tabViewControllers = tabBarController.viewControllers!
    guard let toIndex = tabViewControllers.indexOf(viewController) else {
        return false
    }

    // Our method
    animateToTab(toIndex)

    return true
}

Это очень чисто и красиво оживляет.
Мохаммад Зекраллах

9

Мое решение в Swift:

Создайте собственный класс TabBar и установите его в раскадровке TabBar

class MainTabBarController: UITabBarController, UITabBarControllerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
    // Do any additional setup after loading the view.
}

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


    let tabViewControllers = tabBarController.viewControllers!
    let fromView = tabBarController.selectedViewController!.view
    let toView = viewController.view

    if (fromView == toView) {
        return false
    }

    let fromIndex = tabViewControllers.indexOf(tabBarController.selectedViewController!)
    let toIndex = tabViewControllers.indexOf(viewController)

    let offScreenRight = CGAffineTransformMakeTranslation(toView.frame.width, 0)
    let offScreenLeft = CGAffineTransformMakeTranslation(-toView.frame.width, 0)

    // start the toView to the right of the screen


    if (toIndex < fromIndex) {
        toView.transform = offScreenLeft
        fromView.transform = offScreenRight
    } else {
        toView.transform = offScreenRight
        fromView.transform = offScreenLeft
    }

    fromView.tag = 124
    toView.addSubview(fromView)

    self.view.userInteractionEnabled = false
    UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: {

        toView.transform = CGAffineTransformIdentity

        }, completion: { finished in

            let subViews = toView.subviews
            for subview in subViews{
                if (subview.tag == 124) {
                    subview.removeFromSuperview()
                }
            }
            tabBarController.selectedIndex = toIndex!
            self.view.userInteractionEnabled = true

    })

    return true
 }

}

это не работает в ios9 - ошибка, возвращаемая методом поиска, т.е. Downcast from '[UIViewController]?' to '[UIViewController]' разворачивает только опции; вы хотели использовать '!'?
lozflan

Это было почти хорошо, за исключением того, что я обнаружил ошибку, которая не будет анимироваться ( finishedбудет ложной). Я не знаю, почему это произошло, но я думаю, что это связано с преобразованием CA, которое считает, что «анимировать нечего». Я переключился на анимацию с кадрами, и это сработало.
samwize

3

Я использовал решение @ Mofumofu и обновил его до Swift 1.2, а также реализовал анимацию вверх / вниз. Это означает, что новый ViewController появляется и подталкивает старый, если индекс нового ViewController больше, чем у старого. В противном случае направление будет вниз.

class TabScrollPageAnimationController: NSObject, UIViewControllerAnimatedTransitioning {

    let tabBarController: UITabBarController

    init(tabBarController: UITabBarController) {
        self.tabBarController = tabBarController
    }

    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 0.5
    }

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        if let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
            let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) {
                let fromView = fromVC.view
                let toView = toVC.view

                let containerView = transitionContext.containerView()

                var directionUpwardMultiplier: CGFloat = 1.0
                if let vcs = tabBarController.viewControllers as? [UIViewController],
                    let fIndex = find(vcs, fromVC),
                    let tIndex = find(vcs, toVC) {
                        directionUpwardMultiplier = (fIndex < tIndex) ? +1.0 : -1.0
                }

                containerView.clipsToBounds = false
                containerView.addSubview(toView)

                var fromViewEndFrame = fromView.frame
                fromViewEndFrame.origin.y -= (containerView.frame.height * directionUpwardMultiplier)

                let toViewEndFrame = transitionContext.finalFrameForViewController(toVC)
                var toViewStartFrame = toViewEndFrame
                toViewStartFrame.origin.y += (containerView.frame.height * directionUpwardMultiplier)
                toView.frame = toViewStartFrame

                toView.alpha = 0.0
                UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
                    toView.alpha = 1.0
                    toView.frame = toViewEndFrame
                    fromView.alpha = 0.0
                    fromView.frame = fromViewEndFrame
                }, completion: { (completed) -> Void in
                    toView.alpha = 1.0
                    fromView.removeFromSuperview()
                    transitionContext.completeTransition(completed)
                    containerView.clipsToBounds = true
                })

        }
    }

}

В контейнере ViewController:

extension XYViewController: UITabBarControllerDelegate {

    func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return TabScrollPageAnimationController(tabBarController: tabBarController)
    }

}

3

Вот мое решение Swift 3:

Я переопределяю selectedIndex моего UITabBarViewController следующим образом:

override var selectedIndex: Int{
    get{
        return super.selectedIndex
    }
    set{
        animateToTab(toIndex: newValue)
        super.selectedIndex = newValue
    }
}

Затем я использую эту функцию, которая имитирует встроенную анимацию push / pop:

func animateToTab(toIndex: Int) {
    guard let tabViewControllers = viewControllers, tabViewControllers.count > toIndex, let fromViewController = selectedViewController, let fromIndex = tabViewControllers.index(of: fromViewController), fromIndex != toIndex else {return}

    view.isUserInteractionEnabled = false

    let toViewController = tabViewControllers[toIndex]
    let push = toIndex > fromIndex
    let bounds = UIScreen.main.bounds

    let offScreenCenter = CGPoint(x: fromViewController.view.center.x + bounds.width, y: toViewController.view.center.y)
    let partiallyOffCenter = CGPoint(x: fromViewController.view.center.x - bounds.width*0.25, y: fromViewController.view.center.y)

    if push{
        fromViewController.view.superview?.addSubview(toViewController.view)
        toViewController.view.center = offScreenCenter
    }else{
        fromViewController.view.superview?.insertSubview(toViewController.view, belowSubview: fromViewController.view)
        toViewController.view.center = partiallyOffCenter
    }

    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseIn, animations: {
        toViewController.view.center   = fromViewController.view.center
        fromViewController.view.center = push ? partiallyOffCenter : offScreenCenter
    }, completion: { finished in
        fromViewController.view.removeFromSuperview()
        self.view.isUserInteractionEnabled = true
    })
}

Я надеюсь, что это помогает :)



2

это можно решить двумя способами

1 - Запишите это в файл AppDelegate.m один раз. Не забудьте включить UITabBarControllerDelegate, используя <> после двоеточия (:) в вашем AppDelegate.h

-(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 02

Я попробовал оба, первый отображал новый вид, а затем анимировал его, что выглядело странно. Второй вроде не повлиял. Я вошел в представление, связанное с tab2, добавил код в viewWillAppear и протестировал его, и не было видимой анимации между вкладками.
Шеннон Коул

Пробовал это с проектом Xcode TabBarController по умолчанию. Не повезло ни с 1, ни с 2. Я очень хотел, чтобы они работали. :) Я что-то упускаю?
Эндрю Дункан

Мне тоже не повезло .. есть идеи?
Джон

2

Вы можете анимировать в зависимости от затронутого элемента - в этом примере мы flipFromLeft, если затронутый индекс>, чем предыдущий выбранный индекс, и мы flipFromRight, если затронутый индекс <, чем предыдущий выбранный индекс. Это Swift 4. Реализуйте метод UITabBarControllerDelegate.

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

    let fromView: UIView = tabBarController.selectedViewController!.view
    let toView: UIView = viewController.view

    if fromView == toView {
        return false
    }

    if let tappedIndex = tabBarController.viewControllers?.index(of: viewController) {
        if tappedIndex > tabBarController.selectedIndex {
            UIView.transition(from: fromView, to: toView, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromLeft, completion: nil)
        } else {
            UIView.transition(from: fromView, to: toView, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromRight, completion: nil)
        }
    }
    return true
}

Это не работает. Я реализовал это в контроллере представления
devedv

@devedv, что не работает с этим решением? Вы установили UITabBarControllerDelegate в свой ViewController?
Teetz

да, я сделал следующее в классе AppDelegate AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate {}. Я новичок в Swift, можете ли вы уточнить шаги в своем ответе, пожалуйста?
devedv

@devdev Если это ваш класс AppDelegate, поместите указанную выше функцию в свой AppDelegate, и она должна работать
Teetz


1

Ответ Дрекки действительно великолепен. Я немного изменил переход прокрутки, чтобы анимация была больше похожа на анимацию нажатия Apple. Я добавил дополнительную анимацию по завершении первой анимации, чтобы эффект скольжения выглядел правильно.

// 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

Я хотел использовать флип-переход между двумя дочерними контроллерами представления при нажатии кнопки и добился этого следующим образом:

-(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

Вот мой рабочий код ( для 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,

Спасибо за подсказку, Феррибиг! Я действительно пытался задокументировать код настолько, насколько это было возможно, чтобы облегчить его понимание, надеюсь, это поможет
Науэль Ролдан

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
}

0

@samwize Ответ, переведенный на Swift 3 - 2 больших пальца вверх на этом, создает эффект страницы слева направо:

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

Ответ @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
  })
}

-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
    });

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.