2

iOS 13 似乎使用了一种新UIPresentationController的呈现模式视图控制器,但它不依赖于为呈现视图控制器拍摄快照(就像大多数/所有库一样)。呈现视图控制器是“活动的”,并在模态视图控制器显示在透明/着色背景上方时继续显示动画/更改。

我可以通过CGAffineTransform在呈现视图控制器的视图上使用 a 轻松复制这一点(因为目的是为 iOS 10 / 11 / 12 等制作向后兼容的版本),但是在旋转设备时频繁地,呈现视图开始变形和增长似乎是因为系统更新了它frame,而有一个活动transform应用于它。

根据文档,frametransform应用到视图时未定义。鉴于系统似乎正在修改框架而不是我,我该如何解决这个问题而不会以我正在更新呈现视图的边界的 hacky 解决方案结束?我需要这个演示控制器保持通用,因为演示控制器可以是任何形状或形式,不一定是全屏视图。

这是我到目前为止所拥有的 - 它是一个简单的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)
    }
  }
}

和呈现动画控制器:

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
  }
}
4

2 回答 2

5

好吧,我想通了。似乎 iOS 13 不使用缩放变换。正如所解释的那样,当您这样做时,旋转设备将修改呈现视图的框架,并且由于您已经将 atransform应用于视图,因此视图将以意想不到的方式调整大小并且转换将不再有效。

解决方案是改用 z 轴透视图,这将为您提供完全相同的结果,但这样做会在旋转等情况下幸存,因为您所做的只是将视图移回 3D 空间(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
      }
    })
  }
于 2019-07-19T00:15:35.843 回答
0

自定义演示文稿是 UIKit 的一个棘手部分。这是我想到的,不保证;-)

我建议您尝试在呈现视图上“提交”动画 - 因此在 PresentationTransitionDidEnd(Bool) 回调中删除转换并在呈现视图上设置与转换所做的匹配的适当约束。或者您也可以只为约束更改设置动画以模拟变换。

如果发生轮换,您可能会收到viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)回电以管理正在进行的演示。

于 2019-07-16T02:19:17.060 回答