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