it-roy-ru.com

При реализации пользовательских представлений контроллера представления, где применить ограничения представленного представления?

Я прочитал всю документацию Apple о пользовательских модальных презентациях и не смог найти этот ответ. При представлении контроллера представления с использованием пользовательской анимации мы можем вернуть 3 вещи (если переход не является интерактивным; интерактивный может вернуть 4): контроллер представления и два контроллера анимации (один для настоящего и один для отклонения).

И в контроллере представления, и в настоящем контроллере анимации ни один из кодов Apple не содержит ограничений. Степень обсуждения фреймов - рекомендация Apple установить фрейм представленного представления контроллера представления в существующем контроллере анимации (ниже):

// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;

...и это все.

У меня проблема в том, что строка состояния двойной высоты не распознается этими представленными контроллерами представления в симуляторе (и устройствами, которыми я владею). Точнее, ни одно решение не работает во всех симуляторах - решения, которые работают в новых симуляторах (например, iPhone 8), не работают в старых симуляторах (например, iPhone 5). Если я позволю Apple обработать презентацию с использованием анимации UIKit по умолчанию, строка состояния двойной высоты будет хорошо обрабатываться представленным контроллером представления; поэтому я могу предположить, что ограничения в представленном контроллере представления не являются проблемой.

Поэтому я безуспешно обратился к методам containerViewDidLayoutSubviews и containerViewWillLayoutSubviews контроллера представления:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.frame = containerView!.bounds
}

Приведенный выше код работает в симуляторе только при первом появлении строки состояния двойной высоты; с этого момента этот метод перестает отвечать на запросы. Чтобы проверить это:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.frame = containerView!.bounds
    print("did layout")
}

приведенный выше метод останавливает печать на консоли после первого введения строки состояния двойной высоты. Но если я изменю задачу на что-то, что не изменит размер рамки представленного представления, как, скажем, изменение цвета фона:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.backgroundColor = UIColor.blue
    print("did layout")
}

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

Несмотря на это, Apple заявляет, что, если авторазметка используется правильно, программисту ничего не нужно делать! Итак, мой вопрос в том, где или как мы должны предоставить ограничения представления представленного контроллера представления, чтобы он мог адаптироваться к своему временному контейнеру? Потому что, ИМО, это проблема. Представленный контроллер представления принадлежит представлению контейнера перехода, которое является временным представлением, предоставленным UIKit, над которым мы не имеем большого доминирования. Если мы сможем привязать представленное представление к этому контейнеру, все проблемы будут решены. Но я никогда не видел, чтобы Apple делала это или даже говорила об этом.

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

ВЫВОДЫ: (1) Нет способа официально, правильно, аккуратно или последовательно обрабатывать строку состояния двойной высоты с модальными презентациями; (2) Apple испортила его с помощью строки состояния двойной высоты и не может дождаться того дня, когда на всех iPhone появится метка на экране.

15
bsod

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

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


Пример дела:

Анимация пользовательского интерфейса карты выглядит следующим образом:

enter image description here

Условия дальнейшего использования:

  • Parent - UIViewController с элементом кнопки панели «Detail»
  • Child - UIViewController с "Другой"

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

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

После нескольких дней отладки и поиска я нашел следующее решение (извините за некоторые магические числа;)):

UIView.animate(withDuration: transitionDuration(using: transitionContext),
                       delay: 0,
                       usingSpringWithDamping: 1,
                       initialSpringVelocity: 0.4,
                       options: .curveEaseIn, animations: {
            toVC.view.transform = CGAffineTransform(translationX: 0, y: self.finalFrame.minY)
            toVC.view.frame = self.finalFrame
            toVC.view.layer.cornerRadius = self.cornerRadius

            fromVC.view.layer.cornerRadius = self.cornerRadius
            var transform = CATransform3DIdentity
            transform = CATransform3DScale(transform, scale, scale, 1.0)
            transform = CATransform3DTranslate(transform, 0, wdiff, 0)
            fromVC.view.layer.transform = transform
            fromVC.view.alpha = 0.6
        }) { _ in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }

Главное здесь то, что Вы должны использовать CGAffineTransform3D, чтобы избежать проблем с анимацией и проблем с анимацией подпредставлений (2D Transforms не работают по неизвестным причинам). 

Этот подход исправляет, я надеюсь, все ваши проблемы без использования ограничений.

Не стесняйтесь задавать вопросы.

UPD: согласно строке состояния In-Call

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


Образцы кода:

Вы можете увидеть рабочее решение здесь на Github

P.S. Я не уверен, что можно опубликовать ссылку GitHub в ответе. Буду признателен за совет, как разместить 100-300 строк кода в ответе. 

5
fewlinesofcode

Я боролся с statusBar двойной высоты в моем текущем проекте, и мне удалось решить почти каждую проблему (последняя оставшаяся проблема - очень странная проблема преобразования, когда presentingViewController встроен в UITabBarController).

При изменении высоты строки состояния отправляется уведомление.
Ваш подкласс UIPresentationController должен подписаться на это конкретное уведомление и настроить фрейм containerView и его подпредставлений:

UIApplication.willChangeStatusBarFrameNotification

Вот пример кода, который я использую:

final class MyCustomPresentationController: UIPresentationController {

    // MARK: - StatusBar

    private func subscribeToStatusBarNotifications() {
        let notificationName = UIApplication.willChangeStatusBarFrameNotification
        NotificationCenter.default.addObserver(self, selector: #selector(statusBarWillChangeFrame(notification:)), name: notificationName, object: nil)
    }

    @objc private func statusBarWillChangeFrame(notification: Notification?) {
        if let newFrame = notification?.userInfo?[UIApplication.statusBarFrameUserInfoKey] as? CGRect {
            statusBarWillChangeFrame(to: newFrame)
        } else {
            statusBarWillChangeFrame(to: .zero)
        }
    }

    func statusBarWillChangeFrame(to newFrame: CGRect) {
        layoutContainerView(animated: true)
    }

    // MARK: - Object Lifecycle

    deinit {
        // Unsubscribe from all notifications
        NotificationCenter.default.removeObserver(self)
    }

    // MARK: - Layout

    /// Called when the status-bar is about to change its frame.
    /// Relayout the containerView and its subviews
    private func layoutContainerView(animated: Bool) {
        guard let containerView = self.containerView else { return }

        // Retrieve informations about status-bar
        let statusBarHeight = UIApplication.shared.statusBarFrame.height
        let normalStatusBarHeight = Constants.Number.statusBarNormalHeight // 20
        let isStatusBarNormal = statusBarHeight ==~ normalStatusBarHeight

        if animated {
            containerView.frame = …
            updatePresentedViewFrame(animated: true)
        } else {
            // Update containerView frame
            containerView.frame = …
            updatePresentedViewFrame(animated: false)
        }
    }

    func updatePresentedViewFrame(animated: Bool) {
        self.presentedView?.frame = …
    }
}

 result image

0
Vin Gazoil