This is the 12th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Here’s what you want to achieve:

Add the required parts first:

class ViewController: UIViewController { let screenWidth = UIScreen.main.bounds.size.width let screenHeight = UIScreen.main.bounds.size.height  let backgroundImageView = UIImageView() let summaryIcon = UIImageView() let summaryLabel = UILabel() let flightLabel = UILabel() let gateLabel = UILabel() let flightInfoLabel = UILabel() let gateInfoLabel = UILabel() let departingLabel = UILabel() let arrivingLabel = UILabel() let planeImageView = UIImageView() let flightStatusLabel = UILabel() let statusBanner = UIImageView() override func viewDidLoad() { super.viewDidLoad() let blackView = UIView() blackView.backgroundColor = .black blackView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: 80) // Do any additional setup after loading the view. view.addSubview(backgroundImageView) view.addSubview(blackView) view.addSubview(summaryIcon) view.addSubview(summaryLabel) view.addSubview(flightLabel) view.addSubview(gateLabel) view.addSubview(flightInfoLabel) view.addSubview(gateInfoLabel) view.addSubview(departingLabel) view.addSubview(planeImageView) view.addSubview(arrivingLabel) view.addSubview(statusBanner) view.addSubview(flightStatusLabel) backgroundImageView.image = UIImage(named: "bg-snowy") backgroundImageView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight) summaryIcon.image = UIImage(named: "icon-blue-arrow") summaryIcon.frame = CGRect(x: 70, y: 44, width: 18, height: 18) summaryLabel.font = UIFont.systemFont(ofSize: 18) summaryLabel.frame = CGRect(x: 0, y: 44, width: screenWidth, height: 20) summaryLabel.textColor = .white summaryLabel.textAlignment = .center flightLabel.text = "Flight" flightLabel.font = UIFont.systemFont(ofSize: 18) flightLabel.frame = CGRect(x: 42, y: 91, width: screenWidth, height: 25) flightLabel. TextColor = UIColor. White. WithAlphaComponent gateLabel (0.8). The text = "Gate" gateLabel. The font = UIFont.systemFont(ofSize: 18) gateLabel.frame = CGRect(x: screenWidth - 75, y: 91, width: screenWidth, height: 25) gateLabel. TextColor = UIColor. White. WithAlphaComponent flightInfoLabel. (0.8). The font = UIFont systemFont (ofSize: 24) flightInfoLabel.frame = CGRect(x: 23, y: 137, width: screenWidth, height: 25) flightInfoLabel.textColor = .white gateInfoLabel.font = UIFont.systemFont(ofSize: 24) gateInfoLabel.frame = CGRect(x: screenWidth - 100, y: 137, width: screenWidth, height: 25) gateInfoLabel.textColor = .white departingLabel.font = UIFont.systemFont(ofSize: 30) departingLabel.frame = CGRect(x: 23, y: 341, width: screenWidth, height: 25) departingLabel.textColor = .systemYellow arrivingLabel.font = UIFont.systemFont(ofSize: 30) arrivingLabel.frame = CGRect(x: screenWidth - 100, y: 341, width: screenWidth, height: 25) arrivingLabel.textColor = .systemYellow planeImageView.image = UIImage(named: "plane") planeImageView.frame = CGRect(x: view.center.x - 50, y: 341, width: 88, height: 35) statusBanner.image = UIImage(named: "banner") statusBanner.frame = CGRect(x: view.center.x - 95, y: 526, width: 191, height: 50) flightStatusLabel.font = UIFont.systemFont(ofSize: 30) flightStatusLabel.frame = CGRect(x: view.center.x - 95, y: 526, width: screenWidth, height: 50) flightStatusLabel.center = statusBanner.center flightStatusLabel.textAlignment = .center flightStatusLabel.textColor  = .brown } }Copy the code

Then create a FlightData and create two models for later assignment of labels and so on.

struct FlightData {
  let summary: String
  let flightNr: String
  let gateNr: String
  let departingFrom: String
  let arrivingTo: String
  let weatherImageName: String
  let showWeatherEffects: Bool
  let isTakingOff: Bool
  let flightStatus: String
}

//
// Pre- defined flights
//

let londonToParis = FlightData(
  summary: "01 Apr 2015 09:42",
  flightNr: "ZY 2014",
  gateNr: "T1 A33",
  departingFrom: "LGW",
  arrivingTo: "CDG",
  weatherImageName: "bg-snowy",
  showWeatherEffects: true,
  isTakingOff: true,
  flightStatus: "Boarding")

let parisToRome = FlightData(
  summary: "01 Apr 2015 17:05",
  flightNr: "AE 1107",
  gateNr: "045",
  departingFrom: "CDG",
  arrivingTo: "FCO",
  weatherImageName: "bg-sunny",
  showWeatherEffects: false,
  isTakingOff: false,
  flightStatus: "Delayed")
Copy the code

Create a changeFlight method that assigns a value to the label

