IOS13 APP『Swift5』實例說明 — 算數練習App(動畫篇)

Shin Chao
38 min readDec 23, 2019

--

【 飄雪動畫效果】

利用 CAEmitterLayer 製作飄雪動畫,因為viewDidLoad是在畫面載入完成後就會執行,所以只需要在 view controller 的 viewDidLoad 內加入 CAEmitterLayer 飄雪的程式就可以。

飄雪效果:

簡單來說CAEmitterLayer是一個發射器,而發射的東西是粒子,其實就是 CAEmitterCell,因此需要產生 CAEmitterCell 物件,CAEmitterLayer才能發射,舉例來說CAEmitterLayer是煙火發射器,而CAEmitterCell就是煙火的砲彈。

因此第一步就要產生CAEmitterCell的物件。

let snowFlakeEmitterCell = CAEmitterCell()

物件產生後就要設定這物件的屬性,下面就來說明一些常用的屬性:

設定要顯示的圖片有關的屬性,說明如下:

  • .contents : 就是指定圖片給粒子來顯示,因為contents的型別是CGImage,所以使用UIImage後再讀取型別 CGImage 的屬性 cgImage。
  • .name : 粒子的名稱,當CAEmitterLayer內有很多個CAEmitterCell的時候,可以給每個CAEmitterCell設定好名字,那如要修改一些屬性來改變動畫效果時,就可以通過KVC拿到這個CAEmitterCell的某個屬性。
snowFlakeEmitterCell.contents = UIImage(named: "snowflake")?.cgImagesnowFlakeEmitterCell.name = "snowFlake"

粒子產生有關的屬性,說明如下:

  • .birthRate : 這是說粒子每秒產生的數量,範例上是每秒25個。
  • .lifetime: 這是每個粒子的存活時間,也就是幾秒後這粒子會消失,範例上是15秒後粒子消失。
  • .lifetimeRange : 粒子存活時間的範圍設定,這是什麼意思呢?其實就是讓粒子的存活時間變就一個範圍(lifetime — lifetimeRange) ~ (lifetime + lifetimeRange),而不是一個固定值,範例上是lifetimeRange是1.0,而lifetime是 15,所以現在粒子存活的時間就變成14~16秒。
snowFlakeEmitterCell.birthRate = 25snowFlakeEmitterCell.lifetime = 15snowFlakeEmitterCell.lifetimeRange = 1.0

粒子發射角度有關的屬性,說明如下:
角度的單位是弧度,而 180 度等於弧度 pi ,因此 CGFloat.pi / 180 等於1度的弧度。

  • .emissionLongitude : X平面的起始發射角度,這決定了粒子的飛行方向與水平座標軸(X軸)之間的夾角,如果為0,則就是順著X軸飛行。範例上是90度,也就是順時針旋轉 90度,如果是-90度就是逆時針旋轉 90度。
  • .emissionRange : 發射角度的範圍設定,這是什麼意思呢?其實跟lifetimeRange很像,只不過是角度的範圍(emissionLongitude — emissionRange) ~ (emissionLongitude + emissionRange),範例上是emissionRange是90度,而emissionLongitude是90度,所以現在粒子發射的角度範圍是0到180度。
  • .emissionLatitude : Z軸方向的發射角度(因為是平面,所以範例中沒設定)。
snowFlakeEmitterCell.emissionLongitude = CGFloat.pi * 0.5snowFlakeEmitterCell.emissionRange = CGFloat.pi * 0.5

粒子發射初始速度有關的屬性,說明如下:

  • .velocity : 粒子初始的速度。如果是負值,則在粒子生成時並不會飛,一但出現就會開始飄落,反之正值時會先飛起來再向下飄落。
  • .velocityRange : 粒子初始的速度的範圍。
snowFlakeEmitterCell.velocity = 30.0snowFlakeEmitterCell.velocityRange = 100.0

