來寫個簡單的音樂播放器(上)

Una
彼得潘的 Swift iOS / Flutter App 開發教室
11 min readJul 25, 2023

這篇拉出畫面及能夠切換歌曲的功能是上集,下集在這裡

這次也是看著螢幕發呆好久,上完課都回憶不太起來自己怎麼寫出課堂範例的,然後坐在計時收費的自修室就會一臉呆滯,想到我的每分鐘都是錢才緩慢擠記憶

Let’s Go!

操作畫面

目標是希望可以滑動歌曲封面切換歌曲,點擊分頁點點及上下首按鈕時,歌曲封面、名稱、歌詞都會隨切換變化,如果使用者切換到歌詞,換歌時也需要切回歌曲封面。

1/建立專案並設計介面

在Storyboard中設計音樂播放器介面,畫面中有一個UILabel顯示歌曲名稱、一個UILabel顯示歌手、一個UISegmentedControl切換專輯封面和歌詞視圖、一個UIImageView顯示專輯封面、一個UIScrollView顯示歌詞、一個UIPageControl顯示當前播放的歌曲,以及三個UIButton控制播放上一首、播放、播放下一首。我是參考Figma的設計圖拉出來的。

🌟特別說明:

  • 圖中會看到imageView跟scrollView元件重疊,這是因為我先把元件拉出來之後會利用程式碼控制哪個元件該消失哪個元件該出現。
  • 其中滑動的手勢會有兩個左右滑動,因此需要設定2個,但我還有歌詞滑動也可以切換,因此再加2個總共4個
歌詞手勢2個,封面照手勢2個

可參考資料:

2/建立PlayerViewController類別

此步驟可以直接看情歌王子的文章來實踐😇

3/@IBOutlet和@IBAction的使用

