#19 使用 IBOutlet,IBAction & 亂數創作全民打棒球抽卡app

Lou
彼得潘的 Swift iOS / Flutter App 開發教室
31 min readApr 19, 2023

紀念逝去的台版BBO

現代貝比魯斯 世界的大谷翔平

SetCardViewController 篩選抽卡畫面

首頁隨機圖片
import UIKit
import AVFoundation

let player = AVPlayer()
var timer: Timer?

override func viewDidLoad() {
super.viewDidLoad()

//播放BBO背景音樂
let url = Bundle.main.url(forResource: "my-hero", withExtension: "mp3")!
let playerItem = AVPlayerItem(url: url)
player.replaceCurrentItem(with: playerItem)
player.play()
//漸層背景
setupGradientBackground()
//標題旋轉
titleLabel.transform = CGAffineTransform(rotationAngle: -.pi / 80)
//初始球員卡圖片
initialImageView()
//設定Silder Thumb圖案
hitValueSilder.setThumbImage(UIImage(systemName: "baseball.fill"), for: .normal)
//利用timer隨機替換圖片
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(changeImageView), userInfo: nil, repeats: true)

}

//漸層背景
func setupGradientBackground() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = view.bounds
gradientLayer.colors = [
CGColor(red: 1, green: 216/255, blue: 118/255, alpha: 1),
UIColor.white.cgColor
]
view.layer.insertSublayer(gradientLayer, at: 0)
}


//利用shuffle隨機替換圖片
@objc func changeImageView(){
playerImageArray.shuffle()
initialImageView()
}

//設定圖片
func initialImageView(){
player1ImageView.image = UIImage(named: playerImageArray[0])
player2ImageView.image = UIImage(named: playerImageArray[1])
player3ImageView.image = UIImage(named: playerImageArray[2])
}

//鍵盤按下return收鍵盤
@IBAction func returnCloseKeyboard(_ sender: Any) {

}
//點選空白處收鍵盤
@IBAction func closeKeyboard(_ sender: Any) {
view.endEditing(true)
}

斜角ImageView & View

抽一波
建立SharpView SharpView
import UIKit

//斜角效果的View
class SharpView: UIView {

override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: bounds.height * 0.2))
path.addLine(to: CGPoint(x: bounds.width, y: 0))
path.addLine(to: CGPoint(x: bounds.width, y:bounds.height))
path.addLine(to: CGPoint(x: 0, y: bounds.height))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
layer.mask = shapeLayer
}


import UIKit
//斜角效果的ImageView
class SharpView: UIImageView {

override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.width * 0.2, y: 0))
path.addLine(to: CGPoint(x: bounds.width, y: 0))
path.addLine(to: CGPoint(x: bounds.width * 0.8, y:bounds.height))
path.addLine(to: CGPoint(x: 0, y: bounds.height))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
layer.mask = shapeLayer

}

Silder篩選球員卡能力類型

Slider滑動顯示類型

//滑動Slider顯示不同類型
@IBAction func changeHitValue(_ sender: UISlider) {
if hitValueSilder.value > 20 {
hitLabel.text = ""
hitPowerLabel.text = "力量型"
hitContactLabel.text = ""
} else if hitValueSilder.value < 10{
hitLabel.text = ""
hitPowerLabel.text = ""
hitContactLabel.text = "打擊型"
} else {
hitLabel.text = "隨機"
hitPowerLabel.text = ""
hitContactLabel.text = ""
}
}

篩選條件功能