粒子發射後加速度有關的屬性,說明如下:
這加速度有點像重力加速度,讓粒子有飄的感覺。

  • .xAcceleration : X軸方向的加速 。
  • .yAcceleration : Y軸方向的加速。
  • .zAcceleration : Z軸方向的加速。(因為是平面,所以範例中沒設定)。
snowFlakeEmitterCell.xAcceleration = 5.0
snowFlakeEmitterCell.yAcceleration = 80.0

粒子縮放有關的屬性,說明如下:

  • .scale : 粒子縮放比例,範例上是0.9倍,也就是原尺寸的90%。
  • .scaleRange : 粒子縮放比例的範圍,範例上是1倍,所以粒子的縮放的倍數是0.1~1.9倍。
  • .scaleSpeed : 粒子縮放比例的速度。範例上是-0.15,所以粒子按比例每秒缩小原始大小的15%。
snowFlakeEmitterCell.scale = 0.9snowFlakeEmitterCell.scaleRange = 1snowFlakeEmitterCell.scaleSpeed = -0.15

粒子旋轉有關的屬性,說明如下:

  • .spin : 粒子平均的旋轉速度,範例上spin是1,所以就是1秒要轉spinRange所設定的角度,也就是一秒內順時針轉180度。
  • .spinRange : 粒子自己旋轉角度範圍,範例上spinRange是pi,所以就是要轉180度。
snowFlakeEmitterCell.spin = 1snowFlakeEmitterCell.spinRange = CGFloat.pi

粒子顏色有關的屬性,說明如下:
如果設置了顏色變化的範圍之後,每個生出來的粒子顏色都是隨機產生的。

  • .color : 需使用UIColor生成一個顏色,這個顏色屬性的作用是給粒子上色。
  • .redRange : 紅色的範圍值,範例上redRange是0.1,所以紅色的範圍是0.8~1.0。
  • .greenRange : 綠色的範圍值,範例上greenRange是0.1,所以綠色的範圍是0.9~1.0。(如範圍設定超過1.0,則就會是1.0)。
  • .blueRange : 藍色的範圍值,範例上blueRange是0.1,所以藍色的範圍是0.9~1.0。(如範圍設定超過1.0,則就會是1.0)。
snowFlakeEmitterCell.color = UIColor(red: 0.9, green: 1.0, blue: 1.0, alpha: 0.5).cgColorsnowFlakeEmitterCell.redRange = 0.1snowFlakeEmitterCell.greenRange = 0.1snowFlakeEmitterCell.blueRange = 0.1

粒子顏色透明度有關的屬性,說明如下:

  • .alphaRange : 粒子顏色透明度的初始值。
  • .alphaSpeed : 每秒粒子顏色透明度的變化,範例上是寫- 0.15 就是每秒透明度減少 0.15 ,這樣就會產生慢慢消失的效果。
snowFlakeEmitterCell.alphaRange = 0.75snowFlakeEmitterCell.alphaSpeed = -0.15

介紹完CAEmitterCells那接下來要介紹CAEmitterLayer了。

因此第一步與CAEmitterCells一樣,要產生CAEmitterLayer的物件。

let snowFlakeEmitterLayer = CAEmitterLayer()

物件產生後就要設定這物件的屬性,下面就來說明一些常用的屬性:

  • .emitterPosition : 這是設定發射器的中心點位置,因為位置是CGPoint,所以要提供CGPoint的X座標與Y座標,範例上CGPoint(x: view.bounds.width / 2.0, y: 0),所以位置是畫面最上方的中間,就是發射器中心點的位置。
  • .emitterSize : 發射器的大小尺寸。因為尺寸是CGSize,所以要提供CGSize的寬與高。
  • .emitterShape : 發射器的形狀。這形狀是指發射器發射出去的形狀與粒子形狀無關。有圓形(circle)、立體矩形(cuboid)、直線(line)、點(point)、矩形(rectangle)、立體球形(Sphere),如果設置立體矩形(cuboid)與立體球形(Sphere)的話,需要設定Z值,不然就跟平面一樣,立體矩形(cuboid)就會是矩形(rectangle),而立體球形(Sphere)就變成圓形(circle)。

