#16 利用 page control,segmented control,button & gesture 更換內容-影集介紹

前言:早上時告訴自己中午前要弄好,中午時告訴自己晚上前一定要好,然後就隔天了..

學習目標:
1. 熟悉page control,segmented control,button & gesture UI元件
2. 利用segmented control控制scrollView滑動
3. 使用AVPlayerViewController播放影片

成果如下:

安排畫面結構:

先將IBOutlet拉進程式碼

建立資料Array:

let dramas = [
[
Drama(dramaName: "First Love 初戀", intro: "年輕、自由、瘋狂墜入愛河。對於青少年時期的他們來說,世界是能盡情揮灑的舞台。然而成年之後,他們的生活變得黯淡無光,彷彿缺了非常重要的一塊。", releaseDate: "2022年", trial: "初戀預告片"),
Drama(dramaName: "我家的故事", intro: "隸屬於弱小摔角團體的職業摔跤手觀山壽一,在能樂師同時也是人類國寶的父親病危之際,壽一回到了20多年沒聯絡的老家,圍繞看護和遺產繼承問題,與家人和神秘的女性家護展開了鬥爭..", releaseDate: "2021年", trial: "我家的故事預告片"),
Drama(dramaName: "相撲聖域", intro: "個性強悍又拼命的孩子成了相撲選手,他狂妄的態度吸引到一群粉絲,卻也惹毛了講究傳統的業界。", releaseDate: "2023年", trial: "相撲聖域預告片"),
Drama(dramaName: "AV帝王", intro: "故事講述成人影片(AV)導演村西透的起伏人生,在日本繁榮的1980年代,他徹底改變了日本成人影片產業。", releaseDate: "2021年", trial: "AV帝王預告片"),
Drama(dramaName: "今際之國的闖關者", intro: "改編自同名小說的劇集,講述了一群人穿越到異世界的故事,他們必須通過各種關卡才能生存下去。", releaseDate: "2020年", trial: "今際之國的闖關者預告片")
],

[
Drama(dramaName: "模仿犯", intro: "故事背景設定在1990年代的台灣,描述檢察官郭曉其面臨一宗連環謀殺案的調查。。", releaseDate: "2023年", trial: "模仿犯預告片"),
Drama(dramaName: "華燈初上", intro: " 羅雨儂和蘇慶儀的故事在她們還是女孩的時候 就已經開始了。生長背景與個性天差地別的兩人,命運卻將她們緊緊綁在一起 她們合力頂下一間在林森北路條通的酒店 「 光 」。 原本以為這家店將會承載她們人生最華美的燦爛時光 無奈一個叫江瀚的男子出現,打亂了她們原本的生命軌跡 ..", releaseDate: "2021年", trial: "華燈初上預告片"),
Drama(dramaName: "她和她的她", intro: "描述一名在職場上很成功的女子在車禍醒來後,發現世界變得不一樣,這一切又似乎與她國中老師遭殺害的事件有關。", releaseDate: "2022年", trial: "她和她的她預告片"),
Drama(dramaName: "茶金", intro: "以1950年代為背景,述說新竹北埔的客家茶產業,如何在爾虞我詐的商場中,面對強大競爭起落浮沉,靠著茶業創造經濟奇蹟為題材。。", releaseDate: "2021年", trial: "茶金預告片"),
Drama(dramaName: "比悲傷更悲傷的故事", intro: "講述一名患有絕症的男子打算幫助心愛之人找到長久伴侶。", releaseDate: "2021年", trial: "比悲傷更悲傷的故事預告片")
]
]

建立UIScrollView物件:

//建立scrollView物件,建在全域是因為很多function會用到
var introScrollView = UIScrollView(frame: CGRect(x: 24, y: 674, width: 344, height: 144))

override func viewDidLoad() {
super.viewDidLoad()
introScrollView.contentSize = CGSize(width: introScrollView.frame.width*2, height: introScrollView.frame.height)
introScrollView.isPagingEnabled = true
view.addSubview(introScrollView)
}
}

記得要使用 .contentSize 來告訴scrollView可以轉動的範圍有多少。因為這裡只有textView與Button兩個物件,所以可轉動size的寬度就是兩個物件的寬度;可轉動高度就跟introScrollView的高度一樣。

(width: introScrollView.frame.width*2, height: introScrollView.frame.height)

建立呈現簡介的UITextView物件:

var introTextView:UITextView!