   func changeFlight(to data: FlightData, animated: Bool = false) {
        summaryLabel.text = data.summary
        backgroundImageView.image =  UIImage(named: data.weatherImageName)
        flightInfoLabel.text = data.flightNr
        gateInfoLabel.text = data.gateNr
        departingLabel.text = data.departingFrom
        arrivingLabel.text = data.arrivingTo
        flightStatusLabel.text = data.flightStatus
    }
Copy the code

When run like this, you get:Next up is the animation. Transition is used to add animations to view transitions, such as page-turning effects. Add a fade method for background transitions and so on.

func fade(imageView: UIImageView, toImage: UIImage, showEffects: Bool) { UIView.transition(with: imageView, duration: 1.0, the options: transitionCrossDissolve, animations: {imageView. Image = toImage}, completion: nil)}Copy the code

Add a judgment to changeFlight, and call the Delay method here to make the transition every three seconds. This method in the first of the article, is actually DispatchQueue. Main. AsyncAfter encapsulation.

func changeFlight(to data: FlightData, animated: Bool = false) { summaryLabel.text = data.summary if animated { fade(imageView: backgroundImageView, toImage: UIImage(named: data.weatherImageName)! , showEffects: data.showWeatherEffects) } else { backgroundImageView.image = UIImage(named: data.weatherImageName) flightInfoLabel.text = data.flightNr gateInfoLabel.text = data.gateNr departingLabel.text = data.departingFrom arrivingLabel.text = data.arrivingTo flightStatusLabel.text = data.flightStatus } } delay(seconds: 3.0) {self. ChangeFlight (to: data.istakingoff? ParisToRome: londonToParis, Animated: true)}Copy the code

In this way, the background image and other switches are done. The next step is to animate the flight number, boarding number and flight status. Create a cubeTransition method to animate, and add the following code after the fade method. The direction is used to determine whether the animation is up or down, and then the cubeTransition method is called.

let direction: AnimationDirection = data.isTakingOff ? .positive : .negative

cubeTransition(label: flightInfoLabel, text: data.flightNr, direction: direction)
cubeTransition(label: gateInfoLabel, text: data.gateNr, direction: direction)
cubeTransition(label: flightStatus, text: data.flightStatus,  direction: direction)
Copy the code

In the cubeTransition method, you create a temporary label, set its text to the value passed in, change its y size by transform, animate it from small to large, and animate the incoming label from large to small. After the animation ends, set the text of the label to the value passed in, and then restore the size to remove the temporary label.

func cubeTransition(label: UILabel, text: String, direction: AnimationDirection) { let auxLabel = UILabel(frame: label.frame) auxLabel.text = text auxLabel.font = label.font auxLabel.textAlignment = label.textAlignment auxLabel.textColor = label.textColor auxLabel.backgroundColor = label.backgroundColor let auxLabelOffset = CGFloat (direction. RawValue) * label. Frame. The size, height / 2.0 auxLabel. Transform = CGAffineTransform (translationX: 0.0, y: Superview?. AddSubview (auxLabel) UIView. Animate (withDuration: 0.5, delay: 0.0, options:. CurveEaseOut, animations: {auxlabel. transform =.identity label.transform = CGAffineTransform(translationX: 0.0, y: ScaledBy (x: 1.0, y: 0.1)}, completion: { _ in label.text = auxLabel.text label.transform = .identity auxLabel.removeFromSuperview() }) }Copy the code

Then animate the departure and arrival labels for flights. I’m going to create a moveLabel method, which is very similar to the cubeTransition method, but I’m just doing a position shift instead of a size shift.

func moveLabel(label: UILabel, text: String, offset: CGPoint) { let auxLabel = UILabel(frame: label.frame) auxLabel.text = text auxLabel.font = label.font auxLabel.textAlignment = label.textAlignment auxLabel.textColor = label.textColor auxLabel.backgroundColor = .clear auxLabel.transform = CGAffineTransform(translationX: offset.x, y: Offset. Y) auxLabel. Alpha = 0 view.addSubView (auxLabel) UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: { label.transform = CGAffineTransform(translationX: offset.x, y: Offset. Y) label.alpha = 0.0}, completion: nil) uiView.animate (withDuration: 0.25, delay: 0.1, options: .curveeasein, animations: {auxlabel. transform =. Identity auxlabel. alpha = 1.0}, completion: {_ in / / clean up auxLabel. RemoveFromSuperview () label. The text = the text label. The alpha = 1.0 label. The transform =. Identity})}Copy the code

Then add an animation of the plane. Create a planeDepart method, which uses the animateKeyframes method to move right and up by making x larger and y smaller, and then transform the Angle of the image by CGAffineTransform(rotationAngle: -.pi / 8). When you’re done, reset it to enter from the left side of the screen and return to the original position.

Func planeDepart() {let originalCenter = planeimageview.center uiView.animateKeyframes (withDuration: 1.5, delay: Relativestarttime: 0.0, relativeDuration: 0.25, animations: {// Add keyframes uiView. addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {self. PlaneImageView. Center. X + = 80.0 self. PlaneImageView. Center. - y = 10.0}) UIView. AddKeyframe (withRelativeStartTime: 0.1, relativeDuration: 0.4) {self. PlaneImageView. Transform = CGAffineTransform (rotationAngle: -.pi / 8)} uiView. addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25) {self. PlaneImageView. Center. X + = 100.0 self. PlaneImageView. Center. The y - = 50.0 self. PlaneImageView. Alpha = 0.0} UIView. AddKeyframe (withRelativeStartTime: 0.51, relativeDuration: 0.01) {self. PlaneImageView. Transform =. Identity self. PlaneImageView. Center = CGPoint (0.0 x: y: Originalcenter.y)} uiView. addKeyframe(withRelativeStartTime: 0.55, relativeDuration: 0.45) {self. PlaneImageView. Alpha = 1.0 self. PlaneImageView. Center = originalCenter}}, completion: nil)}Copy the code

Finally, add the summary animation. Simply use uiView.animateKeyFrames to add an upward and downward animation.

Func summarySwitch(to summaryText: String) {uiView. animateKeyframes(withDuration: 1.0, Delay: 0.0, animations: {UIView. AddKeyframe (withRelativeStartTime: 0.0, relativeDuration: 0.45) {self. SummaryLabel. Center. - y = 100.0} UIView. AddKeyframe (withRelativeStartTime: 0.5, relativeDuration: 0.45) {self. SummaryLabel. Center. + y = 100.0}}, completion: nil) delay (seconds: 5) {self. summarylabel. text = summaryText}}Copy the code

There’s only one more animation left, the snow effect. Snow effects need to use CAEmitterLayer, CAEmitterLayer is a particle engine provided by QuartzCore, which can be used to create beautiful particle effects. code

import QuartzCore class SnowView: UIView { override init(frame: CGRect) { super.init(frame: frame) let emitter = layer as! CAEmitterLayer emitter.emitterPosition = CGPoint(x: bounds.size.width / 2, y: 0) emitter.emitterSize = bounds.size emitter.emitterShape = .rectangle let emitterCell = CAEmitterCell() emitterCell.contents = UIImage(named: "flake.png")! BirthRate = 200 EmitterCell. lifetime = 3.5 emittercell. color = uicolor.white.cgcolor Emittercell. redRange = 0.0 emitterCell.blueRange = 0.1 emitterCell.greenRange = 0.0 emitterCell.velocity = 10 emitterCell.velocityRange = 350 emitterCell.emissionRange = CGFloat(Double.pi/2) emitterCell.emissionLongitude = CGFloat (- Double. PI) emitterCell. YAcceleration = 70 emitterCell xAcceleration = 0 emitterCell. Scale = 0.33 Emittercell. scaleRange = 1.25 EmitterCell. scaleSpeed = -0.25 EmitterCell. alphaRange = 0.5 emitterCell  emitter.emitterCells = [emitterCell] } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override class var layerClass: AnyClass { return CAEmitterLayer.self } }Copy the code

And you have a particle emitter. And then you want to add it to the view. Declare a snowView

  var snowView = SnowView(frame: CGRect(x: -150, y:-100, width: 300, height: 50))
Copy the code

Add a snowClipView to viewDidLoad as the parent view of snowView.

  let snowClipView = UIView(frame: view.frame.offsetBy(dx: 0, dy: 50))
    snowClipView.clipsToBounds = true
    snowClipView.addSubview(snowView)
    view.addSubview(snowClipView)
Copy the code

Add to else in changeFlight

snowView.isHidden = ! data.showWeatherEffectsCopy the code

Add to the fade method

Uiview.animate (withDuration: 1.0, delay: 0.0, options:.curveeaseout, animations: {self.snowview.alpha = showEffects? 1.0:0.0}, completion: nil)Copy the code

And that completes the animation.

CAEmitterLayer parameters function:

  • EmitterPosition: The center of the emission shape. The default is (0,0,0).
  • EmitterSize: Size of the emission shape. The default is (0,0,0), and some values may be ignored depending on the emission shape property.
  • EmitterShape: Emission shape types, including: Point (default), Line, Rectangle, Circle,cuboid, and Sphere.

Functions of CAEmitterCell parameters:

  • Contents: Cell contents, usually CGImageRef, default to nil.
  • BirthRate: Number of launch objects created per second. The default value is 0.
  • Lifetime: Lifetime, in seconds, of each emitted object, specified as the mean value and the range of the mean value (lifetimeRange). Both values default to zero.
  • Color: Average color of each emitted object. Color defaults to opaque white.
  • RedRange, blueRange, greenRange: range from the average color. Default is (0, 0, 0).
  • Velocity: The initial average velocity of each emitted object. The default is 0
  • VelocityRange: The initial average speed range of each launched object. Default is 0.
  • EmissionRange: Defines the Angle (in radians) of the cone around which the emitted objects are evenly distributed.
  • EmissionLongitude: The Angle in the XY plane with the x axis, often called azimuth or θ, which defaults to zero.
  • YAcceleration, xAcceleration: Acceleration vector applied to the emitted object, default to zero.
  • Scale: The scale factor applied to each emitted object, defined as the average value, which defaults to 1.
  • ScaleRange: Scale factor applied to each emitted object, average range, default to 0.
  • ScaleSpeed: Scale to original size per second.
  • AlphaRange: Range of alpha.
  • AlphaSpeed: Changes the value of the original alpha every second.
  • EmitterCells: Array of emitters

The complete code

import UIKit
import QuartzCore

// A delay function
func delay(seconds: Double, completion: @escaping ()-> Void) {
  DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: completion)
}

class ViewController: UIViewController {

  enum AnimationDirection: Int {
    case positive = 1
    case negative = -1
  }

  @IBOutlet var bgImageView: UIImageView!
  
  @IBOutlet var summaryIcon: UIImageView!
  @IBOutlet var summary: UILabel!
  
  @IBOutlet var flightNr: UILabel!
  @IBOutlet var gateNr: UILabel!
  @IBOutlet var departingFrom: UILabel!
  @IBOutlet var arrivingTo: UILabel!
  @IBOutlet var planeImage: UIImageView!
  
  @IBOutlet var flightStatus: UILabel!
  @IBOutlet var statusBanner: UIImageView!
  
  var snowView = SnowView(frame: CGRect(x: -150, y:-100, width: 300, height: 50))
  
  //MARK: view controller methods
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    //adjust ui
    summary.addSubview(summaryIcon)
    summaryIcon.center.y = summary.frame.size.height/2
    
    //add the snow effect layer
    
    let snowClipView = UIView(frame: view.frame.offsetBy(dx: 0, dy: 50))
    snowClipView.clipsToBounds = true
    snowClipView.addSubview(snowView)
    view.addSubview(snowClipView)
    
    //start rotating the flights
    changeFlight(to: londonToParis)
  }
  
  //MARK: custom methods
  
  func changeFlight(to data: FlightData, animated: Bool = false) {
    
    // populate the UI with the next flight's data
    if animated {
      fade(imageView: bgImageView,
           toImage: UIImage(named: data.weatherImageName)!,
           showEffects: data.showWeatherEffects)

      let direction: AnimationDirection = data.isTakingOff ? .positive : .negative

      cubeTransition(label: flightNr, text: data.flightNr, direction: direction)
      cubeTransition(label: gateNr, text: data.gateNr, direction: direction)

      let offsetDeparting = CGPoint(x: CGFloat(direction.rawValue * 80), y: 0.0)
      moveLabel(label: departingFrom, text: data.departingFrom, offset: offsetDeparting)

      let offsetArriving = CGPoint(x: 0.0, y: CGFloat(direction.rawValue * 50))
      moveLabel(label: arrivingTo, text: data.arrivingTo, offset: offsetArriving)

      cubeTransition(label: flightStatus, text: data.flightStatus,  direction: direction)

      planeDepart()
      summarySwitch(to: data.summary)

    } else {
      bgImageView.image = UIImage(named: data.weatherImageName)
      snowView.isHidden = !data.showWeatherEffects

      flightNr.text = data.flightNr
      gateNr.text = data.gateNr
      departingFrom.text = data.departingFrom
      arrivingTo.text = data.arrivingTo
      flightStatus.text = data.flightStatus

      summary.text = data.summary
    }
    
    // schedule next flight
    delay(seconds: 3.0) {
      self.changeFlight(to: data.isTakingOff ? parisToRome : londonToParis, animated: true)
    }
  }

  func fade(imageView: UIImageView, toImage: UIImage, showEffects: Bool) {
    UIView.transition(with: imageView, duration: 1.0, options: .transitionCrossDissolve, animations: {
      imageView.image = toImage
    }, completion: nil)

    UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseOut, animations: {
      self.snowView.alpha = showEffects ? 1.0 : 0.0
    }, completion: nil)
  }

  func cubeTransition(label: UILabel, text: String, direction: AnimationDirection) {
    let auxLabel = UILabel(frame: label.frame)
    auxLabel.text = text
    auxLabel.font = label.font
    auxLabel.textAlignment = label.textAlignment
    auxLabel.textColor = label.textColor
    auxLabel.backgroundColor = label.backgroundColor

    let auxLabelOffset = CGFloat(direction.rawValue) * label.frame.size.height/2.0
    auxLabel.transform = CGAffineTransform(translationX: 0.0, y: auxLabelOffset)
      .scaledBy(x: 1.0, y: 0.1)

    label.superview?.addSubview(auxLabel)

    UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseOut, animations: {
      auxLabel.transform = .identity
      label.transform = CGAffineTransform(translationX: 0.0, y: -auxLabelOffset)
        .scaledBy(x: 1.0, y: 0.1)
    }, completion: { _ in
      label.text = auxLabel.text
      label.transform = .identity

      auxLabel.removeFromSuperview()
    })
  }

  func moveLabel(label: UILabel, text: String, offset: CGPoint) {
    let auxLabel = UILabel(frame: label.frame)
    auxLabel.text = text
    auxLabel.font = label.font
    auxLabel.textAlignment = label.textAlignment
    auxLabel.textColor = label.textColor
    auxLabel.backgroundColor = UIColor.clear

    auxLabel.transform = CGAffineTransform(translationX: offset.x, y: offset.y)
    auxLabel.alpha = 0
    view.addSubview(auxLabel)

    UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: {
      label.transform = CGAffineTransform(translationX: offset.x, y: offset.y)
      label.alpha = 0.0
    }, completion: nil)

    UIView.animate(withDuration: 0.25, delay: 0.1, options: .curveEaseIn, animations: {
      auxLabel.transform = .identity
      auxLabel.alpha = 1.0
    }, completion: {_ in
      //clean up
      auxLabel.removeFromSuperview()
      label.text = text
      label.alpha = 1.0
      label.transform = .identity
    })
  }

