"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]; вызывается, внезапно исчезает первый контроллер вида, и под вторым контроллером вида отображается черный экран.

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

15 ответов

Решение

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

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

Reveal'd

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

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

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

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

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

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

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

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

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

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

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

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

  2. Если вы хотите анимировать представление контроллеров представления представления, вы должны рассмотреть возможность использования UIModalPresentationFullScreen стиль или продолжать использовать UIModalPresentationCustom и реализовать свой собственный подкласс UIPresentationController с shouldRemovePresentersView возврате 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's 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: Спасибо Грэвли за предложение, использование UIModalPresentationFullScreen решает проблему. Возможно, это не ошибка. Apple может предположить, что UIModalPresentationCustom только изменяет вид входящего модального сообщения. Если вы хотите изменить исходящее представление, вам нужно гарантировать полноэкранное представление нового представления? В любом случае, вы должны использовать viewForKey и UIModalPresentationFullScreen.

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

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

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

Я нашел этот чрезвычайно полезный ответ в связанной ветке Лефтериса: /questions/33794854/animatsiya-predstavleniya-predstavleniya-v-uipresentationcontroller/33794864#33794864

Подвести итог:

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

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

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

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

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

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

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

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

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

Обсуждение:

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

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

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

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

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

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

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

initWithPresentedViewController:presented presentingViewController:presenting

До того, как это было представлено, а затем представлено. Просто поменяйте их местами, и это должно сработать.

Вместо [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

Вот версия Эша для Objective C.

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

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

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

Я обнаружил, что это работает нормально для Obj-C:

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

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

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

Однако, это, кажется, приводит к тому, что представление "to" не перемещается из контейнера в окно, когда completeTransition:YES называется. Так что если viewForKey:UITransitionContextToViewKey возвращается ноль, я падаю 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://stackru.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];
        }
    }];
}

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

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

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

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

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

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

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

    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)
    
  3. Теперь переопределите метод 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 в своем классе presentAnimator. Это будет ноль, и вы получите ошибку во время выполнения. Кроме этого, если вы реализуете подобные вещи, вы получите свой собственный переход с его анимацией и полупрозрачным фоном, если вы его сделаете.

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

Хорошо, ребята, я думаю, что я решил один случай, когда "рабочий аниматор" перестает работать должным образом, когда вы создаете приложение в iOS 13 и выше.

EnvXcode 11.1, iOS 13.1

Проблема

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

Я настроил переход в IB путем перетаскивания из моей коллекции VC в подробный VC. Стиль перехода - "Представлять модально", а для презентации установлен "Полноэкранный режим".

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

1. Сразу после того, как следующая строка вызывается из функции 'animateTransition()', просмотр коллекции возобновляется и отображается

transitionContext.completeTransition(true)

2. до тех пор, пока подробное представление не полностью покрывает представление коллекции, представление коллекции не исчезнет при переходе обратно от представления подробностей.

Решение

Если честно, я мало знаю о том, как работает анимированный переход. Поэтому я могу следить только за этим и другим постом, пробуя каждый из ответов. К сожалению, у меня ни один из них не работает. Наконец, я дошел до того, что единственное, что я могу настроить, - это стиль представления segue в IB (что я должен был сделать в самом начале). Когда я устанавливаю презентацию на "Во весь экран", происходит чудо, и моя проблема решается. Подробное представление может отображаться в полноэкранном режиме с анимацией, и когда оно закрыто, я могу видеть как представление коллекции в качестве фона, так и анимированное подробное представление.

Затем еще одно открытие по дороге

3. Для обращения к toView и fromView работают оба следующих метода.

Косвенно:

transitionContext.viewController(forKey: .to)?.view
transitionContext.viewController(forKey: .from)?.view

Прямо способом:

transitionContext.view(forKey: .to)
transitionContext.view(forKey: .from)

Но когда я переключил стиль перехода на 'Over Full Screen', прямой способ вернуть 'nil' как для 'toView', так и 'fromView' и только косвенно работает, эта проблема также упоминается в другом сообщении, поэтому я думаю, что это стоит разместить здесь свое маленькое открытие.

Надеюсь, это будет полезно кому-то в будущем.

У меня была такая же проблема при отключении контроллера представления содержимого.

В моем приложении есть этот родительский контроллер представления, показывающий контроллер дочернего представления (представляющий vc) модально. Затем при нажатии на подпредставление в childVC отображается другой vc (который я называю контроллером представления содержимого (представленный vc))

Моя проблема в том, что при отклонении contentVC (теперь представляющего vc) он должен перейти в дочерний VC (теперь представленный VC), но как только мой пользовательский переход завершается, childVC внезапно исчезает, показывая родительский VC.

Что я сделал для решения этой проблемы, так это

  1. изменить .modalPresentationStyle childVC, представленного parentVC по умолчанию .automatic к .fullscreen.
  2. Затем изменил .modalPresentationStyle контента VC в .fullscreen также.

Это решает проблему. но он не будет показывать ваш дочерний VC как таблицу стилей карты поверх parentVC (при использовании.overCurrentContext или автоматически), что является новым в iOS 13.

Хотелось бы знать, есть ли какое-либо решение, которое сохранит таблицу стилей карты для childVC, когда она будет представлена ​​родителем.

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

[self addChildViewController:childViewController];                 

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

Другие вопросы по тегам