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

版本记录

版本号 时间
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. 动画示例(十三) —— 一种自定义视图控制器转场动画 (一)

源码

祝福大家端午节快乐吧!

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
  }
}

后记

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容