Swift CoreAnimation:CATransition 转场动画

Posted by Calvin on 2017-07-02

CATransition 同 CoreAnimation 核心动画中 CABasicAnimation 等相关类的使用方法类似。主要分为三个步骤:

(1)实例化 CATransition,并设置相应的转场动画 key。
(2)设置合适的转场动画属性,比如动画时间、过渡方向、动画保持状态等。
(3)将动画效果添加到对应视图的 Layer 图层中。

CATransition 转场动画效果合集

例如:基于 CATransition 的图片查看器

先初始化一张图片视图和按钮

var imageView:UIImageView?
var animBtn:UIButton?
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView()
imageView?.frame = CGRect(x: 0, y: 0, width: 300, height: 400)
imageView?.center = self.view.center
imageView?.image = UIImage(named: "1.jpg")
imageView?.contentMode = UIViewContentMode.scaleAspectFit
self.view.addSubview(imageView!)
animBtn = UIButton()
animBtn?.frame = CGRect(x: 20, y: 30, width: 80, height: 44)
animBtn?.backgroundColor = UIColor.red
animBtn?.setTitle("AnimBtn", for: UIControlState.normal)
animBtn?.setTitleColor(UIColor.white, for: UIControlState.normal)
animBtn?.addTarget(self, action: #selector(animBtnClick), for: UIControlEvents.touchUpInside)
self.view.addSubview(animBtn!)
}

实现按钮的点击事件和动画效果,一次使用一个效果即可

func animBtnClick() {
imageView?.image = UIImage(named: "2.jpg")
let animation:CATransition = CATransition()
animation.duration = 2;
animation.type = "oglFlip" // 翻转效果
animation.type = "cube" // 立方体效果
animation.type = "stuckEffect" // 收缩效果
animation.type = "rippleEffect" // 水滴波纹效果
animation.type = "pageUnCurl" // 向下翻页
animation.type = "pageCurl" // 向上翻页
animation.type = "cameraIrisHollowOpen" // 相机打开
animation.type = "cameraIrisHollowClose" // 相机关闭
animation.type = kCATransitionFade // 淡入淡出
animation.type = kCATransitionPush // 推送效果
animation.type = kCATransitionReveal // 揭开效果
animation.type = kCATransitionMoveIn // 移动效果
animation.subtype = kCATransitionFromRight
self.view.layer.add(animation, forKey: nil)
}

视图过渡动画

视图控制器过渡动画相关协议

(1)UINavigationControllerDelegate
(2)UIViewControllerAnimatedTransitioning

UINavigationControllerDelegate 是视图控制器使用的委托代理协议,该协议可以代理视图的以下功能:

(1)拦截导航栏视图控制器
(2)拦截视图控制器目标 ViewController 和源 ViewController。

在过渡动画中将使用 UINavigationControllerDelegate 的第二个功能,即拦截视图控制器目标 ViewController 和源 ViewController。该功能的回调方法如下所示:

func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?{
}

在该方法中 formVC 表明该视图控制器在跳转过程中来自哪个视图控制器。toVC 表明该视图控制器在跳转过程中最终跳转到何处去。所以只要拿到这两个视图控制器,在其上的 View 图层中添加想要实现的动画即可实现转场过渡动画效果。

实际上以上所有的动画操作都将借助于 UIViewControllerAnimatedTransitioning 协议,将所有的动画效果最终封装成一个实例对象返回给视图控制器 UIViewController-AnimatedTransitioning 协议,定义了视图过渡动画的执行周期和执行内容。

它有两个非常重要的回调方法。

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
func animateTransition(using transitionContext: UIViewControllerContextTransitioning?)

transitionDuration 方法返回转场动画执行周期。

animateTransition 方法用于构建转场动画内容。在该方法中可以通过 transitionContext 属性获取当前的 fromViewControllertoViewController。拿到这两个视图控制器后就可以设置当前视图控制器的各种动画效果。

视图控制器过渡动画实现

第一步:实现当前页面 ViewController.swift 内容

var viewcontroller:NewViewController = NewViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Main Viewcontroller";
self.view.backgroundColor = UIColor.blue
navigationController!.delegate = self
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?{
let transitionAnim:TransitionAnim = TransitionAnim()
return transitionAnim
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.navigationController?.pushViewController(viewcontroller, animated: false)
}

第二步:实现下一页面 NewViewController.swift 内容

override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
self.title = "New Viewcontroller"
}

第三步:实现视图控制器转场动画。这里实现一个从下到上的推出动画效果。TransitionAnim.swift 内容如下

class TransitionAnim: NSObject,UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval{
return 2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning){
let fromVC:UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toVC:UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let fromVCRect = transitionContext.initialFrame(for: fromVC)
let toVCRect = CGRect(x: 0,y: fromVCRect.size.height*2,width: fromVCRect.size.width,height: fromVCRect.size.height)
let fromView:UIView = fromVC.view
let toView:UIView = toVC.view
fromView.frame = fromVCRect
toView.frame = toVCRect
transitionContext.containerView.addSubview(fromView)
transitionContext.containerView.addSubview(toView)
UIView.animate(withDuration: 2, animations: { () in
toView.frame = fromVCRect;
toView.alpha = 1;
}, completion: { (Bool) in
transitionContext.completeTransition(true)
})
}
}

最终效果如下:

2017070214989864082214.gif

侧滑栏动画

参考网上的一些新闻类应用,发现在侧滑栏动画实现的同时还具有蒙版或者模糊效果,所以在这个案例中将动画分为2个部分,第一部分为蒙版模糊效果,第二部分为侧滑滑出以及收起效果。

第一部分:蒙版模糊效果

