自定义 UIViewController 动画问题
Custom UIViewController animation issue
我使用 UIViewControllerAnimatedTransitioning
为 UIViewController
创建了一个简单的自定义过渡动画。一切正常,但是当目标视图出现时,内容从右下角开始出现,当视图消失时,内容从左上角消失。我附上了 gif 示例。这是我的代码:
CustomViewAnimator.swift
class CustomViewAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private let interval = 0.5
var forward = false
var originFrame: CGRect = .zero
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return interval
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: .to) else {return}
guard let fromView = transitionContext.view(forKey: .from) else {return}
let destinationView = forward ? toView : fromView
destinationView.alpha = forward ? 0 : 1
forward ? containerView.addSubview(toView) : containerView.insertSubview(toView, belowSubview: fromView)
forward ? (destinationView.frame = originFrame) : (destinationView.frame = fromView.frame)
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.frame = fromView.frame) : (destinationView.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
MainViewController.self
class MainViewController: UIViewController {
private var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
@objc private func openView() {
let viewController = TargetViewController()
viewController.transitioningDelegate = self
navigationController?.pushViewController(viewController, animated: true)
}
}
extension MainViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.originFrame = button.frame
animator.forward = (operation == .push)
return animator
}
}
extension MainViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.forward = true
return animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.forward = false
return animator
}
}
TargetViewController.swift
class TargetViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
}
extension TargetViewController {
fileprivate func setup() {
view.backgroundColor = .orange
navigationItem.title = "Target view"
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello from target view :)"
label.textColor = .white
label.font = UIFont.boldSystemFont(ofSize: 18)
return label
}()
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
谁能告诉我如何解决这个问题,使 TargetViewController 中的标签(或任何其他内容)保持在原位?
基本上你的问题是因为混合了自动布局和框架。您将子视图(按钮和标签)定位在 ViewController
的视图中,但对于动画,您使用帧更新。因此,据我正确理解布局是如何工作的,您的标签约束在过渡期间未激活,或者至少布局引擎未使用它来计算标签的位置。
因此,最好的建议是避免过渡中的帧动画,并尝试为 .from
和 .to
视图和动画约束常量创建适当的约束。这将需要更多代码,但您可以确定您不会混合使用两种不同的布局策略。
更简单的解决方案是通过简单地将 containerView.layoutIfNeeded()
添加到动画块来告诉 containerView
计算约束并在动画期间适当定位元素。所以它会是:
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.frame = fromView.frame) : (destinationView.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
containerView.layoutIfNeeded()
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
但这只能使事情正常进行,并不能解决同时使用两种布局策略的主要问题。
我同意其他答案的讨论。我的建议是不要为 UIView 的框架设置动画,您可以为目标视图的蒙版设置动画,如下所示。应该是歌剧真正想要的效果。
加粗部分是与原文的区别post。
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: .to) else {return}
guard let fromView = transitionContext.view(forKey: .from) else {return}
let destinationView = forward ? toView : fromView
destinationView.alpha = forward ? 0 : 1
让 maskView = UIView.init(frame: forward ? originFrame : fromView.frame)
maskView.backgroundColor = UIColor.white
destinationView.mask = maskView
forward ? containerView.addSubview(toView) : containerView.insertSubview(toView, belowSubview: fromView)
forward ? (destinationView.frame = toView.frame) : (destinationView.frame = fromView.frame)
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.mask?.frame = fromView.frame) : (destinationView.mask?.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
我使用 UIViewControllerAnimatedTransitioning
为 UIViewController
创建了一个简单的自定义过渡动画。一切正常,但是当目标视图出现时,内容从右下角开始出现,当视图消失时,内容从左上角消失。我附上了 gif 示例。这是我的代码:
CustomViewAnimator.swift
class CustomViewAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private let interval = 0.5
var forward = false
var originFrame: CGRect = .zero
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return interval
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: .to) else {return}
guard let fromView = transitionContext.view(forKey: .from) else {return}
let destinationView = forward ? toView : fromView
destinationView.alpha = forward ? 0 : 1
forward ? containerView.addSubview(toView) : containerView.insertSubview(toView, belowSubview: fromView)
forward ? (destinationView.frame = originFrame) : (destinationView.frame = fromView.frame)
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.frame = fromView.frame) : (destinationView.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
MainViewController.self
class MainViewController: UIViewController {
private var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
@objc private func openView() {
let viewController = TargetViewController()
viewController.transitioningDelegate = self
navigationController?.pushViewController(viewController, animated: true)
}
}
extension MainViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.originFrame = button.frame
animator.forward = (operation == .push)
return animator
}
}
extension MainViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.forward = true
return animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = CustomViewAnimator()
animator.forward = false
return animator
}
}
TargetViewController.swift
class TargetViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
}
extension TargetViewController {
fileprivate func setup() {
view.backgroundColor = .orange
navigationItem.title = "Target view"
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello from target view :)"
label.textColor = .white
label.font = UIFont.boldSystemFont(ofSize: 18)
return label
}()
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
谁能告诉我如何解决这个问题,使 TargetViewController 中的标签(或任何其他内容)保持在原位?
基本上你的问题是因为混合了自动布局和框架。您将子视图(按钮和标签)定位在 ViewController
的视图中,但对于动画,您使用帧更新。因此,据我正确理解布局是如何工作的,您的标签约束在过渡期间未激活,或者至少布局引擎未使用它来计算标签的位置。
因此,最好的建议是避免过渡中的帧动画,并尝试为 .from
和 .to
视图和动画约束常量创建适当的约束。这将需要更多代码,但您可以确定您不会混合使用两种不同的布局策略。
更简单的解决方案是通过简单地将 containerView.layoutIfNeeded()
添加到动画块来告诉 containerView
计算约束并在动画期间适当定位元素。所以它会是:
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.frame = fromView.frame) : (destinationView.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
containerView.layoutIfNeeded()
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
但这只能使事情正常进行,并不能解决同时使用两种布局策略的主要问题。
我同意其他答案的讨论。我的建议是不要为 UIView 的框架设置动画,您可以为目标视图的蒙版设置动画,如下所示。应该是歌剧真正想要的效果。
加粗部分是与原文的区别post。
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: .to) else {return}
guard let fromView = transitionContext.view(forKey: .from) else {return}
let destinationView = forward ? toView : fromView
destinationView.alpha = forward ? 0 : 1
让 maskView = UIView.init(frame: forward ? originFrame : fromView.frame)
maskView.backgroundColor = UIColor.white
destinationView.mask = maskView
forward ? containerView.addSubview(toView) : containerView.insertSubview(toView, belowSubview: fromView)
forward ? (destinationView.frame = toView.frame) : (destinationView.frame = fromView.frame)
UIView.animate(withDuration: interval, animations: {
self.forward ? (destinationView.mask?.frame = fromView.frame) : (destinationView.mask?.frame = self.originFrame)
destinationView.alpha = self.forward ? 1 : 0
}) { (finished) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}