#27 viewDidLoad 卡娜赫拉 兔兔要吃飯

Rose
彼得潘的 Swift iOS / Flutter App 開發教室
18 min readApr 15, 2021

在 viewDidLoad 裡寫程式加入漸層、動畫、下雪、音樂等有興趣的功能。

兔兔與 P 助 肚子餓了~一直敲桌子,所以我要用 APP 下食物雨,讓他們有吃不完的食物

使用漸層背景

在 Playground 裡製作

import UIKitlet gradientView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))let gradientLayer = CAGradientLayer()gradientLayer.frame = gradientView.boundsgradientLayer.colors = [UIColor.orange.cgColor, UIColor.blue.cgColor]gradientView.layer.addSublayer(gradientLayer)gradientView

研究使用 image view 實現多張圖片連續播放的動畫

iOS 13之後

guard let data = NSDataAsset(name: "runny-cat")?.data else { return }let cfData = data as CFDataCGAnimateImageDataWithBlock(cfData, nil) { (_, cgImage, _) inself.imageView.image = UIImage(cgImage: cgImage)}

開始製作卡娜赫拉 兔兔 P助動畫

前面是熱身運動,現在開始來製作動畫

Xcode 加入動畫圖檔

開啟新的專案,把準備好把的 apng 動圖拖拉到與 .storyboard 同一層的位置放開滑鼠會出現視窗

第一個 Copy items if needed 要勾選,不勾檔案是外連的,原圖刪掉之後程式會出錯

按 Finish 即可

圖檔的位置

撰寫程式

(1) 在 storyboard 加入播放 動畫 的 image view & 連結 outlet

class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!

(2) 產生動畫圖片

方法一: function CGAnimateImageDataWithBlock

override func viewDidLoad() {
super.viewDidLoad()
guard let data = NSDataAsset(name: "animated-cat-image-72")?.data else { return }
let cfData = data as CFData
CGAnimateImageDataWithBlock(cfData, nil) { (_, cgImage, _) in
self.imageView.image = UIImage(cgImage: cgImage)
}
}

方法二: 透過 CGAnimateImageAtURLWithBlock 搭配 gif 的 URL 產生動畫圖片

(最後採用方法二)

override func viewDidLoad() {
super.viewDidLoad()

// 載入動畫
guard let url = Bundle.main.url(forResource: "15281900@2x", withExtension: "png") else { return }
let cfUrl = url as CFURL
CGAnimateImageAtURLWithBlock(cfUrl, nil) { (_, cgImage, _) in
self.imageView.image = UIImage(cgImage: cgImage)
}
}

編譯執行:動圖就出現了

import UIKitclass ViewController: UIViewController {

@IBOutlet weak var imageView: UIImageView!

override func viewDidLoad() {
super.viewDidLoad()

//製作漸層畫面
let gradientView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 926))
let gradientLayer = CAGradientLayer()
gradientLayer.frame = gradientView.bounds
gradientLayer.colors = [UIColor.orange.cgColor, UIColor.blue.cgColor]
gradientView.layer.addSublayer(gradientLayer)
view.layer.addSublayer(gradientLayer)

// 載入動畫
guard let url = Bundle.main.url(forResource: "15281900@2x", withExtension: "png") else { return }
let cfUrl = url as CFURL
CGAnimateImageAtURLWithBlock(cfUrl, nil) { (_, cgImage, _) in
self.imageView.image = UIImage(cgImage: cgImage)
}
}
}

(3) 漸層背景

我推薦可以到這裡選擇漂亮的漸層複製色碼
WebGradients 漂亮的漸層色票網站

不過發現模擬器顯示出來的顏色比較螢光一點,原本的顏色跟封面一樣是比較溫和的漸層色,模擬器的這個粉紅色變得有點俗氣呀

雪花動畫

詳細做法可參考彼得潘的文章

利用 CAEmitterLayer 我們可以發射粒子(particle),產生常見的下雪,下雨,火焰,煙火等動畫。

