Користувацька анімація переходу контролера навігації


74

Я дотримувався деяких підручників зі створення власної анімації під час переходу з одного подання на інше.

Мій тестовий проект із використанням користувацького segue тут працює нормально, але хтось сказав мені, що більше не рекомендується робити власну анімацію в користувацькому segue, і я повинен використовувати UIViewControllerAnimatedTransitioning.

Я дотримувався кількох навчальних посібників, які використовують цей протокол, але всі вони стосуються модальної презентації (наприклад, цього підручника ).

Що я намагаюся зробити, це push segue всередині дерева контролера навігації, але коли я намагаюся зробити те ж саме з show (push) segue, це вже не працює.

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

І чи є в будь-якому разі я можу використовувати один метод для всіх перехідних анімацій? Було б незручно, якби одного дня я захотів зробити ту саму анімацію, але в підсумку довелося дублювати код двічі, щоб працювати над переходом модального та контролера.


@Rob Urgh Вибачте, якщо це звучить ідіотськи, але як я можу зробити свій контролер перегляду делегатом контролера навігації? Здається, я не можу прив’язати їх на розкадруванні та моєму 'navigationController: animationControllerForOperation: fromViewController: toViewController:' ніколи не викликати.
AVAVT

Ви можете просто зробити self.navigationController.delegate = self;в viewDidLoad. Ви можете отримати попередження, якщо ви також не вкажете в @interfaceрядку, що ваш контролер подання відповідає <UINavigationControllerDelegate>протоколу.
Роб

Цей код є швидким 4 для кругового переходу. stackoverflow.com/a/49321985/8334818
Прамод Море

Відповіді:


196

Щоб виконати власний перехід за допомогою навігаційного контролера ( UINavigationController), вам слід:

  • Визначте свій контролер подання, щоб він відповідав UINavigationControllerDelegateпротоколу. Наприклад, ви можете мати приватне розширення класу у .mфайлі контролера подання, яке визначає відповідність цьому протоколу:

    @interface ViewController () <UINavigationControllerDelegate>
    
    @end
    
  • Переконайтеся, що ви насправді вказали свій контролер перегляду як делегат вашого контролера навігації:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.navigationController.delegate = self;
    }
    
  • Впровадьте animationControllerForOperationу свій контролер подання:

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                      animationControllerForOperation:(UINavigationControllerOperation)operation
                                                   fromViewController:(UIViewController*)fromVC
                                                     toViewController:(UIViewController*)toVC
    {
        if (operation == UINavigationControllerOperationPush)
            return [[PushAnimator alloc] init];
    
        if (operation == UINavigationControllerOperationPop)
            return [[PopAnimator alloc] init];
    
        return nil;
    }
    
  • Впровадити аніматори для анімації push та pop, наприклад:

    @interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning>
    
    @end
    
    @interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning>
    
    @end
    
    @implementation PushAnimator
    
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return 0.5;
    }
    
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController* toViewController   = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
        [[transitionContext containerView] addSubview:toViewController.view];
    
        toViewController.view.alpha = 0.0;
    
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            toViewController.view.alpha = 1.0;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }
    
    @end
    
    @implementation PopAnimator
    
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
    {
        return 0.5;
    }
    
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController* toViewController   = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
        [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
    
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            fromViewController.view.alpha = 0.0;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }
    
    @end
    

    Це переходить до загасання, але ви можете сміливо налаштовувати анімацію, як вважаєте за потрібне.

  • Якщо ви хочете обробляти інтерактивні жести (наприклад, щось на зразок власного проведення пальцем зліва направо до спливаючого), вам слід застосувати контролер взаємодії:

    • Визначте властивість для контролера взаємодії (об’єкт, який відповідає UIViewControllerInteractiveTransitioning):

      @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
      

      Це UIPercentDrivenInteractiveTransitionхороший об’єкт, який важко піднімає оновлення власної анімації на основі того, наскільки завершений жест.

    • Додайте розпізнавач жестів до свого перегляду. Тут я просто реалізую лівий розпізнавач жестів для імітації спливаючого звуку:

      UIScreenEdgePanGestureRecognizer *edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)];
      edge.edges = UIRectEdgeLeft;
      [view addGestureRecognizer:edge];
      
    • Реалізуйте обробник розпізнавача жестів:

      /** Handle swipe from left edge
       *
       * This is the "action" selector that is called when a left screen edge gesture recognizer starts.
       *
       * This will instantiate a UIPercentDrivenInteractiveTransition when the gesture starts,
       * update it as the gesture is "changed", and will finish and release it when the gesture
       * ends.
       *
       * @param   gesture       The screen edge pan gesture recognizer.
       */
      
      - (void)handleSwipeFromLeftEdge:(UIScreenEdgePanGestureRecognizer *)gesture {
          CGPoint translate = [gesture translationInView:gesture.view];
          CGFloat percent   = translate.x / gesture.view.bounds.size.width;
      
          if (gesture.state == UIGestureRecognizerStateBegan) {
              self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
              [self popViewControllerAnimated:TRUE];
          } else if (gesture.state == UIGestureRecognizerStateChanged) {
              [self.interactionController updateInteractiveTransition:percent];
          } else if (gesture.state == UIGestureRecognizerStateEnded) {
              CGPoint velocity = [gesture velocityInView:gesture.view];
              if (percent > 0.5 || velocity.x > 0) {
                  [self.interactionController finishInteractiveTransition];
              } else {
                  [self.interactionController cancelInteractiveTransition];
              }
              self.interactionController = nil;
          }
      }
      
    • У делегаторі вашого контролера навігації вам також потрібно реалізувати interactionControllerForAnimationControllerметод делегування

      - (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                               interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
          return self.interactionController;
      }
      

