动画示例(十四) —— 一种自定义视图控制器转场动画 (二)

190 阅读6分钟
原文链接: www.jianshu.com

版本记录

版本号 时间
V1.0 2019.06.07 星期五

前言

如果你细看了我前面写的有关动画的部分,就知道前面介绍了CoreAnimation、序列帧以及LOTAnimation等很多动画方式,接下来几篇我们就以动画示例为线索,进行动画的讲解。部分相关代码已经上传至GitHub - 刀客传奇。感兴趣的可以看我写的前面几篇。
1. 动画示例(一) —— 一种外扩的简单动画
2. 动画示例(二) —— 一种抖动的简单动画
3. 动画示例(三) —— 仿头条一种LOTAnimation动画
4. 动画示例(四) —— QuartzCore之CAEmitterLayer下雪❄️动画
5. 动画示例(五) —— QuartzCore之CAEmitterLayer烟花动画
6. 动画示例(六) —— QuartzCore之CAEmitterLayer、CAReplicatorLayer和CAGradientLayer简单动画
7. 动画示例(七) —— 基于CAShapeLayer图像加载过程的简单动画(一)
8. 动画示例(八) —— UIViewController间转场动画的实现 (一)
9. 动画示例(九) —— 一种复杂加载动画的实现 (一)
10. 动画示例(十) —— 一种弹性动画的实现 (一)
11. 动画示例(十一) —— 一种基于UIView的Spring弹性动画的实现 (一)
12. 动画示例(十二) —— 一种不规则形状注入动画的实现 (一)
13. 动画示例(十三) —— 一种自定义视图控制器转场动画 (一)

源码

写在前面:今天心情很差,因为前些天和女友分手了,她说我自私,我问了很多亲戚和朋友都说我不自私,我自己200的衣服舍不得买,给她可以买4000多块钱的,反而看不到我的真心和付出,我不知道这是因为什么?最后说从来没喜欢过我,我就是想知道为什么要这么坑我?男的就没长心就很容易吗?要让你这么伤害。确认关系后,再没见过就分开了,还说从来没喜欢过我,我现在人都是懵的,我不知道自己在做什么?以后对女人不会比对自己还好了,我长记性了!现在心里很抑郁,要是文章哪里写错了,大家多体谅吧!

不说那么多还是要继续努力的!

祝福大家端午节快乐吧!

1. Swift

首先看下工程组织结构

接着看下sb中的内容

下面就是代码中的内容了

1. HomeViewController.swift
import UIKit

// MARK: - UIViewController

class HomeViewController: UITableViewController {
  let transition = PopAnimator()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    transition.dismissCompletion = { [weak self] in
      guard
        let selectedIndexPathCell = self?.tableView.indexPathForSelectedRow,
        let selectedCell = self?.tableView.cellForRow(at: selectedIndexPathCell) as? RecipeTableViewCell
        else {
          return
      }
      
      selectedCell.shadowView.isHidden = false
    }
  }
  
  override var prefersStatusBarHidden: Bool {
    return true
  }
}

// MARK: - UITableViewDataSource & UITableViewDelegate

extension HomeViewController {
  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return Recipe.all().count
  }
  
  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(
      withIdentifier: "RecipeTableViewCell",
      for: indexPath
    ) as! RecipeTableViewCell
    
    cell.recipe = Recipe.all()[indexPath.row]
    
    return cell
  }
  
  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    performSegue(withIdentifier: "showDetails", sender: Recipe.all()[indexPath.row])
  }
}

// MARK: - Prepare for Segue

extension HomeViewController {
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if
      let detailsViewController = segue.destination as? DetailsViewController,
      let recipe = sender as? Recipe {
        detailsViewController.transitioningDelegate = self
        detailsViewController.recipe = recipe
    }
  }
}

// MARK: - UIViewControllerTransitioningDelegate

extension HomeViewController: UIViewControllerTransitioningDelegate {
  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    guard
      let selectedIndexPathCell = tableView.indexPathForSelectedRow,
      let selectedCell = tableView.cellForRow(at: selectedIndexPathCell) as? RecipeTableViewCell,
      let selectedCellSuperview = selectedCell.superview
      else {
        return nil
    }
    
    transition.originFrame = selectedCellSuperview.convert(selectedCell.frame, to: nil)
    transition.originFrame = CGRect(
      x: transition.originFrame.origin.x + 20,
      y: transition.originFrame.origin.y + 20,
      width: transition.originFrame.size.width - 40,
      height: transition.originFrame.size.height - 40
    )
    
    transition.presenting = true
    selectedCell.shadowView.isHidden = true
    
    return transition
  }
  
  func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    transition.presenting = false
    return transition
  }
}
2. DetailsViewController.swift
import UIKit