其實這樣你可能不明白這些有什麼差異,所以直接舉圓形(circle)、直線(line)與點(point)的圖因該比較容易明白。

snowFlakeEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2.0, y: view.bounds.height / 2.0)snowFlakeEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)snowFlakeEmitterLayer.emitterShape = .line

最後就是要將CAEmitterCells生成的粒子裝入CAEmitterLayer這粒子發射器,然後再view中顯示。

snowFlakeEmitterLayer.emitterCells = [snowFlakeEmitterCell]view.layer.addSublayer(snowFlakeEmitterLayer)

如果只想在背景顯示那就用.insertSublayer,指定顯示在哪一層。

view.layer.insertSublayer(fireworksEmitterLayer, at: 0)

下面是顯示效果與完整的程式碼。

func showSnowFlake(viewWidth: CGFloat) -> CAEmitterLayer{
let snowFlakeEmitterCell = CAEmitterCell()
snowFlakeEmitterCell.contents = UIImage(named: "snowFlake")?.cgImage
snowFlakeEmitterCell.name = "snowFlake"

snowFlakeEmitterCell.birthRate = 25
snowFlakeEmitterCell.lifetime = 15
snowFlakeEmitterCell.lifetimeRange = 1.0

snowFlakeEmitterCell.emissionLongitude = CGFloat.pi * 0.5
snowFlakeEmitterCell.emissionRange = CGFloat.pi * 0.5

snowFlakeEmitterCell.velocity = 30.0
snowFlakeEmitterCell.velocityRange = 100.0

snowFlakeEmitterCell.xAcceleration = 5.0
snowFlakeEmitterCell.yAcceleration = 80.0

snowFlakeEmitterCell.scale = 0.9
snowFlakeEmitterCell.scaleRange = 1
snowFlakeEmitterCell.scaleSpeed = -0.15

snowFlakeEmitterCell.spin = 1
snowFlakeEmitterCell.spinRange = CGFloat.pi

snowFlakeEmitterCell.color = UIColor(red: 0.9, green: 1.0, blue: 1.0, alpha: 0.5).cgColor
snowFlakeEmitterCell.redRange = 0.1
snowFlakeEmitterCell.greenRange = 0.1
snowFlakeEmitterCell.blueRange = 0.1

snowFlakeEmitterCell.alphaRange = 0.75
snowFlakeEmitterCell.alphaSpeed = -0.15

let snowFlakeEmitterLayer = CAEmitterLayer()
snowFlakeEmitterLayer.emitterPosition = CGPoint(x: viewWidth / 2.0, y: 0)
snowFlakeEmitterLayer.emitterSize = CGSize(width: viewWidth, height: 0)
snowFlakeEmitterLayer.emitterShape = .line

snowFlakeEmitterLayer.emitterCells = [snowFlakeEmitterCell]
return snowFlakeEmitterLayer
}

【 煙火動畫效果】

前面已經說明了利用 CAEmitterLayer 製作飄雪動畫,而煙火動畫怎麼做呢?其實也是調整參數與建立多個CAEmitterCell來達到煙火的效果。範例中使用雪花圖片來當煙火,所以這是雪花煙火動畫效果,接下來就說明一下差異部分。

煙火最少要建立3個CAEmitterCell來完成。分別功用如下:
1. 用來管理另外兩個CAEmitterCell的顏色與消失的CAEmitterCell。
2. 用來顯示煙火路徑的CAEmitterCell。
3. 用來顯示煙火爆炸效果的CAEmitterCell。

你或覺得奇怪為什麼需要一個CAEmitterCell來管理另外兩個CAEmitterCell的顏色與消失,因為若是兩個CAEmitterCell的顏色都設顏色與顏色的區間,那麼就會發生路徑上的煙火是紅色,但爆炸時的煙火變成綠色,這樣就很奇怪了,當爆炸時如果路徑上的煙火在跑也會變得很奇怪,所以利用這個CAEmitterCell讓路徑上的煙火消失。

