Episode 92 — 實作 Table View 基本功能
Published in
16 min readMay 14, 2022
完整操作
製作 Table View
使用技巧
- 客製 cell 顯示表格的內容
- 多個 section
- 使用 view controller,在 view controller 上另外加入 table view
- cell 採用固定高度,cell 裡的元件設定 auto layout 條件
步驟
- Table View 放進 View Controller 中,自定元件設定 constraints 在 cell 裡。
- Style 選擇 Custom,Identifier 取名 songCell
- 建立歌手及歌曲 Structure
struct Song {
var name: String
var album: String
var image: String
var url: URL
var videoURL: URL
}struct Singer {
var name: String
var songs: [Song]
}
- 建立一個歌手實例,裡面排三個歌手、五首歌。
let singers = [
Singer(name: "Lauv", songs: [
Song(name: "I Like Me Better", album: "I Like Me Better-Single", image: "i like me better", url: Bundle.main.url(forResource: "I Like Me Better", withExtension: "mp3")!, videoURL: Bundle.main.url(forResource: "i like me better", withExtension: "mov")!),...]),
Singer(name: "Khalid", songs: [
Song(name: "Silence", album: "", image: "silence", url: Bundle.main.url(forResource: "Silence", withExtension: "mp3")!, videoURL: Bundle.main.url(forResource: "silence", withExtension: "MP4")!),...]),
Singer(name: "Justin Bieber", songs: [
Song(name: "Ghost", album: "", image: "ghost", url: Bundle.main.url(forResource: "Ghost", withExtension: "mp3")!,videoURL: Bundle.main.url(forResource: "ghost", withExtension: "mov")!),...])]
- 將 table view 連到 view controller ,點選 dataSource 及 delegate 以指派 view controller 幫 table view 忙
- 讓 ViewController 遵從 UITableViewDataSource 及 UITableViewDelegate 才能呼叫代理功能或屬性,使用 extension 不讓 ViewController 版面太複雜。
extension ViewController: UITableViewDataSource, UITableViewDelegate{ }
- 這次有三位歌手所以我想做三個 sections,在 numberOfSections 功能裡回傳歌手實例的數量。
func numberOfSections(in tableView: UITableView) -> Int {
return singers.count
}
- 使用參數有 numberOfRowsInSection 的功能顯示歌曲,由歌手實例裡的每位歌手的歌曲數量決定 row 的數量
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return singers[section].songs.count
}
- 新增一個 file 使用 Cocoa Touch File 來創建一個型別為 UITableViewCell 的 class。把 storyboard 上 cell 裡面的自定元件建立 IBOutlet。
class SongTableViewCell: UITableViewCell {
@IBOutlet weak var songNameLabel: UILabel!
@IBOutlet weak var songImageView: UIImageView!
@IBOutlet weak var playButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
} override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated) // Configure the view for the selected state
}}
- 回 ViewController 呼叫參數有 cellForRowAt 且回傳 UITableViewCell 的功能來顯示資料。讓 table view 使用 dequeueReusableCell(withIdentifier:,for:) 方法來重複使用 cell。withIdentifier 使用剛剛命名的 songCell。轉型為剛剛新建立的 SongTableViewCell。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "songCell", for: indexPath) as! SongTableViewCell
return cell
}
- 利用 indexPath 的 section 及 row 將資料給元件。section 用來呼叫歌手,row 用來呼叫歌曲。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "songCell", for: indexPath) as! SongTableViewCell
cell.songImageView.image = UIImage(named: singers[indexPath.section].songs[indexPath.row].image)
cell.songNameLabel.text = singers[indexPath.section].songs[indexPath.row].name
return cell
}
- 呼叫參數有 titleForHeaderInSection 的功能來將歌手名稱顯示在每個 section 的 header 上。
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return singers[section].name
}
- 打開模擬器可以看到三個 section(歌手) 跟各五個 row (歌曲)了
播放歌曲
使用技巧
- 使用 convert(at:, to:) 尋找元件位置
- 維持 cell 元件狀態
- 使用 AVPlayer 播音樂
步驟
- 為了讓按鈕按下去直接播放音樂,先連接成一個 IBAction。必須要知道要播的歌是哪一個歌手跟歌曲,需要得到 section 跟 row。先用 convert 方法得知按到的按鈕是在 Table View 的哪個位置。
@IBAction func playMusic(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
}
- 用剛剛得到的位置後從 table view 呼叫 indexPathForRow(at:) 帶入座標,可以從中得到目前的 section 跟 row。用得到 section、row 取得歌曲並播放。最後把按鈕隱藏起來顯示(已事先排好的)暫停按鈕。
@IBAction func playMusic(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
if let section = tableView.indexPathForRow(at: point)?.section, let row = tableView.indexPathForRow(at: point)?.row {
musicPlayer = AVPlayer(url: singers[section].songs[row].url)
musicPlayer?.play()
sender.isHidden = true
}
}
- 模擬器測試有成功播放歌曲,但詭異的是每次播放後上下滾動,暫停鍵卻消失了。原因是因為每次滾動 cell 都會被更新,所以播放鍵又長回來了。
- 宣告 isPlaying 判斷歌曲是否正在播放、buttonSection 紀錄被按到的按鈕原本的 section、buttonRow 紀錄被按到按鈕原本的 row。
var isPlaying = false
var buttonSection = 0
var buttonRow = 0
- 到 dequeueReusableCell 的方法中加入以下方法判斷。用isPlaying 判斷歌曲是否正在播放,是的話代表剛剛有一個播放件事消失的,我要用目前的 section、row 來判斷在在是不是在剛剛紀錄的 section 跟 row。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "songCell", for: indexPath) as! SongTableViewCell
cell.songImageView.image = UIImage(named: singers[indexPath.section].songs[indexPath.row].image)
cell.songNameLabel.text = singers[indexPath.section].songs[indexPath.row].name
if isPlaying {
if indexPath.section == buttonSection && indexPath.row == buttonRow {
cell.hidePlayButton()
} else {
cell.showPlayButton()
}
} else {
cell.showPlayButton()
}
return cell
}
- 回到播放功能中,isPlaying 變成 true 代表正在播放。把按到的按鈕 section & row 記錄起來,就可以把剛剛 sender.isHidden 改為 tableView.reloadData 就會去重新呼叫 dequeueReusableCell 那個功能了。
@IBAction func playMusic(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
isPlaying = trueif let section = tableView.indexPathForRow(at: point)?.section, let row = tableView.indexPathForRow(at: point)?.row {
musicPlayer = AVPlayer(url: singers[section].songs[row].url)
buttonSection = section
buttonRow = row
tableView.reloadData()
musicPlayer?.play()
}}
- 再跑一次模擬器成功讓按鈕維持在消失狀態了。
播放 MV
使用技巧
- 利用 IBSegueAction點選 cell 後到下一頁顯示詳細資訊
- 使用 AVPlayerViewController 播影片
步驟
- 匯入程式庫
import AVKit
- 從 Library 叫出一個 AV Player View Controller 後,從 cell 連 segue 到那邊。
- 幫這個 segue 取個小名叫 playVideo
- 從 segue 連到 view controller 形成 IBSegueAction
@IBSegueAction func playVideo(_ coder: NSCoder) -> AVPlayerViewController? {
}
- 先建立一個AVPlayerViewController 並傳入參數 code。先回傳這個 controller 消除錯誤訊息。
@IBSegueAction func playVideo(_ coder: NSCoder) -> AVPlayerViewController? {
let controller = AVPlayerViewController(coder: coder)
return controller
}
- 使用 table view 的 indexPathForSelectedRow 屬性取得 section 跟 row
@IBSegueAction func playVideo(_ coder: NSCoder) -> AVPlayerViewController? {
let controller = AVPlayerViewController(coder: coder)
if let section = tableView.indexPathForSelectedRow?.section, let row = tableView.indexPathForSelectedRow?.row {
} else {
print("no section or no row")
}
return controller
}
- 利用 section & row 取得影片來源並且播放
@IBSegueAction func playVideo(_ coder: NSCoder) -> AVPlayerViewController? {
let controller = AVPlayerViewController(coder: coder)
if let section = tableView.indexPathForSelectedRow?.section, let row = tableView.indexPathForSelectedRow?.row {
videoPlayer = AVPlayer(url: singers[section].songs[row].videoURL)
if let musicPlayer = musicPlayer {
musicPlayer.pause()
}
controller?.player = videoPlayer
videoPlayer = nil
controller?.player?.play()
pauseMusic()
} else {
print("no section or no row")
}
return controller
}
- 使用模擬器可以播放影片
完整程式碼
資料來源
View Controller
Song Table View Cell