2 將雪花圖片加到 Assets.xcassets。

3 在 view controller 的 viewDidLoad 裡加入 CAEmitterLayer 發射雪花的程式。

viewDidLoad 將在畫面載入完成時執行,我們在裡面加入下蘿蔔雨的效果。

override func viewDidLoad() {   super.viewDidLoad()   view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.4)
let snowEmitterCell = CAEmitterCell() snowEmitterCell.contents = UIImage(named: "snowflake")?.cgImage snowEmitterCell.birthRate = 1 snowEmitterCell.lifetime = 2 snowEmitterCell.velocity = 100
let snowEmitterLayer = CAEmitterLayer() snowEmitterLayer.emitterCells = [snowEmitterCell] view.layer.addSublayer(snowEmitterLayer)}

只有蘿蔔太單調了,兔兔喜歡菜色多點變化所以再加點番茄和花椰菜

加入音樂

將音樂檔案拖曳到專案的 Project navigator 裡。(記得 copy items & Add to targets 要勾選)
(此音樂是自己在線上圖庫有購買版權的)

利用 AVPlayer 播放 App 裡的音樂

import UIKit
import AVFoundation
class ViewController: UIViewController {

let player = AVPlayer()

override func viewDidLoad() {
super.viewDidLoad()

let fileUrl = Bundle.main.url(forResource: "music", withExtension: "mp4")!
let playerItem = AVPlayerItem(url: fileUrl)
player.replaceCurrentItem(with: playerItem)
player.play()
}
}

因為有加音樂,但是螢幕錄影沒聲音所以暫時沒影片,等搞定錄影再重新上傳影片

gif 動畫展示

天上掉下來好多食物,兔兔與 P 助不怕餓肚子了

完整程式碼

import UIKit
import AVFoundation
class ViewController: UIViewController {@IBOutlet weak var imageView: UIImageView!
//播音樂
let player = AVPlayer()

override func viewDidLoad() {
super.viewDidLoad()

//製作漸層畫面1
let gradientView = UIView(frame: CGRect(x: 0, y: 0, width: 428, height: 926))
let gradientLayer = CAGradientLayer()
gradientLayer.frame = gradientView.bounds
gradientLayer.colors = [
UIColor(red: 1, green: 113/255, blue: 154/155, alpha: 1).cgColor,
UIColor(red: 1, green: 169/255, blue: 159/155, alpha: 1).cgColor,
UIColor(red: 1, green: 226/255, blue: 159/255, alpha: 1).cgColor
]
//view.layer.addSublayer(gradientLayer)
view.layer.insertSublayer(gradientLayer, at: 0)
// 載入動畫
guard let url = Bundle.main.url(forResource: "15281900@2x", withExtension: "png") else { return }
let cfUrl = url as CFURL
CGAnimateImageAtURLWithBlock(cfUrl, nil) { (_, cgImage, _) in
self.imageView.image = UIImage(cgImage: cgImage)
}
//蘿蔔
let carrotsEmitterCell = CAEmitterCell()
// 設定粒子顯示的雪花圖片。由於 contents 要求的型別是 CGImage,因此我們產生 UIImage 後要再讀取型別 CGImage 的屬性 cgImage。
carrotsEmitterCell.contents = UIImage(named: "Carrots")?.cgImage
// 設定每秒發射幾個雪花,我們指定一秒一個。
carrotsEmitterCell.birthRate = 4
// 雪花維持的秒數,我們讓雪花只停留兩秒鐘。
carrotsEmitterCell.lifetime = 18
// 雪花移動的速度。
carrotsEmitterCell.velocity = -30
carrotsEmitterCell.velocityRange = -20
// 設定向下移動的加速度,沒有加這個會是由左往右移動
carrotsEmitterCell.yAcceleration = 30
carrotsEmitterCell.xAcceleration = 5
// 雪花大小
carrotsEmitterCell.scale = 0.4
// 控制雪花大小的範圍 0.5-0.3=0.2 ~ 0.5+0.3 = 0.8也就是 0.2~0.8
carrotsEmitterCell.scaleRange = 0.1
// 雪花大小改變的速度,小於 0 會愈來愈小,大於 0 會愈來愈大
carrotsEmitterCell.scaleSpeed = -0.02
// 旋轉吧,雪花,spin 和 spinRange 設定雪花轉速的範圍為 -0.5(0.5–1) ~ 1.5(0.5+1),單位為弧度
carrotsEmitterCell.spin = -0.5
carrotsEmitterCell.spinRange = 1
// 雪花發射的角度範圍,讓它們有些往左下,有些往右下
carrotsEmitterCell.emissionRange = CGFloat.pi
// 產生 CAEmitterLayer,將它的 emitterCells 指定為剛剛產生的雪花粒子 carrotsEmitterCell。
let carrotsEmitterLayer = CAEmitterLayer()
carrotsEmitterLayer.emitterCells = [carrotsEmitterCell]
// 雪花發射的路徑,落到底部
carrotsEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: -50) //畫面上方的水平線發射
carrotsEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)
carrotsEmitterLayer.emitterShape = .line //可控制雪花從哪裡發射.line 將讓雪花從水平線發射
// 利用 addSublayer 將 carrotsEmitterLayer 的下雪效果加到畫面上。
view.layer.addSublayer(carrotsEmitterLayer)

