"From View Controller" исчезает с помощью UIViewControllerContextTransitioning

У меня есть одна проблема, и я описал ее ниже.

Я использую UIViewControllerContextTransitioning для пользовательских переходов.

У меня есть 2 контроллера вида, первый контроллер вида и второй контроллер вида.

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

Но я не могу видеть первый контроллер вида, и я вижу только черный экран под вторым контроллером вида.

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.transitionContext = transitionContext;
    if(self.isPresenting){
        [self executePresentationAnimation:transitionContext];
    }
    else{
       [self executeDismissalAnimation:transitionContext];
    }
  }

-(void)executePresentationAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
     UIView* inView = [transitionContext containerView];
     UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

     UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

     CGRect offScreenFrame = inView.frame;
     offScreenFrame.origin.y = inView.frame.size.height;
     toViewController.view.frame = offScreenFrame;

    toViewController.view.backgroundColor = [UIColor clearColor];
    fromViewController.view.backgroundColor = [UIColor clearColor];
    inView.backgroundColor = [UIColor  clearColor];
    [inView insertSubview:toViewController.view aboveSubview:fromViewController.view];
     // [inView addSubview:toViewController.view];
    CFTimeInterval duration = self.presentationDuration;
    CFTimeInterval halfDuration = duration/2;

    CATransform3D t1 = [self firstTransform];
    CATransform3D t2 = [self secondTransformWithView:fromViewController.view];

    [UIView animateKeyframesWithDuration:halfDuration delay:0.0 options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{

    [UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t1;
    }];

    [UIView addKeyframeWithRelativeStartTime:0.5f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t2;
    }];
    } completion:^(BOOL finished) {
    }];


    [UIView animateWithDuration:duration delay:(halfDuration - (0.3*halfDuration)) usingSpringWithDamping:0.7f initialSpringVelocity:6.0f options:UIViewAnimationOptionCurveEaseIn animations:^{
        toViewController.view.frame = inView.frame;
    } completion:^(BOOL finished) {
        [self.transitionContext completeTransition:YES];
    }];
}

При вызове [self.transitionContext completeTransition:YES]; первый контроллер вида внезапно исчезает, а под вторым контроллером вида отображается черный экран.

У кого-нибудь есть идея? Благодарю.

Ответ 1

У меня была такая же проблема здесь - выглядит как ошибка в iOS 8. Я подал радар.

Я использовал Reveal для проверки иерархии представлений после того, как экран стал черным. Ключ UIWindow полностью пуст - нет иерархии просмотров!

Reveal'd

Я немного поиграл, и похоже, что это простой способ для простых случаев. Вы можете просто повторно добавить представление toViewController в качестве подзона в окне ключа:

transitionContext.completeTransition(true)
UIApplication.sharedApplication().keyWindow!.addSubview(toViewController.view)

Я проверил, и окно ключа rootViewController по-прежнему настроено правильно, так что отлично. Я не уверен, что произойдет, если вы представите свой контроллер из уже представленного модального контроллера, поэтому для более сложных случаев вам придется экспериментировать.

Ответ 2

Я чувствую, что рассуждения, стоящие за этим, должны быть объяснены лучше.

Вид исчезает из-за того, что вы выводите представление контроллера представления из его исходного местоположения (иерархию представлений), помещаете его внутри контейнера, который предоставляет ваш аниматор, но никогда не возвращает его после окончания анимации. Таким образом, представление контроллера просмотра полностью удаляется из окна наблюдения (containerView) из окна.

В iOS 7 система всегда возвращала представления контроллеров представлений, которые участвуют в презентации (презентации и презентации) в их исходные места после того, как переход завершил анимацию автоматически. Это больше не происходит для некоторых стилей презентации в iOS 8.

Правило очень просто: аниматор должен работать только с представлением диспетчера представлений, если этот вид контроллера просмотра будет полностью скрыт (удален из иерархии представлений) к концу перехода. Другими словами, это означает, что после того, как анимация начального представления закончится, будет отображаться только представленное представление контроллера представления, а не представление представления представления представления. Например, если вы установите прозрачность представления представления контроллера представления на 50% и используете UIModalPresentationFullScreen, вы не сможете увидеть представление представления контроллера представления под представленным, но если вы используете UIModalPresentationOverFullscreen - вы будете (метод UIPresentationController shouldRemovePresentersView отвечает за указание этого).