所以來看一下差異吧!在CAEmitterCell這邊要介紹一下剛剛沒說的屬性beginTime與duration,這兩個屬性是在CAEmitterCell有父CAEmitterCell的時候使用,說明如下:

  • .beginTime : 如果子CAEmitterCell的beginTime為0時,表示粒子從CAEmitterLayer上發射後,就會立即開始發射子CAEmitterCell,如果設為00.1,則是0.01秒後才發射子CAEmitterCell,要注意子CAEmitterCell的beginTime是不能大於父CAEmitterCell的lifetime。
  • .duration : 指定動畫持續幾秒。
flyEmitterCell.beginTime = 0.01flyEmitterCell.duration = 1.5

在這範例中,還有使用.redSpeed、.greenSpeed與.blueSpeed來改變粒子在生命週期內顏色變化的速度。

  • .redSpeed : 每秒粒子紅色的變化,範例上是寫- 0.1 就是每秒紅色減少 0.1 。
  • .greenSpeed : 每秒粒子綠色的變化,範例上是寫 0.1 就是每秒綠色增加 0.1 。
  • .blueSpeed : 每秒粒子藍色的變化,範例上是寫 0.2 就是每秒藍色增加 0.2 。
fireworksEmitterCell.redSpeed = -0.1fireworksEmitterCell.greenSpeed = 0.1fireworksEmitterCell.blueSpeed = 0.2

還有要如何將CAEmitterCell變成子CAEmitterCell呢?因為emitterCells是陣列 (Array),所以只要用逗號(,)將兩個CAEmitterCell放入陣列 (Array)後,就會變成這個CAEmitterCell的子類別。

fireworksEmitterCell.emitterCells = [flyEmitterCell, explosionEmitterCell]fireworksEmitterLayer.emitterCells = [fireworksEmitterCell]

至於CAEmitterLayer部分的差異,則是renderMode,說明如下:
.renderMode : 渲染模式。有加疊顯示(additive)、由下層向上層顯示(backToFront)、最新的在上層顯示(oldestFirst)、最新的在下層顯示(oldestLast)與隨機顯示(unordered)。

至於emitterMode在這App中並未使用,所以介紹一下.emitterMode是發射模式,其實就是設定發射的區域是在發射形狀的哪一個位置。

  • .outline : 在發射形狀的邊界輪廓上發射粒子。
  • .points : 發射器是以點的形式發射粒子。發射點就是形狀的一個特定點,如果是圓形(circle),那麼就是圓心。
  • .surface : 在發射形狀的表面上發射粒子。
  • .volume : 在3D發射形狀物體內部發射粒子。

要注意一點的是,無論粒子是從什麼樣的形狀上發射出來,當它要發射子CAEmitterCell時,子CAEmitterCell是由CAEmitterLayer.emitterShape的形狀上的父CAEmitterCell中心發射出來的。

最後一定要注意的是不論是下雪還是煙火的圖片,一定要使用適當的圖片,因為如果要縮小或是放大,都需要使用CPU資源,因此在同時顯示很多的圖片時,會造成延遲或是沒有回應的結果,如果是原尺寸是512*512並需要先縮小為32*32在使用。圖片可以上網找free的圖片,範例上的雪花是從flaticon下載的,上面有付費與免費的圖片可供下載。

