以下是我们为实现动画转场时浮动截图(Swift 4)所做的内容:
背后的想法:
- 源视图控制器和目标视图控制器都遵循
InterViewAnimatable
协议。我们使用此协议来查找源视图和目标视图。
- 然后我们创建这些视图的快照并将其隐藏。相反,快照会在同一位置显示。
- 接下来我们开始对快照进行动画处理。
- 在转场结束时,我们取消隐藏目标视图。
结果看起来就像源视图正在飞向目标视图。
文件:InterViewAnimation.swift
protocol InterViewAnimatable {
var targetView: UIView { get }
}
class InterViewAnimation: NSObject {
var transitionDuration: TimeInterval = 0.25
var isPresenting: Bool = false
}
extension InterViewAnimation: UIViewControllerAnimatedTransitioning {
func transitionDuration(using: UIViewControllerContextTransitioning?) -> TimeInterval {
return transitionDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard
let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else {
transitionContext.completeTransition(false)
return
}
guard let fromTargetView = targetView(in: fromVC), let toTargetView = targetView(in: toVC) else {
transitionContext.completeTransition(false)
return
}
guard let fromImage = fromTargetView.caSnapshot(), let toImage = toTargetView.caSnapshot() else {
transitionContext.completeTransition(false)
return
}
let fromImageView = ImageView(image: fromImage)
fromImageView.clipsToBounds = true
let toImageView = ImageView(image: toImage)
toImageView.clipsToBounds = true
let startFrame = fromTargetView.frameIn(containerView)
let endFrame = toTargetView.frameIn(containerView)
fromImageView.frame = startFrame
toImageView.frame = startFrame
let cleanupClosure: () -> Void = {
fromTargetView.isHidden = false
toTargetView.isHidden = false
fromImageView.removeFromSuperview()
toImageView.removeFromSuperview()
}
let updateFrameClosure: () -> Void = {
toVC.view.setNeedsLayout()
toVC.view.layoutIfNeeded()
let updatedEndFrame = toTargetView.frameIn(containerView)
let correctedEndFrame = CGRect(origin: updatedEndFrame.origin, size: endFrame.size)
fromImageView.frame = correctedEndFrame
toImageView.frame = correctedEndFrame
}
let alimationBlock: (() -> Void)
let completionBlock: ((Bool) -> Void)
fromTargetView.isHidden = true
toTargetView.isHidden = true
if isPresenting {
guard let toView = transitionContext.view(forKey: .to) else {
transitionContext.completeTransition(false)
assertionFailure()
return
}
containerView.addSubviews(toView, toImageView, fromImageView)
toView.frame = CGRect(origin: .zero, size: containerView.bounds.size)
toView.alpha = 0
alimationBlock = {
toView.alpha = 1
fromImageView.alpha = 0
updateFrameClosure()
}
completionBlock = { _ in
let success = !transitionContext.transitionWasCancelled
if !success {
toView.removeFromSuperview()
}
cleanupClosure()
transitionContext.completeTransition(success)
}
} else {
guard let fromView = transitionContext.view(forKey: .from) else {
transitionContext.completeTransition(false)
assertionFailure()
return
}
containerView.addSubviews(toImageView, fromImageView)
alimationBlock = {
fromView.alpha = 0
fromImageView.alpha = 0
updateFrameClosure()
}
completionBlock = { _ in
let success = !transitionContext.transitionWasCancelled
if success {
fromView.removeFromSuperview()
}
cleanupClosure()
transitionContext.completeTransition(success)
}
}
if isPresenting {
UIView.animate(withDuration: transitionDuration, delay: 0, options: .curveEaseIn,
animations: alimationBlock, completion: completionBlock)
} else {
UIView.animate(withDuration: transitionDuration, delay: 0, options: .curveEaseOut,
animations: alimationBlock, completion: completionBlock)
}
}
}
extension InterViewAnimation {
private func targetView(in viewController: UIViewController) -> UIView? {
if let view = (viewController as? InterViewAnimatable)?.targetView {
return view
}
if let nc = viewController as? UINavigationController, let vc = nc.topViewController,
let view = (vc as? InterViewAnimatable)?.targetView {
return view
}
return nil
}
}
使用方法:
let sampleImage = UIImage(data: try! Data(contentsOf: URL(string: "https://placekitten.com/320/240")!))
class InterViewAnimationMasterViewController: StackViewController {
private lazy var topView = View().autolayoutView()
private lazy var middleView = View().autolayoutView()
private lazy var bottomView = View().autolayoutView()
private lazy var imageView = ImageView(image: sampleImage).autolayoutView()
private lazy var actionButton = Button().autolayoutView()
override func setupHandlers() {
actionButton.setTouchUpInsideHandler { [weak self] in
let vc = InterViewAnimationDetailsViewController()
let nc = UINavigationController(rootViewController: vc)
nc.modalPresentationStyle = .custom
nc.transitioningDelegate = self
vc.handleClose = { [weak self] in
self?.dismissAnimated()
}
nc.view.setNeedsLayout()
nc.view.layoutIfNeeded()
vc.view.setNeedsLayout()
vc.view.layoutIfNeeded()
self?.presentAnimated(nc)
}
}
override func setupUI() {
stackView.addArrangedSubviews(topView, middleView, bottomView)
stackView.distribution = .fillEqually
bottomView.addSubviews(imageView, actionButton)
topView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
middleView.backgroundColor = UIColor.green.withAlphaComponent(0.5)
bottomView.layoutMargins = UIEdgeInsets(horizontal: 15, vertical: 15)
bottomView.backgroundColor = UIColor.yellow.withAlphaComponent(0.5)
actionButton.title = "Tap to perform Transition!"
actionButton.contentEdgeInsets = UIEdgeInsets(dimension: 8)
actionButton.backgroundColor = .magenta
imageView.layer.borderWidth = 2
imageView.layer.borderColor = UIColor.magenta.withAlphaComponent(0.5).cgColor
}
override func setupLayout() {
var constraints = LayoutConstraint.PinInSuperView.atCenter(imageView)
constraints += [
LayoutConstraint.centerX(viewA: bottomView, viewB: actionButton),
LayoutConstraint.pinBottoms(viewA: bottomView, viewB: actionButton)
]
let size = sampleImage?.size.scale(by: 0.5) ?? CGSize()
constraints += LayoutConstraint.constrainSize(view: imageView, size: size)
NSLayoutConstraint.activate(constraints)
}
}
extension InterViewAnimationMasterViewController: InterViewAnimatable {
var targetView: UIView {
return imageView
}
}
extension InterViewAnimationMasterViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = InterViewAnimation()
animator.isPresenting = true
return animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = InterViewAnimation()
animator.isPresenting = false
return animator
}
}
class InterViewAnimationDetailsViewController: StackViewController {
var handleClose: (() -> Void)?
private lazy var imageView = ImageView(image: sampleImage).autolayoutView()
private lazy var bottomView = View().autolayoutView()
override func setupUI() {
stackView.addArrangedSubviews(imageView, bottomView)
stackView.distribution = .fillEqually
imageView.contentMode = .scaleAspectFit
imageView.layer.borderWidth = 2
imageView.layer.borderColor = UIColor.purple.withAlphaComponent(0.5).cgColor
bottomView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
navigationItem.leftBarButtonItem = BarButtonItem(barButtonSystemItem: .done) { [weak self] in
self?.handleClose?()
}
}
}
extension InterViewAnimationDetailsViewController: InterViewAnimatable {
var targetView: UIView {
return imageView
}
}
可重复使用的扩展:
extension UIView {
public func frameIn(_ view: UIView?) -> CGRect {
if let superview = superview {
return superview.convert(frame, to: view)
}
return frame
}
}
extension UIView {
public func caSnapshot(scale: CGFloat = 0, isOpaque: Bool = false) -> UIImage? {
var isSuccess = false
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, scale)
if let context = UIGraphicsGetCurrentContext() {
layer.render(in: context)
isSuccess = true
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return isSuccess ? image : nil
}
}
结果(gif动画):
这段内容展示了一个gif动画的结果。