Почему бы не позволить аниматору манипулировать представлением контроллера представления во все времена? Прежде всего, если представление представления представления представления будет оставаться видимым после окончания анимации в течение всего жизненного цикла презентации, нет необходимости анимировать его вообще - он просто остается там, где он есть. Во-вторых, если управление для этого контроллера представления передано контроллеру представления, контроллер представления, скорее всего, не будет знать, как макет, который рассматривает просмотр контроллера, когда это необходимо, например, когда ориентация меняется, но исходный владелец контроллера представления.

В iOS 8 viewForKey: был введен метод, чтобы получить представления, которыми манипулирует аниматор. Во-первых, это помогает следовать описанному выше правилу, возвращая нуль всякий раз, когда аниматор не должен касаться представления. Во-вторых, он может вернуть другое представление аниматора для анимации. Представьте, что вы выполняете презентацию, похожую на лист формы. В этом случае вам нужно добавить тень или украшение вокруг представленного представления контроллера представления. Аниматор будет анимировать это украшение, а представленное представление контроллера просмотра будет дочерним элементом оформления.

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

Правильно ли вы можете сделать правильно исправление проблемы с исчезающим представлением представления представления, когда вы явно помещаете его в представление контейнера аниматора:

  • Если вам не нужно анимировать представление представления представления представления, используйте viewForKey:, чтобы получать представления для анимации вместо того, чтобы обращаться к просмотру представлений контроллера напрямую. viewForKey: может возвращать нулевые или даже совершенно разные виды.

  • Если вы хотите анимировать представление представления представлений, вам следует использовать стиль UIModalPresentationFullScreen или продолжить использование UIModalPresentationCustom и реализовать свой собственный подкласс UIPresentationController с shouldRemovePresentersView return YES. Фактически реализация этого метода является основным различием между внутренними контроллерами представления, определенными стилями UIModalPresentationFullScreen и UIModalPresentationCustom, кроме того, что последний позволяет использовать пользовательские контроллеры представлений.

  • Во всех других редких случаях вам придется возвращать представление контроллера представления в его исходное местоположение, как и другие предложенные ответы.

Ответ 3

В iOS 8 вы должны управлять представлениями, возвращаемыми viewForKey:, вместо свойства .view контроллеров представления, возвращаемых viewControllerForKey:. Это не особенно ясно из бета-документации, но если вы посмотрите на источник для UIViewControllerTransitioning.h, вы увидите этот комментарий выше viewControllerForKey::

// Currently only two keys are defined by the
// system - UITransitionContextToViewControllerKey, and
// UITransitionContextFromViewControllerKey.
// Animators should not directly manipulate a view controller views and should
// use viewForKey: to get views instead.
- (UIViewController *)viewControllerForKey:(NSString *)key;

Поэтому вместо настройки фреймов и т.д. toViewController.view используйте возвращаемое значение [transitionContext viewForKey:UITransitionContextToViewKey].

Если вашему приложению необходимо поддерживать iOS7 и/или Xcode 5, вы можете использовать простой метод категорий в UIViewController, как показано ниже:

- (UIView *)viewForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        NSString *key = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] == self ? UITransitionContextFromViewKey : UITransitionContextToViewKey;
        return [transitionContext viewForKey:key];
    } else {
        return self.view;
    }
#else
    return self.view;
#endif
}

Затем получите ваши toViewController и fromViewController, как обычно, но получите представления с помощью [toViewController viewForTransitionContext:transitionContext].

Изменить: Кажется, есть ошибка, когда представление представления представления представления равно нулю при возврате из viewForKey, что мешает вам создавать модальные переходы, которые вообще оживляют представление (например, сползание или флип- по горизонтали). Я подал ошибку для iOS8 на rdar://17961976 (http://openradar.appspot.com/radar?id=5210815787433984). Также посмотрите пример проекта http://github.com/bcherry/TransitionBug

Редактировать 2: Спасибо graveley за это предложение, используя UIModalPresentationFullScreen, исправляет проблему. Возможно, это не ошибка. Apple может предполагать, что UIModalPresentationCustom только изменяет вид входящего модального. Если вы хотите изменить исходящее представление, вам необходимо гарантировать полноэкранную презентацию нового представления? В любом случае вы должны использовать viewForKey и UIModalPresentationFullScreen.

Ответ 4

Не устанавливать modalPresentationStyle в UIModalPresentationCustom исправил проблему для меня.

Другими словами, оставляя при умолчанию UIModalPresentationFullScreen вместо указания UIModalPresentationCustom, исправлена ​​проблема исчезновения представления. Обратите внимание, что протокол UIViewControllerTransitioningDelegate по-прежнему соблюдается, даже если оставить его по умолчанию. Если я правильно помню, когда-то требовалось UIModalPresentationCustom.

