文章目錄
自定義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