// 花耶
let broccoliEmitterCell = CAEmitterCell()
broccoliEmitterCell.contents = UIImage(named: "Broccoli")?.cgImage
broccoliEmitterCell.birthRate = 2
broccoliEmitterCell.lifetime = 16
broccoliEmitterCell.velocity = -28
broccoliEmitterCell.velocityRange = -20
broccoliEmitterCell.yAcceleration = 32
broccoliEmitterCell.xAcceleration = 5
broccoliEmitterCell.scale = 0.3
broccoliEmitterCell.scaleRange = 0.2
broccoliEmitterCell.scaleSpeed = -0.02
broccoliEmitterCell.spin = -0.5
broccoliEmitterCell.spinRange = 1
broccoliEmitterCell.emissionRange = CGFloat.pi
let broccoliEmitterLayer = CAEmitterLayer()
broccoliEmitterLayer.emitterCells = [broccoliEmitterCell]
broccoliEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: -50)
broccoliEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)
broccoliEmitterLayer.emitterShape = .line
view.layer.addSublayer(broccoliEmitterLayer)

// 番茄
let tomatoEmitterCell = CAEmitterCell()
tomatoEmitterCell.contents = UIImage(named: "Tomato")?.cgImage
tomatoEmitterCell.birthRate = 3
tomatoEmitterCell.lifetime = 10
tomatoEmitterCell.velocity = -28
tomatoEmitterCell.velocityRange = -20
tomatoEmitterCell.yAcceleration = 29
tomatoEmitterCell.xAcceleration = 4
tomatoEmitterCell.scale = 0.4
tomatoEmitterCell.scaleRange = 0.1
tomatoEmitterCell.scaleSpeed = -0.02
tomatoEmitterCell.spin = -0.4
tomatoEmitterCell.spinRange = 1
tomatoEmitterCell.emissionRange = CGFloat.pi
let tomatoEmitterLayer = CAEmitterLayer()
tomatoEmitterLayer.emitterCells = [tomatoEmitterCell]
tomatoEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: -33)
tomatoEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)
tomatoEmitterLayer.emitterShape = .line
view.layer.addSublayer(tomatoEmitterLayer)

// 音樂
let fileUrl = Bundle.main.url(forResource: "the_table_song", withExtension: "mp3")!
let playerItem = AVPlayerItem(url: fileUrl)
player.replaceCurrentItem(with: playerItem)
player.play()

}
}

參考文章

--

--

Rose
彼得潘的 Swift iOS / Flutter App 開發教室

Coding & Design 一直在學習的路上,從未停止,一有空檔就會摸摸我的兔子🐰