swiftPractice[19]_聖誕倒數月曆
CAEmitterLayer, 亂數 & Gesture Recognizer 練習
HOHO! 聖誕節快到了,想到 Line 或是 Google 都會有一些應景的畫面或是小遊戲, 這次的練習參考了 Peter 最新聖誕倒數月曆作業+下雪動畫, 做了一個每日隨機抽卡的 APP, 內容來自正向能量滿滿的彩虹卡, 做的時候還會不小心唱起聖誕歌~🎄🎄🎄
成品
✦ 假設今天日期是 12/17 (17號之後的按鈕不能按)
APP 功能 & 練習項目:
- 功能:點選數字顯示卡片,卡片內容隨機,還沒到的日期不能點選
- 功能:點選卡片回到數字的樣子,選過的數字變灰色
- 使用 CAEmitterLayer 製作下雪動畫
拆解!
目錄:
1.拉 IBOutlet & IBAction
2.設定初始畫面
2-1.使用 CAEmitterLayer 製作下雪動畫
補充:使用#Preview macro即時預覽畫面
3.功能:點選數字顯示卡片(內容隨機),還沒到的日期不能點選
4.功能:點選卡片回到數字的樣子
1. 拉 IBOutlet & IBAction
2. 設定初始畫面
✦ 主要是設定彈出卡片的內容與樣式,然後在使用 .isHidden 將卡片隱藏,方便之後按下按鈕可以顯示
override func viewDidLoad() {
super.viewDidLoad()
//設定彈出畫面樣式
popUpImageView.layer.cornerRadius = 10
//文字內容設定
textLabel.font = .systemFont(ofSize: 27)
textLabel.numberOfLines = 0
textLabel.backgroundColor = .white.withAlphaComponent(CGFloat(0.85))
textLabel.textAlignment = .center
textLabel.text = contents[0]
//日期標籤設定
dayLabel.font = .systemFont(ofSize: 90, weight: UIFont.Weight.semibold)
dayLabel.textAlignment = .center
dayLabel.textColor = UIColor(red: 73/255 , green: 88/255, blue: 0, alpha: 1)
popUpImageView.addSubview(textLabel)
popUpImageView.addSubview(dayLabel)
//隱藏卡片
blurView.isHidden = true
popUpImageView.isHidden = true
}
2-1. 使用 CAEmitterLayer 製作下雪動畫
✦ 這是最好玩又療癒的部分!需要先設定 CAEmitterCell 的各種條件,再藉由 CAEmitterLayer 呈現出來,搭配最新的 #Preview macro 功能可以邊預覽邊微調!
//下雪動畫
let snowEmitterCell = CAEmitterCell()
//設定雪花照片
snowEmitterCell.contents = UIImage(named: "snowflakes")?.cgImage
//設定每秒生成的雪花數量
snowEmitterCell.birthRate = 0.5
//設定雪花可以維持的時間(秒)
snowEmitterCell.lifetime = 50
//速度
snowEmitterCell.velocity = 0.5
//雪花大小
snowEmitterCell.scale = 0.06
//大小範圍+-0.1
snowEmitterCell.scaleRange = 0.1
//y軸加速度,>0向下;<0向上
snowEmitterCell.yAcceleration = 0.8
//旋轉速度
snowEmitterCell.spin = 0.6
//旋轉速度範圍+-0.4
snowEmitterCell.spinRange = 0.4
let snowEmitterLayer = CAEmitterLayer()
//傳入設計好的EmitterCell,可傳多種
snowEmitterLayer.emitterCells = [snowEmitterCell]
//發射位置
snowEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width/2, y: -50)
//發射範圍
snowEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)
//發射形狀
snowEmitterLayer.emitterShape = .line
view.layer.addSublayer(snowEmitterLayer)
更多細節跟詳細解說可以參考下面文章👇
3. 功能:點選數字顯示卡片(內容隨機),還沒到的日期不能點選
✦ 在 function 外宣告需要的變數,方便在各處使用;並用 array 儲存卡片內容
//內容標籤
var textLabel = UILabel(frame: CGRect(x: 21, y: 130, width: 320, height: 150))
//日期標籤
var dayLabel = UILabel(frame: CGRect(x: 130, y: 20, width: 120, height: 100))
//卡片內容array
var contents = [
"I approach other people confidently and lovingly.",
"When I need help, I ask my heart or the universe.",
"I am not here to satisfy others and live my life according to their ideas.",
"I am still worthy of love when I ask others for help.",
"Everything I need to know I am told at the right time and in the right place.",
"Peace start with myself.",
"I know the difference between true needs and neediness.",
"I am thankful that everything that I need is provided.",
"I greet every new day with love in my heart.",
"I rejoice in the company of friends."]
✦ 使用 DateFormatter 得到當天日期字串,因為是 12 月專屬,我只需要確認日期跟按鈕上的日期是否對等(或大於),確保還沒到的日子不能抽卡!
✦ 使用 sender.titleLabel!.text! 讀出使用者按下的按鍵日期,再轉為 Int 比較
✦ 我準備了 4 種卡片背景、9個卡片內容,皆用亂數方式 Int.random(in: ) 取得,再分別存入對應的位置
✦ 用 .isHidden = false 將卡片顯示出來
✦ 將 sender 讀取到的按鈕用 .configuration?.baseBackgroundColor = .systemGray3 讓被選過的按鈕變成灰色
@IBAction func tapOnDay(_ sender: UIButton) {
let today = Date()
let formatter = DateFormatter()
//設定日期呈現格式
formatter.dateFormat = "dd"
//生成當日日期字串
let dateString = formatter.string(from: today)
//當天日期>=選擇日期時
if Int(dateString)! >= Int(sender.titleLabel!.text!)!{
//亂數取得背景圖
popUpImageView.image = UIImage(named: "background\(Int.random(in: 1...4))")
//亂數取得卡片內容
textLabel.text = contents[Int.random(in: 0...9)]
//將按鈕對應日期更新到卡片上
dayLabel.text = "\(sender.titleLabel!.text!)"
//顯示卡片
blurView.isHidden = false
popUpImageView.isHidden = false
//選擇過後的按鈕變灰色
sender.configuration?.baseBackgroundColor = .systemGray3
//當天日期<選擇日期時不能點選
}else{
sender.isUserInteractionEnabled = false
}
}
4. 功能:點選卡片回到數字的樣子
✦ 在 popUpImageView 加入 TapGestureRecognizer,拉一條 IBAction,當使用者點擊卡片時將卡片與模糊背景隱藏,回到最開始的數字畫面
✦ 這裡的 BlurView 使用的是 Object Library 裡的 Visual Effect View with Blur
@IBAction func tapGoBack(_ sender: UITapGestureRecognizer) {
blurView.isHidden = true
popUpImageView.isHidden = true
}
完整程式碼
import UIKit
class XmasViewController: UIViewController {
@IBOutlet weak var popUpImageView: UIImageView!
@IBOutlet weak var blurView: UIVisualEffectView!
var textLabel = UILabel(frame: CGRect(x: 21, y: 130, width: 320, height: 150))
var dayLabel = UILabel(frame: CGRect(x: 130, y: 20, width: 120, height: 100))
var contents = [
"I approach other people confidently and lovingly.",
"When I need help, I ask my heart or the universe.",
"I am not here to satisfy others and live my life according to their ideas.",
"I am still worthy of love when I ask others for help.",
"Everything I need to know I am told at the right time and in the right place.",
"Peace start with myself.",
"I know the difference between true needs and neediness.",
"I am thankful that everything that I need is provided.",
"I greet every new day with love in my heart.",
"I rejoice in the company of friends."]
override func viewDidLoad() {
super.viewDidLoad()
let snowEmitterCell = CAEmitterCell()
snowEmitterCell.contents = UIImage(named: "snowflakes")?.cgImage
snowEmitterCell.birthRate = 0.5
snowEmitterCell.lifetime = 50
snowEmitterCell.velocity = 0.5
snowEmitterCell.scale = 0.06
snowEmitterCell.scaleRange = 0.1
snowEmitterCell.yAcceleration = 0.8
snowEmitterCell.spin = 0.6
snowEmitterCell.spinRange = 0.4
let snowEmitterLayer = CAEmitterLayer()
snowEmitterLayer.emitterCells = [snowEmitterCell]
snowEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width/2, y: -50)
snowEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)
snowEmitterLayer.emitterShape = .line
view.layer.addSublayer(snowEmitterLayer)
//設定彈出畫面樣式
popUpImageView.layer.cornerRadius = 10
//文字內容設定
textLabel.font = .systemFont(ofSize: 27)
textLabel.numberOfLines = 0
textLabel.backgroundColor = .white.withAlphaComponent(CGFloat(0.85))
textLabel.textAlignment = .center
textLabel.text = contents[0]
//日期標籤設定
dayLabel.font = .systemFont(ofSize: 90, weight: UIFont.Weight.semibold)
dayLabel.textAlignment = .center
dayLabel.textColor = UIColor(red: 73/255 , green: 88/255, blue: 0, alpha: 1)
popUpImageView.addSubview(textLabel)
popUpImageView.addSubview(dayLabel)
blurView.isHidden = true
popUpImageView.isHidden = true
}
//點選數字顯示卡片(內容隨機),還沒到的日期不能點選
@IBAction func tapOnDay(_ sender: UIButton) {
let today = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd"
let dateString = formatter.string(from: today)
if Int(dateString)! >= Int(sender.titleLabel!.text!)!{
popUpImageView.image = UIImage(named: "background\(Int.random(in: 1...4))")
textLabel.text = contents[Int.random(in:0...9)]
dayLabel.text = "\(sender.titleLabel!.text!)"
blurView.isHidden = false
popUpImageView.isHidden = false
sender.configuration?.baseBackgroundColor = .systemGray3
}else{
sender.isUserInteractionEnabled = false
}
}
//點選卡片回到數字的樣子
@IBAction func tapGoBack(_ sender: UITapGestureRecognizer) {
blurView.isHidden = true
popUpImageView.isHidden = true
}
問題!!!!!
問題 1: 有改變按鈕顏色卻不需要拉 IBOutlet ?
我一開始拉了所有按鈕的 IBOutlet,但是完成後越看越覺得好像不需要,於是實驗性的刪了發現程式還是可以跑!於是我問了ChatGPT
我的理解是:因為 sender 徵測到的是 UIButton,我要更改的剛好就是每一個選到的 Button 自己的屬性,所以可以直接讀取更改;但如果我要更改其他不是使用者當前按下的 Button 的屬性,就會需要用到 IBOutlet
問題 2: 要怎麼儲存亂數取得的內容到對應的 Button 裡?(待解決)
當前的程式會遇到一個問題,當我重按過去選到的 Button 時會得到一組新的內容,表示前幾天抽到的卡再也不會看到,每次點只會得到一張新卡。
目前找到的方向是要存在 dictionary 裡面,但是有點超出範圍也花太多時間了,決定先留著問題,等想到再回來升級它!
作業出處:
GitHub