Якщо ви загуглите "Підручник з користувацьких переходів UINavigationController" і ви отримаєте багато звернень. Або перегляньте відео про спеціальні переходи WWDC 2013 .


Поряд із цим, як зберегти за замовчуванням проведіть пальцем назад (Жест краю екрана)?
kidsid49

@ kidsid49 - Коли ви робите власні переходи, ви втрачаєте вбудований інтерактивний поп-жест, але у вас також є можливість реалізувати власні контролери взаємодії. Дивіться це відео WWDC для отримання додаткової інформації. Зверніться до дискусії про UIPercentDrivenInteractiveTransition(що значно спрощує процес). Я додав кілька відповідних фрагментів коду вище.
Роб

@Rob Дякую, що запропонували таку детальну відповідь. Я зміг успішно додати власну анімацію до свого додатка. Хоча у мене є запит, я додав власну анімацію поп-музики, щоб, коли користувач проводить пальцем зліва направо, з’являється вигляд зліва, як і у звичайного попу без спеціальних анімацій. Проблема полягає в тому, що я, здається, не отримую вид, що надходить зліва, щоб точно слідувати моєму великому пальцю / пальцю, завжди є певна відстань між місцем, де я торкаюся, і межею лівого виду, як би я зробив воно таке, що межа зору знаходиться саме там, де я торкаюся?
Shumais Ul Haq

Як ви запускаєте цей push / pop перехід. Я спробував створити сегменти (показати, наприклад, Push) в раскадровці та використовуючи метод performSegueWithIdentifier з viewControllers у стеку в NavigationController з делегатом, який обробляє перехід так, як це описано, але це не працює. Тож як зробити так, щоб цей перехід розпочався?
Марцін Капуста,

1
@Pavan - Ви animationControllerForOperationможете просто перевірити fromVCта / або toVCповернутися, nilякщо ви не хочете, щоб він виконував власну анімацію. Найпростішим є просто перевірити, чи це конкретний клас. Більш елегантно, ви можете розробити необов’язковий протокол, якому можуть відповідати контролери подання, з деяким логічним властивістю, щоб вказати, чи хочуть вони користувацький push / pop чи ні.
Роб

15

Ви можете додати наступний код раніше addSubview

  toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];

З іншого питання - користувацький перехід-для-push-анімації-з-навігаційним контролером-на-ios-9

З документації Apple для finalFrameForViewController:

Повертає прямокутник кінцевого кадру для подання контролера подання.

Прямокутник, повернутий цим методом, представляє розмір відповідного виду в кінці переходу. Для подання, яке охоплюється під час презентації, значенням, що повертається цим методом, може бути CGRectZero, але це також може бути дійсним прямокутником кадру.


4
Ого, ЦЕ вирішує мою проблему. Чому ця інформація, здається, ніде не є такою, яку я шукав? Як документація Apple, інші навчальні посібники ... Для мене немає сенсу, що ви повинні це встановити.
Девід

1
Справа в тому, що я використовую авторозкладку і ніде більше не встановлюю кадр ...
Девід,

9

Використовуючи ідеальні відповіді Rob's & Q i, ось спрощений код Swift, використовуючи ту саму анімацію затухання для .push та .pop:

extension YourViewController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController,
                              animationControllerFor operation: UINavigationControllerOperation,
                              from fromVC: UIViewController,
                              to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        //INFO: use UINavigationControllerOperation.push or UINavigationControllerOperation.pop to detect the 'direction' of the navigation

        class FadeAnimation: NSObject, UIViewControllerAnimatedTransitioning {
            func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
                return 0.5
            }

            func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
                let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
                if let vc = toViewController {
                    transitionContext.finalFrame(for: vc)
                    transitionContext.containerView.addSubview(vc.view)
                    vc.view.alpha = 0.0
                    UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
                    animations: {
                        vc.view.alpha = 1.0
                    },
                    completion: { finished in
                        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
                    })
                } else {
                    NSLog("Oops! Something went wrong! 'ToView' controller is nill")
                }
            }
        }

        return FadeAnimation()
    }
}

Не забудьте встановити делегата у методі viewDidLoad () YourViewController:

override func viewDidLoad() {
    //...
    self.navigationController?.delegate = self
    //...
}

5

Він працює швидко і 3, і 4

@IBAction func NextView(_ sender: UIButton) {
  let newVC = self.storyboard?.instantiateViewControllerWithIdentifier(withIdentifier: "NewVC") as! NewViewController

  let transition = CATransition()
  transition.duration = 0.5
  transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
  transition.type = kCATransitionPush
  transition.subtype = kCAGravityLeft
  //instead "kCAGravityLeft" try with different transition subtypes

  self.navigationController?.view.layer.add(transition, forKey: kCATransition)
  self.navigationController?.pushViewController(newVC, animated: false)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.