  func planeDepart() {
    let originalCenter = planeImage.center

    UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, animations: {
      //add keyframes
      UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {
        self.planeImage.center.x += 80.0
        self.planeImage.center.y -= 10.0
      })

      UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.4) {
        self.planeImage.transform = CGAffineTransform(rotationAngle: -.pi / 8)
      }

      UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25) {
        self.planeImage.center.x += 100.0
        self.planeImage.center.y -= 50.0
        self.planeImage.alpha = 0.0
      }

      UIView.addKeyframe(withRelativeStartTime: 0.51, relativeDuration: 0.01) {
        self.planeImage.transform = .identity
        self.planeImage.center = CGPoint(x: 0.0, y: originalCenter.y)
      }

      UIView.addKeyframe(withRelativeStartTime: 0.55, relativeDuration: 0.45) {
        self.planeImage.alpha = 1.0
        self.planeImage.center = originalCenter
      }
    }, completion: nil)
  }

  func summarySwitch(to summaryText: String) {
    UIView.animateKeyframes(withDuration: 1.0, delay: 0.0, animations: {
      UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.45) {
        self.summary.center.y -= 100.0
      }
      UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.45) {
        self.summary.center.y += 100.0
      }
    }, completion: nil)

    delay(seconds: 0.5) {
      self.summary.text = summaryText
    }
  }

}
Copy the code