В iOS 13, похоже, используется новый UIPresentationController для представления контроллеров модального представления, но тот, который не полагается на создание снимков представленного контроллера представления (как это делают большинство / все библиотеки). Контроллер представления представления является «живым» и продолжает отображать анимацию/изменения, в то время как контроллер модального представления отображается поверх прозрачного/тонированного фона.
Я могу легко воспроизвести это (поскольку цель состоит в том, чтобы сделать обратно совместимую версию для iOS 10/11/12 и т. д.), используя CGAffineTransform в представлении контроллера представления, однако часто при вращении устройства представление представления начинается деформироваться и расти за пределы, по-видимому, потому, что система обновляет свой frame, пока к нему применяется активный transform.
Согласно документации, frame не определено, когда к представлению применяется transform. Учитывая, что система, похоже, изменяет фрейм, а не я, как мне решить эту проблему, не прибегая к хакерским решениям, в которых я обновляю границы представления представления? Мне нужно, чтобы этот контроллер презентации оставался универсальным, поскольку контроллер представления может быть любой формы и формы и не обязательно будет полноэкранным представлением.
Вот что у меня есть до сих пор - это простой подкласс UIPresentationController, который, кажется, работает нормально, однако вращение устройства, а затем отклонение представленного контроллера представления, кажется, деформирует границы представленного контроллера представления (становится слишком широким или сжимается, в зависимости от того, вы представили модальный контроллер в альбомной/портретной ориентации)
class SheetPresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
return CGRect(x: 40, y: containerView!.bounds.height / 2, width: containerView!.bounds.width-80, height: containerView!.bounds.height / 2)
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
if let _ = presentingViewController.transitionCoordinator {
// We're transitioning - don't touch the frame yet as it'll
// clash with our transform
} else {
self.presentedView?.frame = self.frameOfPresentedViewInContainerView
}
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
containerView?.backgroundColor = .clear
if let coordinator = presentingViewController.transitionCoordinator {
coordinator.animate(alongsideTransition: { [weak self] _ in
self?.containerView?.backgroundColor = UIColor.black.withAlphaComponent(0.3)
// Scale the presenting view
self?.presentingViewController.view.layer.cornerRadius = 16
self?.presentingViewController.view.transform = CGAffineTransform.init(scaleX: 0.9, y: 0.9)
}, completion: nil)
}
}
override func dismissalTransitionWillBegin() {
if let coordinator = presentingViewController.transitionCoordinator {
coordinator.animate(alongsideTransition: { [weak self] _ in
self?.containerView?.backgroundColor = .clear
self?.presentingViewController.view.layer.cornerRadius = 0
self?.presentingViewController.view.transform = .identity
}, completion: nil)
}
}
}
И контроллер Presenting Animation:
import UIKit
final class PresentingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
return
}
let springTiming = UISpringTimingParameters(dampingRatio: 1.0, initialVelocity: CGVector(dx:1.0, dy: 1.0))
let animator: UIViewPropertyAnimator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), timingParameters: springTiming)
let containerView = transitionContext.containerView
containerView.addSubview(presentedViewController.view)
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
presentedViewController.view.frame = finalFrameForPresentedView
// Move it below the screen so it slides up
presentedViewController.view.frame.origin.y = containerView.bounds.height
animator.addAnimations {
presentedViewController.view.frame = finalFrameForPresentedView
}
animator.addCompletion { (animationPosition) in
if animationPosition == .end {
transitionContext.completeTransition(true)
}
}
animator.startAnimation()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
}
А также отклоняющий контроллер анимации:
import UIKit
final class DismissingAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
return
}
guard let presentingViewController = transitionContext.viewController(forKey: .to) else {
return
}
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
let containerView = transitionContext.containerView
let offscreenFrame = CGRect(x: finalFrameForPresentedView.minX, y: containerView.bounds.height, width: finalFrameForPresentedView.width, height: finalFrameForPresentedView.height)
let springTiming = UISpringTimingParameters(dampingRatio: 1.0, initialVelocity: CGVector(dx:1.0, dy: 1.0))
let animator: UIViewPropertyAnimator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), timingParameters: springTiming)
animator.addAnimations {
presentedViewController.view.frame = offscreenFrame
}
animator.addCompletion { (position) in
if position == .end {
// Complete transition
transitionContext.completeTransition(true)
}
}
animator.startAnimation()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
}





Пользовательские презентации — сложная часть UIKit. Вот что приходит на ум, никаких гарантий ;-)
Я бы посоветовал вам либо попытаться «зафиксировать» анимацию в представлении представления, поэтому в обратном вызове PresentationTransitionDidEnd(Bool) удалите преобразование и установите соответствующие ограничения для представления представления, которые соответствуют тому, что сделало преобразование. Или вы также можете просто анимировать изменения ограничения, чтобы имитировать преобразование.
Предположительно, вы получите обратный звонок viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator), чтобы управлять текущей презентацией, если произойдет ротация.
Справедливости ради, iOS 13 делает это очень хорошо, поэтому я уверен, что из этого есть более чистый выход. Возможно, они перемещают представление представления в промежуточное представление, которым они владеют, и масштабируют его, а затем возвращают обратно, когда закончат. Трудно сказать.
Хорошо, я понял это. Кажется, iOS 13 НЕ использует масштабное преобразование. В тот момент, когда вы это сделаете, как объяснялось, вращение устройства изменит рамку представления представления, и, поскольку вы уже применили transform к представлению, размер представления изменится неожиданным образом, и преобразование больше не будет действительным.
Решение состоит в том, чтобы вместо этого использовать перспективу по оси Z, которая даст вам точно такой же результат, но при этом выдержит повороты и т. д., поскольку все, что вы делаете, это перемещаете вид обратно в трехмерное пространство (ось Z), таким образом эффективно уменьшение масштаба. Вот преобразование, которое сделало это для меня (Swift):
func calculatePerspectiveTransform() -> CATransform3D {
let eyePosition:Float = 10.0;
var contentTransform:CATransform3D = CATransform3DIdentity
contentTransform.m34 = CGFloat(-1/eyePosition)
contentTransform = CATransform3DTranslate(contentTransform, 0, 0, -2)
return contentTransform
}
Вот статья, объясняющая, как это работает: https://whackylabs.com/uikit/2014/10/29/add-some-perspective-to-your-uiviews/
В вашем UIPresenterController вам также нужно будет сделать следующее, чтобы правильно обрабатывать это преобразование при поворотах:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// Reset transform before we rotate and then apply it again during rotation
if let presentingView = presentingViewController.view {
presentingView.layer.transform = CATransform3DIdentity
}
coordinator.animate(alongsideTransition: { [weak self] (context) in
if let presentingView = self?.presentingViewController.view {
presentingView.layer.transform = self?.calculatePerspectiveTransform() ?? CATransform3DIdentity
}
})
}
Да, это «хакерские» решения, которых я пытаюсь избежать. Никакие ограничения не могут имитировать красивое масштабирование, которое вы получаете с помощью преобразования
CALayer/View, так что это невозможно. Я также не хочу возиться с рамкой представления представления, потому что ведущий не владеет им и не должен предполагать, какую форму/размер/форму принимает представление представления.