При реализации пользовательских представлений контроллера представления, где применить ограничения представленного представления? - PullRequest
0 голосов
/ 04 ноября 2018

Я прочитал всю документацию 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 говорит, что, если авторазметка используется правильно, программисту ничего не нужно делать! Так что мой вопрос в том, где или как мы должны предоставить ограничения представленного контроллера представления, чтобы он мог адаптироваться к своему временному контейнеру? Потому что, IMO, это проблема. Представленный контроллер представления принадлежит представлению контейнера перехода, которое является временным представлением, предоставляемым UIKit, над которым мы не имеем большого доминирования. Если мы сможем привязать представленное представление к этому контейнеру, все проблемы будут решены. Но я никогда не видел, чтобы Apple делала это или даже говорила об этом.

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

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

Ответы [ 2 ]

0 голосов
/ 14 ноября 2018

Я боролся с двойной высотой 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 голосов
/ 09 ноября 2018

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

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


Пример дела:

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

enter image description here

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

  • Родитель - UIViewController с элементом кнопки панели «Детали»
  • Ребенок - 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-преобразования не работают по неизвестным причинам).

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

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

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

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


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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...