func showFirework(viewWidth: CGFloat, viewHeight: CGFloat) -> CAEmitterLayer{
// Let the flying fireworks disappear before the fireworks explosion.
let fireworksEmitterCell = CAEmitterCell()
fireworksEmitterCell.emissionLongitude = -CGFloat.pi / 2.0
fireworksEmitterCell.emissionLatitude = 0
fireworksEmitterCell.lifetime = 1.6
fireworksEmitterCell.birthRate = 1
fireworksEmitterCell.velocity = 400
fireworksEmitterCell.velocityRange = 100
fireworksEmitterCell.yAcceleration = 0
fireworksEmitterCell.emissionRange = -CGFloat.pi / 4.0
fireworksEmitterCell.color = UIColor.init(red: 0.7, green: 0.5, blue: 0.7, alpha: 0.5).cgColor
fireworksEmitterCell.redRange = 0.3
fireworksEmitterCell.redSpeed = -0.1
fireworksEmitterCell.greenRange = 0.3
fireworksEmitterCell.greenSpeed = 0.1
fireworksEmitterCell.blueRange = 0.3
fireworksEmitterCell.blueSpeed = 0.2

// Fireworks fly in the sky
let flyEmitterCell = CAEmitterCell()
flyEmitterCell.contents = UIImage(named: "snowFlake")!.cgImage
flyEmitterCell.emissionLongitude = CGFloat.pi * 2
flyEmitterCell.scale = 0.4
flyEmitterCell.velocity = 100
flyEmitterCell.birthRate = 200
flyEmitterCell.lifetime = 1.5
flyEmitterCell.yAcceleration = 150
flyEmitterCell.emissionRange = CGFloat.pi / 7
flyEmitterCell.alphaSpeed = -0.7
flyEmitterCell.scaleSpeed = -0.1
flyEmitterCell.scaleRange = 0.1
flyEmitterCell.beginTime = 0.01
flyEmitterCell.duration = 1.5

// Fireworks explosion
let explosionEmitterCell = CAEmitterCell()
explosionEmitterCell.contents = UIImage(named: "snowFlake")!.cgImage
explosionEmitterCell.birthRate = 9999
explosionEmitterCell.scale = 0.6
explosionEmitterCell.velocity = 130
explosionEmitterCell.lifetime = 1.5
explosionEmitterCell.alphaSpeed = -0.2
explosionEmitterCell.yAcceleration = 80
explosionEmitterCell.beginTime = 1.5
explosionEmitterCell.duration = 1
explosionEmitterCell.emissionRange = CGFloat.pi*2
explosionEmitterCell.scaleSpeed = -0.1
explosionEmitterCell.spin = 2

fireworksEmitterCell.emitterCells = [flyEmitterCell, explosionEmitterCell]

let fireworksEmitterLayer = CAEmitterLayer()
fireworksEmitterLayer.emitterPosition = CGPoint(x: viewWidth / 2.0, y: viewHeight)
fireworksEmitterLayer.emitterSize = CGSize(width: viewWidth, height: viewHeight)
fireworksEmitterLayer.renderMode = .oldestFirst
fireworksEmitterLayer.emitterCells = [fireworksEmitterCell]
return fireworksEmitterLayer
}

在這範例中一共有3種動畫,除了煙火與飄雪外,還有一個是落葉,只是落葉使用的參數,在煙火與飄雪中介紹過了,有興趣的可以參考一下程式碼。