// MARK: - UIViewController

class DetailsViewController: UITableViewController {
  @IBOutlet var recipeImage: UIImageView!
  @IBOutlet var closeButton: UIButton!
  @IBOutlet var nameLabel: UILabel!
  @IBOutlet var descriptionLabel: UILabel!
  
  var recipe: Recipe?
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = UIScreen.main.bounds.height
    tableView.contentInset = UIEdgeInsets.zero
    
    NSLayoutConstraint.activate([
      recipeImage.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height)
    ])
  }
  
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    recipeImage.image = recipe?.image
    nameLabel.text = recipe?.name
    descriptionLabel.text = recipe?.description
  }
  
  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    UIView.animate(withDuration: 0.2) {
      self.closeButton.alpha = 0
    }
  }
  
  override var prefersStatusBarHidden: Bool {
    return true
  }
}

// MARK: - Actions

extension DetailsViewController {
  @IBAction func dismissAction() {
    dismiss(animated: true)
  }
}

// MARK: - UITableViewDelegate

extension DetailsViewController {
  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
  }
}
3. RecipeTableViewCell.swift
import UIKit

// MARK: - UITableViewCell

class RecipeTableViewCell: UITableViewCell {
  @IBOutlet var shadowView: UIView!
  @IBOutlet var containerView: UIView!
  @IBOutlet var recipeImageView: UIImageView!
  @IBOutlet var nameLabel: UILabel!
  
  var recipe: Recipe? {
    didSet {
      recipeImageView.image = recipe?.image
      nameLabel.text = recipe?.name
    }
  }
  
  override func awakeFromNib() {
    super.awakeFromNib()
    
    shadowView.layer.backgroundColor = UIColor.clear.cgColor
    shadowView.layer.shadowColor = UIColor.black.cgColor
    shadowView.layer.shadowOffset = CGSize(width: 0, height: 2.5)
    shadowView.layer.shadowOpacity = 0.2
    shadowView.layer.shadowRadius = 10
    
    containerView.backgroundColor = .white
    containerView.layer.cornerRadius = 15.0
    containerView.layer.masksToBounds = true
  }
}

// MARK: - Bounce Animation

extension RecipeTableViewCell {
  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    animate(isHighlighted: true)
  }
  
  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)
    animate(isHighlighted: false)
  }
  
  override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesCancelled(touches, with: event)
    animate(isHighlighted: false)
  }
  
  private func animate(isHighlighted: Bool, completion: ((Bool) -> Void)? = nil) {
    let animationOptions: UIView.AnimationOptions = [.allowUserInteraction]
    if isHighlighted {
      UIView.animate(
        withDuration: 0.5,
        delay: 0,
        usingSpringWithDamping: 1,
        initialSpringVelocity: 0,
        options: animationOptions, animations: {
          self.transform = .init(scaleX: 0.95, y: 0.95)
      }, completion: completion)
    } else {
      UIView.animate(
        withDuration: 0.5,
        delay: 0,
        usingSpringWithDamping: 1,
        initialSpringVelocity: 0,
        options: animationOptions, animations: {
          self.transform = .identity
      }, completion: completion)
    }
  }
}
4. Recipe.swift
import Foundation
import UIKit

struct Recipe {
  let name: String
  let description: String
  let image: UIImage