篩選生日條件
篩選守位 聯盟 能力條件
 @IBAction func filter(_ sender: Any) {
//生日條件只能單獨篩選
if dateSwitch.isOn {
//提示視窗
let alertController = UIAlertController(title: "啟用生日篩選球員卡", message: "只能單獨篩選生日條件,不能加入其他篩選條件。", preferredStyle: .alert)
let okAction = UIAlertAction(title: "確定啟用", style: .default)
let cancelAction = UIAlertAction(title: "取消篩選", style: .cancel){ _ in
self.dateSwitch.isOn = false
}
alertController.addAction(okAction)
alertController.addAction(cancelAction)
present(alertController, animated: true)
//其他條件變成初始隨機
hitValueSilder.value = 15
positionSegmentedControl.selectedSegmentIndex = 0
leagueSegmentedControl.selectedSegmentIndex = 0
}

//篩選條件最多兩項 超過跳出提示視窗
if hitValueSilder.value < 10 || hitValueSilder.value > 20, positionSegmentedControl.selectedSegmentIndex != 0, leagueSegmentedControl.selectedSegmentIndex != 0 {
let alertController = UIAlertController(title: "球員卡篩選限制", message: "守位、聯盟、能力 3項條件,最多只能設定 2個項目!", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "重新設定", style: .cancel)
alertController.addAction(cancelAction)
present(alertController, animated: true)
hitValueSilder.value = 15
hitLabel.text = "隨機"
hitPowerLabel.text = ""
hitContactLabel.text = ""
positionSegmentedControl.selectedSegmentIndex = 0
leagueSegmentedControl.selectedSegmentIndex = 0
}

//重置篩選條件
@IBAction func resetRule(_ sender: Any) {
nameTextField.text = ""
birthdayDatePicker.date = NSDate() as Date
dateSwitch.isOn = false
hitValueSilder.value = 15
hitLabel.text = "隨機"
hitPowerLabel.text = ""
hitContactLabel.text = ""
positionSegmentedControl.selectedSegmentIndex = 0
leagueSegmentedControl.selectedSegmentIndex = 0
}

傳遞資料到下一頁

@IBSegueAction func setRules(_ coder: NSCoder) -> GetCardViewController? {
//關掉隨機圖片timer
timer?.invalidate()
let name = String(nameTextField.text!)
//傳遞生日篩選條件的月份
let dateComponents = Calendar.current.dateComponents(in: TimeZone.current, from: birthdayDatePicker.date)
var month = 0
if dateSwitch.isOn {
//開啟篩選生日switch才有資料
month = Int(dateComponents.month!)
}
let hitValue = Float(hitValueSilder.value)
let position = Int(positionSegmentedControl.selectedSegmentIndex)
let league = Int(leagueSegmentedControl.selectedSegmentIndex)

let controller = GetCardViewController(coder: coder)

controller?.userName = name
controller?.month = month
controller?.hitValue = hitValue
controller?.positionIndex = position
controller?.leagueIndex = league
controller?.playerSeconds = playerSeconds

return controller
}

GetCardViewController抽球員卡拉霸頁面

拉霸抽卡
Scroll View + Stack View

利用Scroll View捲動效果 放入超過View大小的Stack View

再利用程式滾動+圖片替換達成拉霸抽卡效果

    override func viewDidLoad() {
super.viewDidLoad()
//背景漸層
setupGradientBackground()
//加入篩選後球員卡
addPlayerCard()
//球員卡洗牌
shuffleCard()
//設定初始四張球員卡圖片
initialCard()
//捲動球員卡拉霸效果
autoScroll()
}

//背景漸層
func setupGradientBackground() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = view.bounds
gradientLayer.colors = [
CGColor(red: 1, green: 216/255, blue: 118/255, alpha: 1),
UIColor.white.cgColor
]
view.layer.insertSublayer(gradientLayer, at: 0)
}

//設定初始四張球員卡圖片
func initialCard(){
playerCard1ImageView.image = UIImage(named: randomPlayerCardArray[0])
playerCard2ImageView.image = UIImage(named: randomPlayerCardArray[1])
playerCard3ImageView.image = UIImage(named: randomPlayerCardArray[2])
playerCard4ImageView.image = UIImage(named: randomPlayerCardArray[3])
}

滾動Scroll View功能

    var timer: Timer?
