C38.Page control / Segmented Control / Button/ Swipe Gesture 切換內容
Published in
20 min readMay 2, 2023
成果
- 只要是男人就是該看紅豬!
學習發想
這次就以宮崎駿的動畫為練習方向好了,因為這幾個功能的運用很像在Netflix、Disney+、Uniqlo等有圖片畫面的App很常看到 ◉‿◉
- 感覺這些切換功能的運用幾乎涵蓋了許多功能性的App,像是拍賣、影音或是社交軟體等都會運用!
- 先看了其他人的作品,其實可以發現這些功能,蠻直覺的都可以跟ImageView 或是 TextView 去做搭配運用。
- 同時這幾個功能彼此間也會互相組合運用,藉此讓使用者在操作上更有彈性去做快速切換的行為。
練習目標
因為這次的練習是將這幾個切換頁面的功能整合起來,所以UIImageView、TextView都是使用Ispector Attribute去執行屬性修改,藉此減少程式碼。目的是讓我自己可以更清晰地專注在IBAction、IBOutlet的功能連動運用上面。
- 紅框:Next Button & Prev Button (按鈕頁面切換)
- 黃框:Page Control(小圓點頁面切換)
- 籃框:Segmented Control (欄位切換)
- 紫框:Swipe Gesture(滑動手勢切換頁面)
- function運用:使所有切換功能統整連動
- 補:Long Press Gesture Recognizer(長按亂數切換頁面)
IBOutlet、Array
- movieContentTextView:文本顯示。
- movieSegmentedControl:欄位顯示。
- pageControl:小圓點顯示。
- movieCoverImageView:電影海報畫面。
- movies:設置電影項目的Array,0、1、2 分別為魔女宅急便、紅豬、神隱少女。
- moviesDescription:設置電影文本項目的Array。
index變數
的用途是追蹤目前顯示的電影海報
、電影文本
在movies陣列中的索引。陣列包含了三個字串元素,每個元素都代表一部電影的名稱以及文本。
@IBOutlet weak var movieContentTextView: UITextView!
@IBOutlet weak var movieSegmentedControl: UISegmentedControl!
@IBOutlet weak var pageControl: UIPageControl!
@IBOutlet weak var movieCoverImageView: UIImageView!
// 電影海報項目
let movies = ["魔女宅急便", "紅豬", "神隱少女"]
// 電影文本設置
let moviesDescription = ["琪琪是一個實習魔女。依家族慣例,13歲的琪琪該離開家鄉,到新的城市展開獨立生活,修行成正式的魔女。她用她媽媽的掃帚,帶著她的同伴黑貓吉吉去開展她的新生活。\n有一天,琪琪因為心情鬱悶、對自己的行為感到疑惑時失去了法力。最終琪琪在她的新朋友兼畫家烏露絲拉的幫助下,克服了這些障礙。在一次飛船的意外中,琪琪為了拯救蜻蜓,在千鈞一髮之際恢復了飛行的能力並成功救了他。經過此事,琪琪的義舉獲得眾人的讚許與敬重,讓她重拾信心,繼續在城市裡執行送貨服務。", "第一次世界大戰時期義大利空軍的王牌飛行員波魯克・羅梭中了魔法變成了一頭豬。如今他成了一名賞金獵人,專門在亞得里亞海空域阻擊空中劫匪。劫匪們為了打倒羅梭從美國請來了超級飛行員卡地士。\n卡地士趁羅梭的飛機引擎發生故障之時將其擊落。羅梭把愛機送往老朋友保可洛的工廠修理,在那裡遇到了保可洛的孫女菲奧。菲奧完美地修復了羅梭的座機,和羅梭一起重返藍天。\n而等待他們的卻是那批空中劫匪。為了爭奪菲奧,卡地士向羅梭提出決鬥。在決鬥前夜,羅梭向菲奧講述了自己在一戰中經歷。次日,在大批空中劫匪以及菲奧的注視下,決鬥開始了。最後因與卡地士在空戰中無法分出高下,而改以肉搏拳賽打敗了卡地士,也贏得了榮耀。", "年僅十歲的少女千尋與父母一同驅車前往新家,途中迷路車子來到一道拱門前,千尋與父母下車穿越拱門到達一處荒廢的日式小鎮。千尋心生害怕,但是千尋的父母卻在未經主人同意的情況下,大大方方的取用食物,轉眼間,他們變成了兩頭豬。\n原來千尋與父母來到神靈休憩的小城,所有的食物都是呈現給神靈的供品,千尋的父母為了自己的慾望,取用了不屬於他們的食物。\n千尋在驚嚇之餘遇見了似曾相識的少年哈克,哈克對千尋伸出援手,教導千尋在神靈世界的生存之道。千尋在哈克的指點之下,向開設澡堂湯婆婆毛遂自薦。\n唯利是圖的湯婆婆在拗不過千尋的請託之下,答應讓千尋在她開設的澡堂裡工作,並且將千尋的名字改為「小千」。" ]
// index追蹤陣列
var index = 0
Next Button & Prev Button
- 我一開始是先練習Button的切換,基本上只要把index的陣列運算寫好,後續其他切換功能都很好去套用。
下一頁
- index 變數是用來追蹤目前顯示的電影在 movies 陣列中的位置。每次按下「下一頁」的按鈕時,index 的值會加 1,但如果 index 已經到達了 movies 陣列的最後一個元素,那麼 index 就會被設置為 0,這樣就可以實現循環輪播的效果。
- % 運算符,它的作用是取餘數。 index 等於 2 ,movies.count 的值本身為 3,那麼 (index + 1) % movies.count 的值就是 0,這樣就可以從頭再次開始顯示電影了。
- 我當初想了一下,後來用圖來理解就很好懂惹XD
// 下一頁
@IBAction func goToNextPage(_ sender: Any) {
// Button 下一頁 (movies.count = 3)
index = (index + 1) % movies.count
// movie封面
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
// movie文本內容
movieContentTextView.text = moviesDescription[index]
}
- 印出:index, moviesName
上一頁
- 將
index
值減 1 的目的是顯示前一個電影。例如,如果index
值為 2,表示目前正在顯示電影列表中的第三個電影。如果要顯示前一個電影,則需要將index
值減 1,這樣就可以獲得列表中的第二個電影。 - 當使用
%
運算符時,可以保證結果始終在0
和movies.count-1
的範圍內,這樣就可以實現循環顯示電影的效果。
// 上一頁
@IBAction func goToPreviousPage(_ sender: Any) {
// Button 上一頁 (movies.count = 3)
index = (index + movies.count - 1) % movies.count
// movie封面
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
// movie文本內容
movieContentTextView.text = moviesDescription[index]
}
Page Control (小圓點切換)
- 當使用者滑動到另一個頁面時,Page Control 會自動更新當前頁面索引,從而在 UI 上提供更好的可讀性和使用體驗。可以知道當前頁面數量以及所在位置。
- 點選小圓點只會切換至下一個 / 上一個點,不會直接跳到指定的小圓點。
sender
是對應的UIPageControl
,而currentPage
屬性會傳回目前選取的頁面索引值。- 此行程式碼
index = sender.currentPage
的目的是將index
變數的值設為UIPageControl
目前選取的頁面索引值,以便更新顯示的電影封面和說明文本。
@IBAction func pageControlDidChange(_ sender: UIPageControl) {
// pageControl 當前頁面
index = sender.currentPage
// movie封面
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
// movie文本內容
movieContentTextView.text = moviesDescription[index]
}
Page Control與Button連動
- 除了小圓點本身可以點擊以外。因為此次是要將許多的頁面切換功能放在同一個畫面上,而Page Control本身有小圓點可以看到當前位置。
- 如果再
Button
與Page Control
同時存在,又沒有將其連動,這樣會導致使用Button切換頁面時,小圓點的點並不會跟著跳到當前位置。
- 爲了點選Button時,
小圓點的點
也可以跟著連動,將其pageControl 的 outlet,設置當前頁面為index。 - 並且放置到
下一頁、上一頁
的IBAction,這樣點選Button小圓點也會跟著變化。
// 這邊以下一頁為範例
@IBAction func goToNextPage(_ sender: Any) {
// Button 下一頁 (movies.count = 3)
index = (index + 1) % movies.count
// movie封面
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
// movie文本內容
movieContentTextView.text = moviesDescription[index]
// pageControl 的 outlet 連動
pageControl.currentPage = index
}
Segmented Control
- 每個
Segment
代表一部電影,因此sender.selectedSegmentIndex
是目前選中的電影索引。通過將它賦值給 index,我們將 index 設置為目前選中的電影索引。這樣,我們就可以根據這個索引來顯示目前選中的電影的海報和文本內容。 - 實際上,當使用者點擊選項卡時,UISegmentedControl 會自動更新其
selectedSegmentIndex
屬性。在這個 IBAction 中,我們只是將這個屬性的值賦值給了 index。
@IBAction func segmentedControlValueChanged(_ sender: UISegmentedControl) {
// Segmented Control 當前頁面
index = sender.selectedSegmentIndex
// movie封面
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
// movie文本內容
movieContentTextView.text = moviesDescription[index]
}
Segmented Control 與 Button、Page Control 連動
Segmented Control
也是有欄位顯示當前位置,所以必須將其與其他功能連動。selectedSegmentIndex屬性
表示當前被選中的分段的索引。在程式碼中將 index 賦值給selectedSegmentIndex
時,它會將UISegmentedControl
選中的分段更新索引為 index 的分段,從而將選中的分段與當前顯示的電影對應起來。movieSegmentedControl.selectedSegmentIndex = index
放至其他切換的IBAction中做連動。
@IBAction func goToNextPage(_ sender: Any) {
// Button 下一頁 (movies.count = 3)
index = (index + 1) % movies.count
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
movieContentTextView.text = moviesDescription[index]
// pageControl 的 outlet 連動
pageControl.currentPage = index
// SegmentedControl 的 outlet 連動
movieSegmentedControl.selectedSegmentIndex = index
}
@IBAction func goToPreviousPage(_ sender: Any) {
// Button 上一頁
index = (index + movies.count - 1) % movies.count
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
movieContentTextView.text = moviesDescription[index]
// pageControl 的 outlet 連動
pageControl.currentPage = index
// segmentedControl 的 outlet 連動
movieSegmentedControl.selectedSegmentIndex = index
}
@IBAction func pageControlDidChange(_ sender: UIPageControl) {
// pageControl 當前頁面
index = sender.currentPage
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
movieContentTextView.text = moviesDescription[index]
// segmentedControl 的 outlet 連動
movieSegmentedControl.selectedSegmentIndex = index
}
@IBAction func segmentedControlValueChanged(_ sender: UISegmentedControl) {
// Segmented Control 當前頁面
index = sender.selectedSegmentIndex
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
movieContentTextView.text = moviesDescription[index]
// pageControl 的 outlet 連動
pageControl.currentPage = index
}
function運用
- 後來我發現有幾個程式碼功能重複使用性很高,所以就試著將其整理成一個function,
updateMovieInformation()
。 - 包含
封面、文本
的切換,以及pageControl、segmentedControl
的連動顯示。 - 將這個
function
替代原先密密麻麻的程式碼,同時在測試時也相對方便@@
// 設置一個 function: 關於圖片文本切換
func updateMovieInformation() {
// movie封面
let moviesName = movies[index]
movieCoverImageView.image = UIImage(named: moviesName)
// movie文本內容
movieContentTextView.text = moviesDescription[index]
// pageControl 的 outlet 連動
pageControl.currentPage = index
// segmentedControl 的 outlet 連動
movieSegmentedControl.selectedSegmentIndex = index
}
@IBAction func goToNextPage(_ sender: Any) {
// Button 下一頁 (movies.count = 3)
index = (index + 1) % movies.count
updateMovieInformation()
}
@IBAction func goToPreviousPage(_ sender: Any) {
// Button 上一頁
index = (index + movies.count - 1) % movies.count
updateMovieInformation()
}
@IBAction func pageControlDidChange(_ sender: UIPageControl) {
// pageControl 當前頁面
index = sender.currentPage
updateMovieInformation()
}
@IBAction func segmentedControlValueChanged(_ sender: UISegmentedControl) {
// segmentedControl 當前選項
index = sender.selectedSegmentIndex
updateMovieInformation()
}
}
Swipe Gesture
- 設置左滑、右滑會切換頁面的手勢。(要注意我當初設反XD)
- 將Swipe Gesture Recognizer拉到UIImageView上,屆時滑動照片即可觸發。
- 設置其Swipe滑動方向,以及觸碰手勢。
- 一個Swipe Gesture Recognizer只能辨識一個方向,因此我設置了兩個分為左右。
User Interaction Enabled選項
- 讓 image view 觸碰有反應,藉此可以辨識Swipe手勢。
連結手勢的 IBAction
- 先確定 Swipe Gesture 有與 UIImageView 連動。
- 接著在將 Swipe Gesture 拉至 IBAction 的 function 設定滑動時會觸發的功能。
- 這邊我想要偵測到左滑時顯示下一頁的內容,所以將 Swipe Gesture 連結到NextPageButton的IBAction。
- 也因為NextPageButton的IBAction原先就已經被放置
updateMovieInformation()
,所以在Swipe時這些功能也會觸發。
請ChatGPT補充
- Swipe Gesture與Button的差異性
Long Press Gesture Recognizer
用長按的手勢來搭配先前學到的亂數運用。
- 長按手勢的時間我在Attribute Inspector設置1秒。
- 不過我寫完之後用
print
檢查發現,長按一下螢幕時,它會生成不固定數量
的亂數,並更新電影頁面。
@IBAction func changeImageOnLongPress(_ sender: Any) {
// movies.count 的值為 3,因此要使用..<
let number = Int.random(in: 0..<movies.count)
index = number
updateMovieInformation()
print(index)
}
- 如下圖XD
詢問Peter之後才知道這是跟目前還沒學到的判斷狀態
的運用有關聯,雖然這邊還不了解,但先備註。
- 先將
sender
改為UILongPressGestureRecognizer
,這樣才可以直接存取它的屬性和方法,使得長按事件可以正確地觸發。 sender
是指觸發事件的物件,這邊指的是UILongPressGestureRecognizer
物件。在sender
的state
屬性中,記錄了目前手勢的狀態。當手勢的狀態變為.ended
時,表示手指已經離開螢幕,也就是長按的動作已經完成了。- 因此這段程式碼的意思是當長按手勢完成(也就是手指離開螢幕)時,才會執行裡面的程式碼。
@IBAction func changeImageOnLongPress(_ sender: UILongPressGestureRecognizer) {
// 狀態判斷
if sender.state == .ended {
let number = Int.random(in: 0..<movies.count)
index = number
updateMovieInformation()
print(index)
}
}
- 修改過後:當長按手勢離開螢幕時就會執行程式碼,並且只會產生一個亂數!
GitHub
參考