Пока работает, попробовал это только для неинтерактивных анимаций.

Ответ 5

Я нашел этот чрезвычайно полезный ответ в связанной ветке Лефтериса: fooobar.com/questions/64956/...

Подводя итог:

  1. установите modalPresentationStyle в .Custom
  2. подкласс UIPresentationController, переопределить mustRemovePresentersView (с НЕТ)
  3. переопределить presentationControllerForPresentedViewController в вашем классе TransitionDelegate и вернуть ваш пользовательский UIPresentationController

+1 в своем пользовательском переходе не добавляйте toView, когда происходит анимация увольнения.

Здесь продемонстрировано:

https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl=0 без всяких взломов! это как волшебство! :)

Ответ 6

В iOS 8 вам нужно создать UIPresentationController и реализовать метод ниже, в UIViewControllerTransitioningDelegate.

- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source;

Попросит вашего делегата использовать пользовательский контроллер представления для управления иерархией представлений при представлении контроллера представления.

Возвращаемое значение:

Пользовательский контроллер представления для управления модальной презентацией.

Обсуждение:

Когда вы представляете контроллер вида, используя UIModalPresentation Стиль пользовательской презентации, система вызывает это метод и запрашивает контроллер представления, который управляет вашим изготовленный под заказ style. Если вы реализуете этот метод, используйте его для создания и вернуть пользовательский объект контроллера презентации, который вы хотите использовать для управления процессом презентации.

Если вы не реализуете этот метод или если ваша реализация этого метод возвращает nil, система использует контроллер представления по умолчанию объект. Контроллер представления по умолчанию не добавляет никаких просмотров или содержимое иерархии представлений.

Доступность Доступна в iOS 8.0 и более поздних версиях.

Для получения дополнительной информации смотрите видео WWDC 2014:

https://developer.apple.com/videos/wwdc/2014/?include=228

Также есть пример кода из WWDC под названием "LookInside: адаптеры презентаций адаптивности и пользовательские объекты аниматора", которые вы можете загрузить со страницы кода примера WWDC 2014.

Вам может потребоваться немного изменить образец кода. Метод инициализации UIPresentationController изменился на:

initWithPresentedViewController:presented presentingViewController:presenting

Прежде чем он представил, а затем представил. Просто поменяйте их, и он должен работать.

Ответ 7

вместо [inView insertSubview: toViewController.view aboveSubview: fromViewController.view];   просто добавьте: [inView addSubview: toViewController.view];

if (self.presenting) {

    [transitionContext.containerView addSubview:toViewController.view];
    // your code

} else {
    // your code
}

Здесь вы можете увидеть пример: ссылка, и он работает на iOS 7 и iOS 8

Ответ 8

Здесь приведена версия Objective C для исправления Ash.

// my attempt at obj-c version of Ash fix
UIView *theToView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
[[[UIApplication sharedApplication] keyWindow] addSubview:theToView];
[transitionContext completeTransition:YES]

Мне пришлось поменять порядок и вызвать метод [transitionContext completeTransition:] после добавления представления назад, чтобы получить представление нового контроллера представления из блока завершения увольнения другого контроллера представления для правильной работы.

Я не знаю, что это исправит это для всех, но оно работает в моем приложении. Ура!

Ответ 9

Я нашел, что это отлично работает для Obj-C:

    [transitionContext completeTransition:YES];
    if(![[UIApplication sharedApplication].keyWindow.subviews containsObject:toViewController.view]) {
        [[UIApplication sharedApplication].keyWindow addSubview:toViewController.view];
    }

Кажется, что он отлично работает как на ios7, так и на ios8.

Ответ 10

Я обнаружил, что viewForKey:UITransitionContextToViewKey возвращает nil на ios8. Поэтому, если он равен нулю, я получаю представление от контроллера "до".

Однако, похоже, это приводит к тому, что представление "to" не перемещается из контейнера в окно при вызове completeTransition:YES. Поэтому, если viewForKey:UITransitionContextToViewKey возвращает nil, я падаю на toVC.view и отслеживаю тот факт, что он возвратил нуль, и после завершения я переместил его в начальный супервизор контейнера (который, оказывается, является окном).