var scrollDistance = 0
var changeCardCount = 0
var rounds = 3
var scrollSpeed = 0.05

func autoScroll(){
timer = Timer.scheduledTimer(timeInterval: scrollSpeed, target: self, selector: #selector(randomCard), userInfo: nil, repeats: true)
}

@objc func randomCard() {
//畫面顯示imageView編號123 滾動顯示編號234 再變回初始顯示編號123
//scrollView滾動1張imageView的距離
if scrollDistance == 220{
scrollDistance = 0
randomCardScrollView.contentOffset.y = CGFloat(scrollDistance)
changeCardCount += 1
//array剩下倒數三張 將第4張imageView改成array[0]
if changeCardCount == randomPlayerCardArray.count - 3{
playerCard1ImageView.image = UIImage(named: randomPlayerCardArray[randomPlayerCardArray.count - 3])
playerCard2ImageView.image = UIImage(named: randomPlayerCardArray[randomPlayerCardArray.count - 2])
playerCard3ImageView.image = UIImage(named: randomPlayerCardArray[randomPlayerCardArray.count - 1])
playerCard4ImageView.image = UIImage(named: randomPlayerCardArray[0])
//將第3張imageView改成array[0] 第4張改成array[1]
}else if changeCardCount == randomPlayerCardArray.count - 2{
playerCard1ImageView.image = UIImage(named: randomPlayerCardArray[randomPlayerCardArray.count - 2])
playerCard2ImageView.image = UIImage(named: randomPlayerCardArray[randomPlayerCardArray.count - 1])
playerCard3ImageView.image = UIImage(named: randomPlayerCardArray[0])
playerCard4ImageView.image = UIImage(named: randomPlayerCardArray[1])
//將第2張imageView改成array[0] 第3張改成array[1] 第4張改成array[2]
}else if changeCardCount == randomPlayerCardArray.count - 1{
playerCard1ImageView.image = UIImage(named: randomPlayerCardArray[randomPlayerCardArray.count - 1])
playerCard2ImageView.image = UIImage(named: randomPlayerCardArray[0])
playerCard3ImageView.image = UIImage(named: randomPlayerCardArray[1])
playerCard4ImageView.image = UIImage(named: randomPlayerCardArray[2])
//將第1張imageView改成array[0] 第2張改成array[1] 第3張改成array[2] 變回初始才有重複輪盤效果
}else if changeCardCount == randomPlayerCardArray.count {
changeCardCount = 0
rounds -= 1
initialCard()
}else{
//依序放入array球員卡imageView
playerCard1ImageView.image = UIImage(named: randomPlayerCardArray[changeCardCount])
playerCard2ImageView.image = UIImage(named: randomPlayerCardArray[changeCardCount + 1])
playerCard3ImageView.image = UIImage(named: randomPlayerCardArray[changeCardCount + 2])
playerCard4ImageView.image = UIImage(named: randomPlayerCardArray[changeCardCount + 3])
}

}else{
//scrollView每次滾動的距離
scrollDistance += 55
randomCardScrollView.contentOffset.y = CGFloat(scrollDistance)
}

//三次的滾動速度漸漸變緩
switch rounds{
case 0:
timer?.invalidate()
case 1:
timer?.invalidate()
scrollSpeed = 0.2
autoScroll()
case 2:
timer?.invalidate()
scrollSpeed = 0.1
autoScroll()
default:
break
}

//跑完滾動次數 顯示抽到的球員卡
if rounds == 0 {
let playerSeconds = player.currentTime().seconds
player.pause()
if let controller = storyboard?.instantiateViewController(withIdentifier: "AlertView") as? AlertViewController{
controller.playerSeconds = playerSeconds
controller.userName = userName
controller.playerCardArray = randomPlayerCardArray
present(controller, animated: true)
}
}
}

建立struct PlayerCard 球員卡資料字典

加入篩選後的球員卡

改進後寫法:

    func filterPlayerCards() -> [String] {
let filteredCards = playerCards.filter { card in
switch positionIndex {
case 1:
if !card.position.contains("內野") { return false }
case 2:
if !card.position.contains("外野") { return false }
default:
break
}

switch leagueIndex {
case 1:
if card.league != "AL" && card.league != "NL" { return false }
case 2:
if card.league == "AL" || card.league == "NL" { return false }
default:
break
}

switch Int(hitValue) {
case ..<10:
if card.hitType != "打擊型" { return false }
case 20...:
if card.hitType != "力量型" { return false }
default:
break
}

switch month {
case 1, 2, 3:
if card.birthday > 3 { return false }
case 4, 5, 6:
if card.birthday < 4 || card.birthday > 6 { return false }
case 7, 8, 9:
if card.birthday < 7 || card.birthday > 9 { return false }
case 10, 11, 12:
if card.birthday < 10 || card.birthday > 12 { return false }
default:
break
}

return true
}
return filteredCards.map { $0.imageName }
}

func addPlayerCard() {
playerCardArray = filterPlayerCards()
}

原本寫法:

    
var month: Int!
var hitValue: Float!
var positionIndex: Int!
var leagueIndex: Int!
var playerCardArray = [String]()

func addPlayerCard(){
//先篩選球員卡守備位置
switch positionIndex {
case 1:
for i in 0..<playerCards.count {
//篩選MLB內野球員卡
if leagueIndex == 1 {
if playerCards[i].position.contains("內野"), playerCards[i].league == "AL" || playerCards[i].league == "NL" {
playerCardArray.append(playerCards[i].imageName)
}
//篩選台灣內野球員卡
} else if leagueIndex == 2 {
if playerCards[i].position.contains("內野"), playerCards[i].league != "AL", playerCards[i].league != "NL" {
playerCardArray.append(playerCards[i].imageName)
}
//篩選打擊型內野球員卡
} else if hitValue < 10 {
if playerCards[i].position.contains("內野"), playerCards[i].hitType == "打擊型" {
playerCardArray.append(playerCards[i].imageName)
}
//篩選力量型內野球員卡
} else if hitValue > 20{
if playerCards[i].position.contains("內野"), playerCards[i].hitType == "力量型" {
playerCardArray.append(playerCards[i].imageName)
}
//只篩選內野球員卡
}else{
if playerCards[i].position.contains("內野") {
playerCardArray.append(playerCards[i].imageName)
}
}
}
case 2:
for i in 0..<playerCards.count {
//篩選MLB外野球員卡
if leagueIndex == 1 {
if playerCards[i].position.contains("外野"), playerCards[i].league == "AL" || playerCards[i].league == "NL" {
playerCardArray.append(playerCards[i].imageName)
}
//篩選台灣外野球員卡
} else if leagueIndex == 2 {
if playerCards[i].position.contains("外野"), playerCards[i].league != "AL", playerCards[i].league != "NL" {
playerCardArray.append(playerCards[i].imageName)
}
//篩選打擊型外野球員卡
} else if hitValue < 10 {
if playerCards[i].position.contains("外野"), playerCards[i].hitType == "打擊型" {
playerCardArray.append(playerCards[i].imageName)
}
//篩選力量型外野球員卡
} else if hitValue > 20{
if playerCards[i].position.contains("外野"), playerCards[i].hitType == "力量型" {
playerCardArray.append(playerCards[i].imageName)
}
//只篩選外野球員卡
}else{
if playerCards[i].position.contains("外野") {
playerCardArray.append(playerCards[i].imageName)
}
}
}
default:
//剩下篩選球員卡聯盟
switch leagueIndex {
case 1:
for i in 0..<playerCards.count {
//篩選MLB打擊型球員卡
if hitValue < 10{
if playerCards[i].league == "AL" || playerCards[i].league == "NL", playerCards[i].hitType == "打擊型"{
playerCardArray.append(playerCards[i].imageName)
}
//篩選MLB力量型球員卡
} else if hitValue > 20 {
if playerCards[i].league == "AL" || playerCards[i].league == "NL", playerCards[i].hitType == "力量型"{
playerCardArray.append(playerCards[i].imageName)
}
//篩選MLB球員卡
} else {
if playerCards[i].league == "AL" || playerCards[i].league == "NL" {
playerCardArray.append(playerCards[i].imageName)
}
}
}
case 2:
for i in 0..<playerCards.count {
//篩選台灣打擊型球員卡
if hitValue < 10{
if playerCards[i].league != "AL", playerCards[i].league != "NL", playerCards[i].hitType == "打擊型"{
playerCardArray.append(playerCards[i].imageName)
}
//篩選台灣力量型球員卡
} else if hitValue > 20 {
if playerCards[i].league != "AL", playerCards[i].league != "NL", playerCards[i].hitType == "力量型"{
playerCardArray.append(playerCards[i].imageName)
}
//篩選台灣球員卡
} else {
if playerCards[i].league != "AL", playerCards[i].league != "NL" {
playerCardArray.append(playerCards[i].imageName)
}
}
}
default:
switch Int(hitValue){
//篩選打擊型球員卡
case ..<10:
for i in 0..<playerCards.count {
if playerCards[i].hitType == "打擊型" {
playerCardArray.append(playerCards[i].imageName)
}
}
//篩選力量型球員卡
case 20...:
for i in 0..<playerCards.count {
if playerCards[i].hitType == "力量型" {
playerCardArray.append(playerCards[i].imageName)
}
}

default:
//無篩選條件
for i in 0..<playerCards.count {
playerCardArray.append(playerCards[i].imageName)
}
}
}

}

//篩選球員卡生日月份
switch month {
case 1, 2 ,3:
for i in 0...playerCards.count - 1 {
if playerCards[i].birthday >= 1 ,playerCards[i].birthday <= 3 {
playerCardArray.append(playerCards[i].imageName)
}
}
case 4, 5 ,6:
for i in 0...playerCards.count - 1 {
if playerCards[i].birthday >= 4 ,playerCards[i].birthday <= 6 {
playerCardArray.append(playerCards[i].imageName)
}
}
case 7, 8 ,9:
for i in 0...playerCards.count - 1 {
if playerCards[i].birthday >= 7 ,playerCards[i].birthday <= 9 {
playerCardArray.append(playerCards[i].imageName)
}
}
case 10, 11 ,12:
for i in 0...playerCards.count - 1{
if playerCards[i].birthday >= 10 ,playerCards[i].birthday <= 12 {
playerCardArray.append(playerCards[i].imageName)
}
}
default:
break
}
}

篩選後洗牌功能

    var randomPlayerCardArray = [String]()

func shuffleCard(){
//超過10張再篩選(節省拉霸轉盤時間)
if playerCardArray.count > 10{
playerCardArray = playerCardArray.shuffled()
for i in 0...10{
randomPlayerCardArray.append(playerCardArray[i])
}
}else{
randomPlayerCardArray = playerCardArray.shuffled()
}

}

回到上一頁重新抽卡

    @IBAction func backSetPage(_ sender: Any) {

if let controller = storyboard?.instantiateViewController(withIdentifier: "SetCardView"){
timer?.invalidate()
present(controller, animated: true)
}
}

AlertViewController顯示獲得的球員卡

恭喜中獎!!
import UIKit

class AlertViewController: UIViewController {

var userName: String?
var playerCardArray: [String]?
var playerSeconds: Double!
let player = AVPlayer()

@IBOutlet weak var alertLabel: UILabel!
@IBOutlet weak var playerCardView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()

if let playerCardArray = self.playerCardArray{
playerCardView.image = UIImage(named: playerCardArray[1])
}
if let userName = self.userName{
alertLabel.text = "恭喜\(userName)獲得球員卡"
} else {
alertLabel.text = "恭喜您獲得球員卡"
}


}

--

--