  static func all() -> [Recipe] {
    return [
      Recipe(name: "Pizza", description: """
        Pizza is a savory dish of Italian origin, consisting of a usually round, flattened base of leavened wheat-based dough topped with tomatoes, cheese, and various other ingredients (anchovies, olives, meat, etc.) baked at a high temperature, traditionally in a wood-fired oven. In formal settings, like a restaurant, pizza is eaten with knife and fork, but in casual settings it is cut into wedges to be eaten while held in the hand. Small pizzas are sometimes called pizzettas.

        The term pizza was first recorded in the 10th century in a Latin manuscript from the Southern Italian town of Gaeta in Lazio, on the border with Campania. Modern pizza was invented in Naples, and the dish and its variants have since become popular in many countries. It has become one of the most popular foods in the world and a common fast food item in Europe and North America, available at pizzerias (restaurants specializing in pizza), restaurants offering Mediterranean cuisine, and via pizza delivery. Many companies sell ready-baked frozen pizzas to be reheated in an ordinary home oven.

        The Associazione Verace Pizza Napoletana (lit. True Neapolitan Pizza Association) is a non-profit organization founded in 1984 with headquarters in Naples that aims to promote traditional Neapolitan pizza. In 2009, upon Italy's request, Neapolitan pizza was registered with the European Union as a Traditional Speciality Guaranteed dish, and in 2017 the art of its making was included on UNESCO's list of intangible cultural heritage.
        """, image: #imageLiteral(resourceName: "Pizza")),
      Recipe(name: "Spaghetti", description: """
        Spaghetti is a long, thin, solid, cylindrical pasta. Spaghettoni is a thicker form of spaghetti, while capellini is a very thin spaghetti. It is a staple food of traditional Italian cuisine. Like other pasta, spaghetti is made of milled wheat and water and sometimes enriched with vitamins and minerals. Authentic Italian spaghetti is made from durum wheat semolina, but elsewhere it may be made with other kinds of flour. Typically the pasta is white because refined flour is used, but whole wheat flour may be added.

        Originally, spaghetti was notably long, but shorter lengths gained in popularity during the latter half of the 20th century and now it is most commonly available in 25–30 cm (10–12 in) lengths. A variety of pasta dishes are based on it, and it is frequently served with tomato sauce or meat or vegetables.
        """, image: #imageLiteral(resourceName: "Spaghetti")),
      Recipe(name: "Meatballs", description: """
        A meatball is ground meat rolled into a small ball, sometimes along with other ingredients, such as bread crumbs, minced onion, eggs, butter, and seasoning. Meatballs are cooked by frying, baking, steaming, or braising in sauce. There are many types of meatballs using different types of meats and spices. The term is sometimes extended to meatless versions based on vegetables or fish; the latter are commonly known as fishballs.
        """, image: #imageLiteral(resourceName: "Meatballs")),
      Recipe(name: "Cookies", description: """
        A cookie is a baked or cooked food that is small, flat and sweet. It usually contains flour, sugar and some type of oil or fat. It may include other ingredients such as raisins, oats, chocolate chips, nuts, etc.

        In most English-speaking countries except for the United States and Canada, crisp cookies are called biscuits. Chewier biscuits are sometimes called cookies even in the United Kingdom. Some cookies may also be named by their shape, such as date squares or bars.

        Cookies or biscuits may be mass-produced in factories, made in small bakeries or homemade. Biscuit or cookie variants include sandwich biscuits, such as custard creams, Jammie Dodgers, Bourbons and Oreos, with marshmallow or jam filling and sometimes dipped in chocolate or another sweet coating. Cookies are often served with beverages such as milk, coffee or tea. Factory-made cookies are sold in grocery stores, convenience stores and vending machines. Fresh-baked cookies are sold at bakeries and coffeehouses, with the latter ranging from small business-sized establishments to multinational corporations such as Starbucks.
        """, image: #imageLiteral(resourceName: "Cookies"))
    ]
  }
}
5. PopAnimator.swift
import UIKit

// MARK: - UIViewControllerAnimatedTransitioning

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
  let duration = 0.8
  var presenting = true
  var originFrame = CGRect.zero
  
  var dismissCompletion: (() -> Void)?
  
  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return duration
  }
  
  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView
    let toView = transitionContext.view(forKey: .to)!
    let recipeView = presenting ? toView : transitionContext.view(forKey: .from)!
    
    let initialFrame = presenting ? originFrame : recipeView.frame
    let finalFrame = presenting ? recipeView.frame : originFrame
    
    let xScaleFactor = presenting ?
      initialFrame.width / finalFrame.width :
      finalFrame.width / initialFrame.width
    
    let yScaleFactor = presenting ?
      initialFrame.height / finalFrame.height :
      finalFrame.height / initialFrame.height
    
    let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
    
    if presenting {
      recipeView.transform = scaleTransform
      recipeView.center = CGPoint(
        x: initialFrame.midX,
        y: initialFrame.midY)
      recipeView.clipsToBounds = true
    }
    
    recipeView.layer.cornerRadius = presenting ? 20.0 : 0.0
    recipeView.layer.masksToBounds = true
    
    containerView.addSubview(toView)
    containerView.bringSubviewToFront(recipeView)
    
    UIView.animate(
      withDuration: duration,
      delay:0.0,
      usingSpringWithDamping: 0.5,
      initialSpringVelocity: 0.2,
      animations: {
        recipeView.transform = self.presenting ? .identity : scaleTransform
        recipeView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
        recipeView.layer.cornerRadius = !self.presenting ? 20.0 : 0.0
    }, completion: { _ in
      if !self.presenting {
        self.dismissCompletion?()
      }
      transitionContext.completeTransition(true)
    })
  }
  
  private func handleRadius(recipeView: UIView, hasRadius: Bool) {
    recipeView.layer.cornerRadius = hasRadius ? 20.0 : 0.0
  }
}

后记

本篇主要讲述了一种自定义视图控制器转场动画的实现,感兴趣的给个赞或者关注~~~

心很累,回家休息睡觉了,下周见~~