func showMapleLeaf(postionX: CGFloat, postionY: CGFloat, rangeSzie: CGFloat) -> CAEmitterLayer{

let yellowMapleLeafEmitterCell = CAEmitterCell()
yellowMapleLeafEmitterCell.contents = UIImage(named: "mapleLeaf")?.cgImage
yellowMapleLeafEmitterCell.name = "yellowMapleLeaf"
yellowMapleLeafEmitterCell.birthRate = 2
yellowMapleLeafEmitterCell.lifetime = 5
yellowMapleLeafEmitterCell.lifetimeRange = 3.0

yellowMapleLeafEmitterCell.emissionLongitude = CGFloat.pi / 180 * -60
yellowMapleLeafEmitterCell.emissionRange = CGFloat.pi / 180 * 10
yellowMapleLeafEmitterCell.color = UIColor(red: 8.0, green: 7.0, blue: 0, alpha: 0.5).cgColor
yellowMapleLeafEmitterCell.redRange = 0.2
yellowMapleLeafEmitterCell.greenRange = 0.3

yellowMapleLeafEmitterCell.velocity = 30.0
yellowMapleLeafEmitterCell.velocityRange = 100.0

yellowMapleLeafEmitterCell.xAcceleration = 5.0
yellowMapleLeafEmitterCell.yAcceleration = 80.0

yellowMapleLeafEmitterCell.scale = 1.2
yellowMapleLeafEmitterCell.scaleRange = 1
yellowMapleLeafEmitterCell.scaleSpeed = -0.15

yellowMapleLeafEmitterCell.spin = 1
yellowMapleLeafEmitterCell.spinRange = -CGFloat.pi

yellowMapleLeafEmitterCell.alphaRange = 0.8
yellowMapleLeafEmitterCell.alphaSpeed = -0.1


let redMapleLeafEmitterCell = CAEmitterCell()

redMapleLeafEmitterCell.contents = UIImage(named: "mapleLeaf")?.cgImage
redMapleLeafEmitterCell.name = "redMapleLeaf"

redMapleLeafEmitterCell.birthRate = 3
redMapleLeafEmitterCell.lifetime = 10
redMapleLeafEmitterCell.lifetimeRange = 3.0

redMapleLeafEmitterCell.emissionLongitude = CGFloat.pi / 180 * -60
redMapleLeafEmitterCell.emissionRange = CGFloat.pi / 180 * 5

redMapleLeafEmitterCell.velocity = 30.0
redMapleLeafEmitterCell.velocityRange = 100.0

redMapleLeafEmitterCell.xAcceleration = 5.0
redMapleLeafEmitterCell.yAcceleration = 80.0

redMapleLeafEmitterCell.scale = 1.2
redMapleLeafEmitterCell.scaleRange = 1
redMapleLeafEmitterCell.scaleSpeed = -0.15

redMapleLeafEmitterCell.spin = 1
redMapleLeafEmitterCell.spinRange = CGFloat.pi

redMapleLeafEmitterCell.alphaRange = 0.8
redMapleLeafEmitterCell.alphaSpeed = -0.2

let mapleLeafEmitterLayer = CAEmitterLayer()
mapleLeafEmitterLayer.emitterPosition = CGPoint(x: postionX, y: 0)
mapleLeafEmitterLayer.emitterSize = CGSize(width: rangeSzie, height: 0)

mapleLeafEmitterLayer.emitterShape = .line
mapleLeafEmitterLayer.emitterCells = [redMapleLeafEmitterCell, yellowMapleLeafEmitterCell]
return mapleLeafEmitterLayer
}

有關上面3種動畫的程式碼,都在專案的Animation.swift內,3種效果都寫成func,所以只要傳入要加入的View之中,預計顯示的座標,就會回傳CAEmitterLayer(),所以將回傳的CAEmitterLayer()加入要顯示View就可以。

【 圖片移動效果】

圖片動畫效果其實就是改變圖片的位置、大小、透明度或是選轉角度達到動畫效果。基本上有幾種方法可以實現動畫效果,最常見到的可能就是使用 UIView.animate。而在IOS版本10之後可以使用UIViewPropertyAnimator.runningPropertyAnimator來做到這些效果,而這兩種方式有什麼不同呢?其實UIView 動畫效果,一但開始執行無法中斷動畫與控制動畫,而UIViewPropertyAnimator可以暫停動畫、繼續播放動畫或是終止動畫,因此UIViewPropertyAnimator可以做到交互式 (Interactive) 和可中斷 (Interruptible) 的動畫效果。

那就開始來介紹一下如何做圖片的動畫效果吧,首先當然需要一個UIImageView來做動作,所以第一步就是建立UIImageView,你可以從storyboard拉,也可以從程式建立,在這範例App中兩個都用了,所以就說明一下如何從程式建立UIImageView吧。

建立UIImageView主要的步驟如下:

Step1 : 使用UIImage建立圖片。
Step2 : 用UIImage所產生的圖片來建立UIImageView。
Step3 : 設定這UIImageView的位置與大小。
Step4 : 設定UIImageView的屬性,屬性有很多,在這App中我想做消失與慢慢顯示的效果,所以我只設定了alpha。
Step5 : 使用UIView的insertSubview或是addSubview的方式將UIImageView將入View之中。insertSubview與addSubview的差異是,insertSubview可以指定圖層,而addSubview則是加到最上面的圖層,舉例來說如下圖,如果原先有一個UIImageView(藍色),現在加入第二個UIImageView(橘色),如果使用addSubview,則會覆蓋原先的UIImageView,而使用insertSubview的方式,可以讓後加入的UIImageView(綠色)指定要被覆蓋在原先的UIImageView下面,還是覆蓋 原先的UIImageView。

let flightImage = UIImage(named: "flight")
let flightImageView = UIImageView(image: flightImage)

flightImageView.frame = CGRect(x: view.bounds.width, y: view.bounds.height - (flightImage?.size.height)!, width: (flightImage?.size.width)!, height: (flightImage?.size.height)!)
flightImageView.alpha = 1
view.insertSubview(flightImageView, at: 0)

建立完UIImageView之後就來介紹一下如何利用改變位置來做動畫的原理,其實就是改變UIImageView最後的位置,如下圖,如果想要做UIImageView從畫面的右側進入,之後從畫面的左邊離開消失,那就是只有水平移動,也就是只有在X軸上移動,所以如果UIImageView原先的座標是在(414, 368),如果是要由右至左移動,當動畫做完之後,UIImageView的X座標就會修改為-(UIImageView?.size.width)!,Y座標則不變,那(UIImageView?.size.width)!是什麼呢?其實就是UIImageView的寬度,因為最後UIImageView要消失,所以將X座標設到超過UIImageView的寬度,所以是要負的UIImageView的寬度。

那接下來就是實際上動畫效果需要呼叫的API了,那就來介紹一下UIViewPropertyAnimator.runningPropertyAnimator吧,這API所要填的參數如下:

UIViewPropertyAnimator.runningPropertyAnimator(withDuration: <#T##TimeInterval#>, delay: <#T##TimeInterval#>, options: <#T##UIView.AnimationOptions#>, animations: <#T##() -> Void#>, completion: <#T##((UIViewAnimatingPosition) -> Void)?##((UIViewAnimatingPosition) -> Void)?##(UIViewAnimatingPosition) -> Void#>)
  • withDuration : 動畫從開始的位置到最後的位置要幾秒。
  • delay : 幾秒後開始動畫效果。
  • options : 動畫選項,這有很多效果,就不一一介紹了,如果想知道有哪些可以參考一下下面developer.apple.com的說明,如.curveEaseInOut這是動畫一開始的時候加速移動,動畫结束時則減速。參數 options 有預設值,所以可省略。
  • animations : 動畫時間內要做的事情,在動畫結束時畫面要呈現的樣子,如設定最後位置或屬性。
  • completion : 動畫完成後想要做的事,如果沒有可以設定nil。如果completion的參數使用不到,則用下底線_ 表示忽略參數。參數 completion 有預設值,所以與options一樣可省略。

在這App內我用程式做了兩個UIImageView,讓這兩個UIImageView從兩側往中央移動並旋轉30度,完成之後消失,並呼叫下一個動畫,這個UIImageView則是用Storyboard拉出來的,這個UIImageView原先是透明的(cupImageView.alpha = 0),這動畫是慢慢將透明度設定為不透明(cupImageView.alpha = 1)並且將UIImageView放大1.5倍的寬與高。那怎麼旋轉30度呢?其實也是對UIImageView設定.transform這個屬性,因此使用CGAffineTransform(rotationAngle: ),來設定角度,之前說過角度的單位是弧度,而 180 度等於弧度 pi ,因此 CGFloat.pi / 180 等於1度的弧度,所以乘30就是順時針30度,乘-30就是逆時針30度。而放大則是CGAffineTransform(scaleX: , y: )來設定放大或縮小。

UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: {
flightImageView.frame.origin.x = self.view.bounds.width / 2
flightImageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 180 * 30)

racecarImageView.frame.origin.x = self.view.bounds.width / 2 - (racecarImage?.size.width)!
racecarImageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 180 * -30)
}) { (_) in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 3, delay: 0, options: .curveEaseInOut, animations: {
flightImageView.alpha = 0
racecarImageView.alpha = 0
self.cupImageView.alpha = 1
self.cupImageView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}, completion: nil)
}