@IBOutlet用於連結Storyboard中的元件到程式碼中的屬性,讓你可以在程式碼中操作這些元件。(像是web前端document.getElementById("XXX")

@IBAction用於連結Storyboard中的元件到程式碼中的方法,讓你可以在使用者操作元件時執行相應的程式碼。

畫面中相關的@IBOutlet

現在,我們來解釋程式碼中的@IBOutlet@IBAction的使用。

1.@IBOutlet

在這個專案都用了以下這些如上圖對應區塊,這些@IBOutlet連結讓我們可以在程式碼中直接操作Storyboard中的對應元件,例如更改標籤(Label)的文字、設定圖片、顯示/隱藏視圖等等。

@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var singerLabel: UILabel!
@IBOutlet weak var songSegmentedControl: UISegmentedControl!
@IBOutlet weak var songPicImageView: UIImageView!
@IBOutlet weak var lyricsScrollView: UIScrollView!
@IBOutlet weak var lyricsLabel: UILabel!
@IBOutlet weak var songPageControl: UIPageControl!
@IBOutlet weak var playButton: UIButton!
@IBOutlet weak var preButton: UIButton!
@IBOutlet weak var nextButton: UIButton!

2. @IBAction

此專案都用了以下這些方法使用了@IBOutlet,這些@IBAction連結讓我們可以在使用者操作Storyboard中的元件時,觸發相對應的方法,以執行我們想要的程式碼。例如,切換專輯封面和歌詞視圖、切換歌曲、滑動手勢等等。

// 切換專輯封面視圖和歌詞視圖的IBAction
@IBAction func changeShow(_ sender: Any) {
// ...
}

// 切換歌曲的IBAction
@IBAction func changeSongPageControl(_ sender: Any) {
// ...
}

// 滑動手勢事件
@IBAction func onSwiper(_ sender: Any) {
// ...
}

// 上一首按鈕的IBAction
@IBAction func pre(_ sender: Any) {
// ...
}

// 下一首按鈕的IBAction
@IBAction func next(_ sender: Any) {
// ...
}

4/設定連結及操作

選擇每個需要連結的元件,按住Ctrl並拖曳到相對應的屬性上方
  1. 連結@IBOutlet

在Storyboard中,選擇每個需要連結的元件,按住Ctrl並拖曳到相對應的屬性上方,放開滑鼠時選擇連結的類型為Outlet,確定連結完成。

連結的類型為Outlet

2. 連結@IBAction

同樣地,在Storyboard中,選擇每個需要連結的元件,按住Ctrl並拖曳到相對應的方法上方,放開滑鼠時選擇連結的類型為Action,確定連結完成。

連結的類型為Action

程式碼解說:

  • 在viewDidLoad的時候希望在一開始渲染時就能拿到正確渲染第一首歌的資訊,因此有配置一些預設的程式碼:
override func viewDidLoad() {
super.viewDidLoad()

// 設定UIButton的配置
var config = UIButton.Configuration.plain()
// 設定圖像,將圖像顏色設定為白色並調整尺寸為64pt
let resizedImage = UIImage(systemName: "arrowtriangle.right.circle.fill")?.withTintColor(.white, renderingMode: .alwaysOriginal).resized(to: CGSize(width: 64, height: 64))
config.image = resizedImage

// 將配置應用到按鈕
playButton.configuration = config

// pageControl的點點
songPageControl.numberOfPages = songName.count

// 歌詞scrollView先隱藏
lyricsScrollView.isHidden = true
showingLyrics = false

// 動態設定 Label 大小
lyricsLabel.sizeToFit()

// 初始化畫面
changeSong()
}
  • 再來會寫一些共用的方法,當點擊或滑動時就可以呼叫
// 切換至專輯封面視圖
func showSongPicView() {
songPicImageView.isHidden = false
lyricsScrollView.isHidden = true
showingLyrics = false
}

// 切換至歌詞視圖
func showLyricsView() {
songPicImageView.isHidden = true
lyricsScrollView.isHidden = false
showingLyrics = true
}

// 上一首歌曲
func preSong() {
currentIndex = (currentIndex == 0) ? (songName.count - 1) : (currentIndex - 1)
changeSong()
}

// 下一首歌曲
func nextSong() {
currentIndex = (currentIndex == songName.count - 1) ? 0 : (currentIndex + 1)
changeSong()
}

// 更新歌曲資訊及視圖顯示
func changeSong() {
titleLabel.text = songName[currentIndex]
singerLabel.text = singers[currentIndex]
songPicImageView.image = UIImage(named: albumsPic[currentIndex])
songPageControl.currentPage = currentIndex
lyricsLabel.text = "\(songName[currentIndex])歌詞"
lyricsLabel.sizeToFit()
songSegmentedControl.selectedSegmentIndex = 0

// 根據showingLyrics的值,自動切換視圖
showingLyrics = false
if showingLyrics {
showLyricsView()
} else {
showSongPicView()
}
}
  • 最後將@IBAction的邏輯寫好就大功告成了!
// 切換專輯封面視圖和歌詞視圖的IBAction
@IBAction func changeShow(_ sender: Any) {
switch songSegmentedControl.selectedSegmentIndex {
case 0:
showSongPicView()
case 1:
showLyricsView()
default:
break
}
}

// 切換歌曲的IBAction
@IBAction func changeSongPageControl(_ sender: Any) {
currentIndex = songPageControl.currentPage
changeSong()
}

// 滑動手勢事件
@IBAction func onSwiper(_ sender: Any) {
// 判斷滑動方向,並執行對應的上一首或下一首歌曲
if let gestureRecognizer = sender as? UISwipeGestureRecognizer {
if gestureRecognizer.direction == .left {
nextSong()
} else if gestureRecognizer.direction == .right {
preSong()
}
}
}

// 上一首按鈕的IBAction
@IBAction func pre(_ sender: Any) {
preSong()
}

// 下一首按鈕的IBAction
@IBAction func next(_ sender: Any) {
nextSong()
}

附上github(這集可以選分支ui-frame)

--

--