文章目录
自定义Modally转场动画
准备
delegate转交给viewcontroller而不是系统
vc.transitioningDelegate = self
遵循协议
extension MainViewController:UIViewControllerTransitioningDelegate {
//非交互动画
//出现的函数,返回一个UIViewControllerAnimatedTransitioning的一个动画器,于是就新建一个
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PresentAnimator()
}
//退出的函数,同上返回一个动画器
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DismissAnimator()
}
//交互动画
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
//返回UIViewControllerInteractiveTransitioning的子类,完成交互动画
return panInteraction.isInteractive ? panInteraction : nil
}
}
两个非交互动画的动画器
创建动画器
遵循两个协议
完成两个固定方法,一个设置动画经过时间,一个设置动画逻辑
class PresentAnimator:NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {}
}
完成动画器
PresentAnimator:
class PresentAnimator:NSObject, UIViewControllerAnimatedTransitioning {
//动画的经过时间
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
//动画逻辑
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
//转场开始的vc
// let fromVC = transitionContext.viewController(forKey: .from),
//转场开始vc的根视图
let fromView = transitionContext.view(forKey: .from),
//转场结束的vc
// let toVC = transitionContext.viewController(forKey: .to),
//转场结束的vc的根视图
let toView = transitionContext.view(forKey: .to)
else{return}
//类似于NavigationView
let containerView = transitionContext.containerView
//加入toView,UIKit会自动加入fromView
containerView.addSubview(toView)
toView.alpha = 0
toView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fromView.alpha = 0
fromView.transform = CGAffineTransform(translationX: -containerView.frame.width, y: 0)
toView.alpha = 1
toView.transform = .identity
}) { _ in
fromView.transform = .identity
toView.transform = .identity
//UIKit调用这个方法来结束转场动画
transitionContext.completeTransition(true)
}
}
//tcontainerView在动画结束的时候会自动移除fromView只剩下toView,一旦toView被dismiss就会黑屏,但是fromView的transform属性还在,所以在最后要给他还原
}
DismissAnimator:
class DismissAnimator: NSObject,UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to)
else{return}
let containerView = transitionContext.containerView
containerView.addSubview(toView)
toView.alpha = 0
toView.transform = CGAffineTransform(translationX: -containerView.frame.width, y: 0)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
toView.alpha = 1
toView.transform = .identity
fromView.alpha = 0
fromView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
}) { _ in
fromView.transform = .identity
toView.transform = .identity
transitionContext.completeTransition(true)
}
}
}
在VC上设置dismiss
在ImageView上添加手势记得要加上
如果imageView是代码写上去的话添加
detailImageView.isUserInteractionEnabled = true
class DetailViewController: UIViewController {
@IBOutlet weak var detailImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
//target表示这个手势在哪里运行,actiont传入函数的固定写法
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(tap:)))
detailImageView.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
@objc func handleTap(tap:UITapGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
}
交互动画的动画器
创建动画器
是UIViewControllerInteractiveTransitioning的子类,用于完成交互动画
添加交互动画的flag
vc.transitioningDelegate = self
因为当执行玩这个操作的时候,会先去寻找交互动画,若交互动画返回值不是nil就一直执行交互动画而不执行非交互动画,所以要给交互动画设置一个flag(isInteractive)设定交互动画开关的条件
返回UIViewControllerInteractiveTransitioning的子类,完成交互动画
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return panInteraction.isInteractive ? panInteraction : nil
}
完成交互动画
class PanInteraction: UIPercentDrivenInteractiveTransition {
var isInteractive = false
var detailVC = DetailViewController()
init(detailVC: DetailViewController) {
self.detailVC = detailVC//1.先给本类的所有存储属性赋值
super.init()//2.再给所有父类的存储属性赋值(原本是系统默认给我们加的,但是是在最后面)
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
detailVC.view.addGestureRecognizer(pan)
}
@objc func handlePan(pan: UIPanGestureRecognizer) {
let progress = pan.translation(in: pan.view).x / 200
switch pan.state {
case .began:
isInteractive = true
detailVC.dismiss(animated: true, completion: nil)
case .changed:
update(progress)
case .ended, .cancelled:
isInteractive = false
if progress > 0.5 {
finish()
}else{
cancel()
}
default: break
}
}
}
在增加了交互动画之后,结束转场动画就可能在交互动画过程中取消
就得在两个非交互动画下面更改transitionContext.completeTransition
让UIKit调用这个方法来结束转场动画
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
自定义Navigation转场动画
准备
delegate转交给navigationController而不是系统
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
遵循协议,完成两个函数
extension MainViewController: UINavigationControllerDelegate{
//非交互动画
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
return PushAnimator()
case .pop:
return PopAnimator()
default:
return nil
}
}
//交互动画
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
//判断panInteraction是不是nil就是判断用户有没有到达detailVC-若到达才加pop交互动画
//用户pop完后,isInteractive变为false,这里仍旧返回nil,不影响下一次的push操作
guard let panInteraction = panInteraction ,panInteraction.isInteractive else{return nil}
return panInteraction
}
}
完成两个非交互动画的动画器
push动画器内容同modally
pop动画器模仿系统的有阴影,且toView保留1/3
class PopAnimate: NSObject,UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard
let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to)
else{return}
// containerView.addSubview(toView)
//把toView放在fromView的下面
containerView.insertSubview(toView, belowSubview: fromView)
toView.alpha = 1
toView.transform = CGAffineTransform(translationX: -containerView.frame.width / 3, y: 0)
fromView.layer.shadowOpacity = 0.5//即阴影的alpha
fromView.layer.shadowRadius = 10//阴影大小
//每次形成阴影前都会系统都会计算阴影的大小,非常消耗内存,添加shadowPath提前告知阴影的大小
//bounds相对于自己的位置和大小 frame是相对于复式图的位置和大小
fromView.layer.shadowPath = UIBezierPath(rect: fromView.bounds).cgPath
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fromView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
toView.transform = .identity
}) { _ in
toView.transform = .identity
fromView.transform = .identity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
完成交互动画的动画器
实例化动画器
因为push不会有交互动画,所以在push之后实例化动画器,这样也不会影响push非交互动画
class MainViewController: UIViewController {
var panInteraction: PanInteraction?
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let detailVC = segue.destination as? DetailViewController{
//panInteraction在第一次push转场前一直是nil,不影响push的非交互动画
//转场后才给panInteraction赋值
panInteraction = PanInteraction(detailVC: detailVC)
}
}
}
完成交互动画
基本同Modally
class PanInteraction: UIPercentDrivenInteractiveTransition {
let detailVC: DetailViewController
var isInteractive = false
init(detailVC: DetailViewController){
self.detailVC = detailVC
super.init()
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
detailVC.view.addGestureRecognizer(pan)
}
@objc func handlePan(pan: UIPanGestureRecognizer){
let progress = pan.translation(in: pan.view).x / 200
switch pan.state {
case .began:
isInteractive = true
detailVC.navigationController?.popViewController(animated: true)
case .changed:
update(progress)
case .cancelled , .ended:
isInteractive = false
if progress > 0.5{
finish()
}else{
cancel()
}
default:
break
}
}
}
自定义TabBarController转场动画
准备
添加一个UITabBarController
设置delegate
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// Do any additional setup after loading the view.
}
}
遵循协议
这里的非交互动画设置了一个enum用一个动画器完成两个动画
enum Operation {
case toRight, toLeft
}
extension TabBarController: UITabBarControllerDelegate {
//非交互动画
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let fromIndex = tabBarController.viewControllers!.firstIndex(of: fromVC)!
let toIndex = tabBarController.viewControllers!.firstIndex(of: toVC)!
let operation: Operation = toIndex > fromIndex ? .toRight : .toLeft
return CustomAnimator(operation: operation)
}
//交互动画
func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return customInteracton.isInteractive ? customInteracton : nil
}
}
完成非交互动画动画器
class CustomAnimator: NSObject, UIViewControllerAnimatedTransitioning{
let operation:Operation
var flag = 1
init(operation:Operation) {
self.operation = operation
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard
let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to)
else{return}
containerView.addSubview(toView)
if operation == .toRight {
flag = 1
}else{
flag = -1
}
toView.alpha = 0
toView.transform = CGAffineTransform(translationX: containerView.frame.width*CGFloat(flag), y: 0)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fromView.alpha = 0
fromView.transform = CGAffineTransform(translationX: -containerView.frame.width*CGFloat(self.flag), y: 0)
toView.alpha = 1
toView.transform = .identity
}) { _ in
toView.transform = .identity
fromView.transform = .identity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
完成交互动画动画器
实例化动画器
class TabBarController: UITabBarController {
var customInteracton:CustomInteraction!
override func viewDidLoad() {
super.viewDidLoad()
customInteracton = CustomInteraction(tabBarVC: self)
self.delegate = self
// Do any additional setup after loading the view.
}
}
完成动画器
通过改变tabBarVC.selectedIndex可以改变当前页面
左右滑动translationX正负不同,由此可以判断左右滑动
class CustomInteraction: UIPercentDrivenInteractiveTransition {
let tabBarVC: TabBarController
var isInteractive = false
init(tabBarVC:TabBarController) {
self.tabBarVC = tabBarVC
super.init()
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
tabBarVC.view.addGestureRecognizer(pan)
}
@objc func handlePan(pan: UIPanGestureRecognizer) {
let translationX = pan.translation(in: pan.view).x
//左右滑动translationX正负不同
let progress = abs(translationX) / 200
switch pan.state {
case .began:
isInteractive = true
if translationX < 0 {
if tabBarVC.selectedIndex < tabBarVC.viewControllers!.count - 1{
//通过改变tabBarVC.selectedIndex可以改变当前页面
tabBarVC.selectedIndex += 1
}
}else{
if tabBarVC.selectedIndex > 0 {
tabBarVC.selectedIndex -= 1
}
}
case .changed:
update(progress)
case.ended,.cancelled:
isInteractive = false
if progress > 0.5 {
finish()
}else{
cancel()
}
default:
break
}
}
}
第三方包
https://github.com/HeroTransitions/Hero