Итак, этот код работает как на iOS7, так и на iOS8, и должен работать на iOS9, даже если они исправляют это или нет.

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    // Get the 'from' and 'to' views/controllers.
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    BOOL hasViewForKey = [transitionContext respondsToSelector:@selector(viewForKey:)]; // viewForKey is iOS8+.
    UIView *fromView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextFromViewKey] :
        fromVC.view;
    UIView *toView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextToViewKey] :
        toVC.view;

    // iOS8 has a bug where viewForKey:to is nil: http://stackoverflow.com/a/24589312/59198
    // The workaround is: A) get the 'toView' from 'toVC'; B) manually add the 'toView' to the container's
    // superview (eg the root window) after the completeTransition call.
    BOOL toViewNilBug = !toView;
    if (!toView) { // Workaround by getting it from the view.
        toView = toVC.view;
    }
    UIView *container = [transitionContext containerView];
    UIView *containerSuper = container.superview; // Used for the iOS8 bug workaround.

    // Perform the transition.
    toView.frame = container.bounds;
    [container insertSubview:toView belowSubview:fromView];
    [UIView animateWithDuration:kDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
        fromView.frame = CGRectOffset(container.bounds, 0, CGRectGetHeight(container.bounds));
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];

        if (toViewNilBug) {
            [containerSuper addSubview:toView];
        }
    }];
}

Ответ 11

Я обнаружил, что эта ошибка (и многое другое!) исчезает, если вы установите modalPresentationStyle = UIModalPresentationFullScreen. Вы, конечно же, получите свою настраиваемую анимацию перехода.

Ответ 12

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

В некоторых ответах упоминается изменение modalPresentationStyle на .overFullScreen. Это правильно, .overCurrentContext тоже будет работать. Ожидается, и поведение Apple будет документировать. Но почему это не работает для всех? Почему весь взломанный код и комбинации этого с чем-то другим и сумасшедшие вещи, которые вы не должны делать?

Оказывается, вам нужно установить стиль презентации ПЕРЕД ЗАГРУЗКИМИ НАГРУЗКАМИ. Не после. Сделайте это в init или сделайте это с предыдущего контроллера или, как вам угодно, до тех пор, пока он не загрузится.

Ответ 13

Использование нового UIModalPresentationOverCurrentContext исправил его для меня. Мой первоначальный переход на iOS 7 состоял только в том, чтобы иметь размытый фон представления под модальным.

Ответ 14

Я тоже застрял в этом вопросе. Я искал создание настраиваемого перехода с полупрозрачным фоном, где я все еще мог видеть контроллер вида, из которого я шел, но у меня только черный фон. Я нашел ответ Mark Aron в этой теме, помог мне, но он написан в Objective C, поэтому вот версия этого теста Swift 3, которую я тестировал для iOS 9 и iOS 10:

  • Создайте подкласс UIPresentationController. Переопределите параметр shouldRemovePresentersView на false следующим образом:

    class ModalPresentationController: UIPresentationController {
    
    override var shouldRemovePresentersView: Bool {
    return false
    }
    
    override func containerViewWillLayoutSubviews() {
    presentedView?.frame = frameOfPresentedViewInContainerView
    }
    }
    
  • В месте, где вы создаете новый контроллер представления и устанавливаете его делегат перехода, укажите, что вы хотите, чтобы он отображал собственный стиль модальной презентации следующим образом:

    let newVC = mainStoryboard.instantiateViewController(withIdentifier: "newVC") as! NewViewController 
    
    newVC.transitioningDelegate = self
    
    newVC.modalPresentationStyle = UIModalPresentationStyle.custom
    
    newVC.modalPresentationCapturesStatusBarAppearance = true //optional
    
    present(newVC, animated: true, completion: nil)
    
  • Теперь переопределите метод presentationController вашего UIViewControllerTransitioningDelegate и верните свой пользовательский UIPresentationController. У меня было мое расширение для моего текущего класса:

    extension CurrentViewController: UIViewControllerTransitioningDelegate {
    
    //this is where you implement animationController(forPresented) and animationController(forDismissed) methods
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
    
    return ModalPresentationController(presentedViewController: presented, presenting: source)
    
    }
    }
    

Еще одна вещь, которую следует отметить: вы не должны пытаться ссылаться на ваш метод fromView в вашем классе currentAnimator. Это будет равно нулю, и вы получите сообщение об ошибке во время выполнения. Помимо этого, если вы реализуете вещи, подобные вещам, вы получите свой пользовательский переход с его анимацией и полупрозрачным фоном, если вы его сделаете.

Ответ 15

добавляет контроллер представления в качестве дочернего элемента другого контроллера представления.

[self addChildViewController:childViewController];                 

проверьте и дайте мне знать.