fileprivate func setIntroTextView() {
introTextView = UITextView()
//x: 0, y: 0這樣子在加入scrolView後,就會變到最前面
introTextView.frame = CGRect(x: 0, y: 0, width: introScrollView.frame.width, height: introScrollView.frame.height)
introTextView.font = UIFont.boldSystemFont(ofSize: 20)
introTextView.textColor = .white
introTextView.backgroundColor = .clear
introTextView.isPagingEnabled = false
introTextView.isEditable = false
//將introTextView加入introScrollView
introScrollView.addSubview(introTextView)
}
introTextView建立後的位置

建立後要把它放進ScrollView裡,因為要把簡介的部分放左邊,如下圖:

捲動示意圖

所以只要把 introTextViewxy 都設成0,就可以正確對齊ScrollView。

 introTextView.frame = CGRect(x: 0, y: 0, width: introScrollView.frame.width, height: introScrollView.frame.height)

建立播放預告片的UIButton與AVPlayerViewController物件:

//建立播放預告片的按鈕物件,建全域是因為很多function會用到
var playTrialButton = UIButton(type: .system)
func setTrialButton(){
//加入播放的圖片
let playSymbol = UIImageView(frame: CGRect(x: introScrollView.frame.width/2-25, y: introScrollView.frame.height/2-25, width: 50, height: 50))
playSymbol.image = UIImage(systemName: "play.circle")
playSymbol.tintColor = .white
playTrialButton.addSubview(playSymbol)

//設定按鈕內容
var config = UIButton.Configuration.plain()
//button的image的設定寫在其他function
config.background.image = UIImage(named: "")
config.background.imageContentMode = .scaleAspectFill
playTrialButton.configuration = config
playTrialButton.frame = introTextView.frame.offsetBy(dx: introScrollView.frame.width, dy: 0)
playTrialButton.backgroundColor = .clear
playTrialButton.addTarget(self, action: #selector(clickButton), for: .touchUpInside)
introScrollView.addSubview(playTrialButton)
}

//按鈕觸發後的function
@objc func clickButton(){
let currentTypeOfDrama = dramas[dramaSegmentControl.selectedSegmentIndex]
let url = Bundle.main.url(forResource: currentTypeOfDrama[index].dramaName, withExtension: "mp4")
let vedioPlayer = AVPlayer(url: url!)
vedioPlayerController.player = vedioPlayer
present(vedioPlayerController, animated: true) {
self.vedioPlayerController.player!.play()
}
}

由於要把Button放在TextView的下一個位置,因此設定上以TextView的frame為基準,使用 offsetBy(dx:, dy:) 。位移的距離為scrollView的寬度,如下圖所示:

playTrialButton.frame = introTextView.frame.offsetBy(dx: introScrollView.frame.width, dy: 0)

接著要設定Button的功能,需要使用到 .addTarget(target: , action: , for: )

  • target:觸發後會觸發哪個類別(target)的哪個方法(action)這裡輸入 self ,指的是viewController。
  • action:呼叫的物件要執行的方法,以#selector()來指定。這裡要執行的方法是我定義在viewController裡的function @objc func clickButton(){}
  • forControlEvents:觸發的事件。(這裡則是按下事件。)
playTrialButton.addTarget(self, action: #selector(clickButton), for: .touchUpInside)
introScrollView.addSubview(playTrialButton)

//按鈕觸發後的function
@objc func clickButton(){
let currentTypeOfDrama = dramas[dramaSegmentControl.selectedSegmentIndex]
let url = Bundle.main.url(forResource: currentTypeOfDrama[index].dramaName, withExtension: "mp4")
let vedioPlayer = AVPlayer(url: url!)
vedioPlayerController.player = vedioPlayer
present(vedioPlayerController, animated: true) {
self.vedioPlayerController.player!.play()
}

AVPlayerViewController 建立在全域。

點下按鈕後會呼叫clickButton() 建立影片位置,接著傳入 AVplayer() 物件後,再存入AVPlayerViewController的player屬性裡。最後利用 present 就可以把建立的 AVPlayerViewController 給叫出來。

調整 pageControl樣式:

//MARK: - 設定pageControl的樣式
pageControl.currentPage = index
pageControl.frame.size = CGSize(width: dramaCoverImageViewSize.width, height: pageControl.frame.height)
pageControl.frame = pageControl.frame.offsetBy(dx: view.frame.width/2-pageControl.frame.width/2, dy: dramaCoverImageViewSize.maxY-pageControl.frame.size.height)

調整segment Control樣式:

//MARK: - 設定切換預告片還是簡介模的segment的樣式
//設定被選擇前的字體大小與顏色
introSegmentControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.white,NSAttributedString.Key.font : UIFont.italicSystemFont(ofSize: 17)], for: .normal)
//設定被選擇後的字體大小與顏色
introSegmentControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.white, NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 17)], for: .selected)
introSegmentControl.backgroundColor = .clear
introSegmentControl.selectedSegmentTintColor = .clear