當然這只是基本的圖片動畫效果,在這App中並未使用到暫停動畫等等的功能,之後有機會再說明吧。

【 圖片連續播放效果】

圖片連續播放其實就像是Gif的檔案一樣,利用時間快速顯示不同圖片,讓我們看起來像動畫一樣,IOS原生的SDK並沒支援Gif,如要播放Gif需要安裝第三方套件,如Lottie,但是IOS的ImageView有提供animatedImage與animatedImageNamed的方法,讓我們將圖片一直循環播放,下面就來說明一下吧。

  • UIImage.animatedImage : 其實使用UIImage.animatedImage方式很簡單,傳入的參數有兩個,一個是 with 這是一個包含多張圖片的UIImage array,另一個參數是duration,這是多久換下一個圖片的時間。
let finishPics = [UIImage(named: "angel1")!, UIImage(named: "angel2")!, UIImage(named: "angel3")!, UIImage(named: "angel2")!, UIImage(named: "angel1")!]finishImage.image = UIImage.animatedImage(with: finishPics, duration: 1)

所以在呼叫UIImage.animatedImage之前,必須先建立一個UIImage array,裡面就是放入要連續播放的圖片,你可以直接手動一個一個打UIImage(named: “xxxx”)!的方式建立這array,但是圖片多的時候就太麻煩了,這時候可以用for in的方式,讓程式來幫忙建立這個array,其實就是利用array的append方法將UIImage加入array,但是要記得一點,就是圖片的檔案名稱99%必須要一樣,只有任意的一個地方可以更換為連續的整數,這樣才能用for in來幫忙改名稱,然後加入array。

var animationImages = [UIImage]()

for number in 0 ... 11 {
animationImages.append(UIImage(named: "dancing" + "\(number)")!)
}

princessImage.image = UIImage.animatedImage(with: animationImages, duration: 1)
  • UIImage.animatedImageNamed : 使用UIImage.animatedImageNamed的方式是不需要建立array也可以連續播放的圖片,傳入的參數一樣有兩個,一個是String,這是圖片名子,另一個參數是duration,這是多久換下一個圖片的時間。要注意的是圖片名字必須是數字結尾,也必須是從 0 開始依序遞增,會以這遞增的數字決定圖片動畫的順序。例如圖片dancing0, dancing1, dancing2, dancing3,只要傳入dancing就可以,就會自動找到dancing0到dancing4。圖片名字結尾的數字最大可以到 1024,因此最多可以用1025 張圖來組合出動畫效果。
princessImage.image = UIImage.animatedImageNamed("dancing", duration: 1)

【 使用Timer做出圖片動畫效果】

因為Timer是可以設定固定時間去執行某件事,所以也可以利用Timer完成,因為Timer可以隨時終止,所以有的時候需要中斷動畫效果時,使用Timer也是一種選擇。那就來說明一下用法吧。

Step 1 : 宣告一個型別為Timer的全域變數

var calculationTimer: Timer?

Step 2 : 因為是要顯示圖片的動畫效果,所以看是要在哪裡呼叫.scheduledTimer,可以在ViewDidLoad內也可以viewWillAppear等等不同的地方。因為Timer是全域變數,並不會因為ViewDidLoad()或是viewWillAppear()結束就跟者結束。這方法可以有不同的參數,所以這邊只介紹一種

Timer.scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer
  • withTimeInterval : 幾秒執行一次。
  • repeats : 是否要重複執行。
  • block : 每次要執行的動作。

Step 3 : 如果要終止Timer,就只需要呼叫.invalidate(),就會終止動畫。

calculationTimer?.invalidate()

在使用Timer時需要注意一點,就是離開View時必須要終止Timer,不然會繼續執行,所以在viewWillDisappear()時,要呼叫.invalidate()來終止Timer。

--

--