func blurimageFromImage(_ image:UIImage)->UIImage{
let blurRadix:UInt32 = 7
let img:CGImage = image.cgImage!
let inProvider:CGDataProvider = img.dataProvider!
let bitmapdata:CFData = inProvider.data!
var inputBuffer:vImage_Buffer = vImage_Buffer()
inputBuffer.data=(UnsafeMutableRawPointer)(mutating: CFDataGetBytePtr(bitmapdata))
inputBuffer.width=(vImagePixelCount)(img.width)
inputBuffer.height=(vImagePixelCount)(img.height)
inputBuffer.rowBytes=img.bytesPerRow;
let pixelBuffer:UnsafeMutableRawPointer = malloc(img.bytesPerRow * img.height);
var outputBuffer:vImage_Buffer = vImage_Buffer()
outputBuffer.data=pixelBuffer
outputBuffer.width=(vImagePixelCount)(img.width)
outputBuffer.height=(vImagePixelCount)(img.height)
outputBuffer.rowBytes=img.bytesPerRow;
vImageBoxConvolve_ARGB8888(&inputBuffer, &outputBuffer, nil, 0, 0, blurRadix, blurRadix, nil, UInt32(kvImageEdgeExtend))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let w:Int=(Int)(outputBuffer.width)
let h:Int=(Int)(outputBuffer.height)
let ctx:CGContext = CGContext(data: outputBuffer.data, width: w, height: h, bitsPerComponent: 8, bytesPerRow: outputBuffer.rowBytes, space: colorSpace, bitmapInfo: image.cgImage!.bitmapInfo.rawValue)!
let imageRef:CGImage = ctx.makeImage ()!
let imagenew:UIImage = UIImage(cgImage:imageRef)
free(pixelBuffer)
return imagenew;
}
func imageFromUIView(_ view:UIView)->UIImage{
UIGraphicsBeginImageContext(view.frame.size)
let content:CGContext = UIGraphicsGetCurrentContext()!
view.layer.render(in: content)
let imagenew:UIImage = UIGraphicsGetImageFromCurrentImageContext()!;
UIGraphicsEndImageContext();
return imagenew
}

第二部分:侧滑初始化

import Accelerate
let DEVICE_SCREEN_HEIGHT = UIScreen.main.bounds.height
let DEVICE_SCREEN_WIDTH = UIScreen.main.bounds.width
class SliderViewController: UIViewController {
internal var blurView:UIView?
internal var contentView:UIView?
override func viewDidLoad() {
super.viewDidLoad()
blurView = UIView(frame: CGRect(x: 0, y: 0, width: DEVICE_SCREEN_WIDTH, height: DEVICE_SCREEN_HEIGHT))
self.view.addSubview(blurView!)
contentView = UIView(frame: CGRect(x: -DEVICE_SCREEN_WIDTH*0.60, y: 0, width: DEVICE_SCREEN_WIDTH*0.60, height: DEVICE_SCREEN_HEIGHT))
contentView!.backgroundColor = UIColor(red: 255.0 / 255.0, green: 127.0 / 255.0, blue: 79.0 / 255.0, alpha: 1.0)
self.view.addSubview(contentView!)
}

侧滑栏收起效果

func sliderVCDismiss(){
UIView.animate(withDuration: 0.5, animations: {()->Void in
self.contentView!.frame = CGRect(x: -DEVICE_SCREEN_WIDTH*0.6, y: 0, width: DEVICE_SCREEN_WIDTH*0.6, height: DEVICE_SCREEN_HEIGHT)
self.contentView!.alpha = 0.9
}, completion: {(finished:Bool)->Void in
self.view.alpha=0.0
})
}

侧滑栏弹出动画

func sliderLeftViewAnimStart(){
var windowview:UIView = UIView()
windowview = UIApplication.shared.keyWindow!.rootViewController!.view
blurView?.layer.contents = blurimageFromImage(imageFromUIView(windowview)).cgImage
self.view.alpha=1.0
UIView.animate(withDuration: 0.5, animations: {()->Void in
self.contentView!.frame = CGRect(x: 0, y: 0, width: DEVICE_SCREEN_WIDTH*0.6, height: DEVICE_SCREEN_HEIGHT)
self.contentView!.alpha = 0.9
}, completion: {(finished:Bool)->Void in
})
}

在 ViewController 中进行调用

var pushSliderVC:Bool = true
var sliderVC:SliderViewController = SliderViewController()
override func viewDidLoad() {
super.viewDidLoad()
let imageView:UIImageView = UIImageView(frame:CGRect(x: 0, y: 0, width: DEVICE_SCREEN_WIDTH, height: DEVICE_SCREEN_HEIGHT))
imageView.image = UIImage(named: "login.png")
self.view.addSubview(imageView)
let loginButton:UIButton = UIButton(frame: CGRect(x: 20, y: 230, width: self.view.frame.width-20*2,height: 50))
loginButton.backgroundColor = UIColor(red: 50/255.0, green: 185/255.0, blue: 170/255.0, alpha: 1.0)
loginButton.setTitle("登陆", for: UIControlState())
self.view.addSubview(loginButton)
sliderVC.view.frame = CGRect(x: 0, y: 0, width: DEVICE_SCREEN_WIDTH, height: DEVICE_SCREEN_HEIGHT)
sliderVC.view.isHidden=true
self.view.addSubview(sliderVC.view)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if pushSliderVC {
sliderVC.view.isHidden = false
sliderVC.sliderLeftViewAnimStart()
}else{
sliderVC.view.isHidden = true
sliderVC.sliderVCDismiss()
}
pushSliderVC = !pushSliderVC
}

最终效果如下:

20170702149898782468814.gif

全文代码:点击查看