建立切換影片資料的function:

將所需的物件都建立好後,便可以開始著手寫切換影片資料的function。

 fileprivate func dramaChange() {
//目前是台劇還是日劇
let currentTypeOfDrama = dramas[dramaSegmentControl.selectedSegmentIndex]
//目前是台劇或日劇裡的哪一部
let currentDrama = currentTypeOfDrama[index]
//將該部影片的資訊存入元件中,變成畫面上看得到的資訊
dramaCoverImageView.image = UIImage(named: currentTypeOfDrama[index].dramaName)
dramaNameLabel.text = currentTypeOfDrama[index].dramaName
releaseDateLabel.text = currentTypeOfDrama[index].releaseDate
}

只要呼叫這個function,就會讀到切換日劇台劇的segment control的數值,來判斷現在是哪種類型的影集。

再以讀出來的影集類型,去讀index值來判斷現在是哪一部。

讀出哪一部後,就可以把那部劇的的屬性存進畫面元件中,我們就可以看見影集的資訊。

⭐️ 這裡有建立SegmentControl來切換要看簡介還是預告片。判斷時機是用if else條件判斷

紅色框框為控制播影片還是簡介的segmentControl
//切換簡介還是預告片
if introSegmentControl.selectedSegmentIndex == 0{
//currentRect存TextView的寬高資訊
let currentRect = introTextView.frame
introScrollView.scrollRectToVisible(currentRect, animated: true)
//將讀取到的影片介紹傳到introTextView
introTextView.text = currentTypeOfDrama[index].intro
}else{
//currentRect存Button的寬高資訊
let currentRect = playTrialButton.frame
introScrollView.scrollRectToVisible(currentRect, animated: true)
//將讀取到的預告片封面傳入button
playTrialButton.configuration.background.image = UIImage(named: currentTypeOfDrama[index].trial)

}

💡切換邏輯:

SegmentControl為0時, introScrollView會呼叫 .scrollRectToVisible這個method。傳到裡面的 currentRect 表示要滑動到的位置。所以此時滑到的位置就是 introTextView 的frame。

反之,當SegmentControl為1,就會捲動到playTrialButton 的位置。

💡一開始在調整按鈕內容時,不小心打上 playTrialButton.configuration?=config ,則照片會無法顯示,但去掉 ? 後就會正常。

一開始configuration不小心加上?,照片會無法顯示

其他IBAction的程式碼:

 //按下切換日劇台劇的segment contro時會做的事
@IBAction func dramaTypeChange(_ sender: UISegmentedControl) {
//更換影片訊息
dramaChange()
}

//下一部按鈕觸發影片資訊轉換
@IBAction func nextButton(_ sender: UIButton) {
let currentTypeOfDrama = dramas[dramaSegmentControl.selectedSegmentIndex]
index = (index+1) % currentTypeOfDrama.count
//更換影片訊息
dramaChange()
//要把index回傳到pageControl,這樣小圓點才會一起動
pageControl.currentPage = index
}


//上一部按鈕觸發影片資訊轉換
@IBAction func backButton(_ sender: UIButton) {
let currentTypeOfDrama = dramas[dramaSegmentControl.selectedSegmentIndex]
index = (index-1+currentTypeOfDrama.count) % currentTypeOfDrama.count
//更換影片訊息
dramaChange()
//要把index回傳到pageControl,這樣小圓點才會一起動
pageControl.currentPage = index
}


//手動變更小圓點位置,可以觸發影片資訊轉換
@IBAction func pageChangeControl(_ sender: UIPageControl) {
//將目前的移動後的位置回存index
index = sender.currentPage
//再啟動這個function,他會讀到回存後的index,就可以改變影片資訊
dramaChange()
}


@IBAction func introTypeChange(_ sender: UISegmentedControl) {
//更換影片訊息
dramaChange()
}

後記:又是一個原本以為可以早早結束的日常,打完之後眼睛迷濛的程度大概跟傑利鼠一樣。

--

--

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

喜歡彈吉他的 iOS 工程師,所以手指沒有閒下來的一天