stack view + scroll view 製作分頁 tutorial (懶得寫程式版)
在開發 iOS App 時,我們時常以表格實現上下捲動的畫面。那如果是水平左右捲動的畫面呢 ? 左右捲動一般而言比較麻煩,往往要搭配程式,使用 scroll view ,collection view 或 page view controller 實現。
但如果只想做個 App 第一次打開時看到的教學介紹頁面,一個四五頁捲動的 tutorial 頁面,其實是可以在 storyboard 利用 stack view,scroll view 和 auto layout,完全不寫程式實現的 !
接下來,我們就以此生必讀的經典 Peter Pan 為例,製作一個瀏覽 Peter Pan 書籍封面的頁面吧。
將 5 張 peter pan 的圖片加到 Assets.xcassets
加入佔滿螢幕的 scroll view
選取 scroll view & view(可利用 cmd 鍵多選),然後設定上下左右對齊。(ps: 如果不想 scroll view 被美麗瀏海檔到,也可以設定 scroll view & Safe Area 上下左右對齊。)
此時將出現紅色錯誤,因為 scroll view 覺得頭很大,他不知道捲動的範圍。
待會我們將設定 auto layout 條件,由 content layout guide 決定捲動的範圍,對 scroll view 如何決定捲動範圍不熟悉的朋友可先參考以下連結。
將顯示 Peter Pan 圖片的 1 個 image view 加到 scroll view
先加 1 個 image view 就好,其它的 image view 等下再加。
為了讓圖片佔滿畫面,Content Mode 記得設為 Aspect Fill。(ps: 若想要圖片維持比例完整顯示,則可設為 Aspect Fit )
將 image view 加到 stack view
設定 stack view
我們將在 stack view 放 5 個 image view,想要每張圖一樣大,然後搭配 scroll view 左右滑動,因此請將 Axis 設為 Horizontal,Alignment 設為 Fill,Distribution 設為 Fill Equally, Spacing 設為 0。
將 stack view 的上下左右間距設為 0
讓 scroll view 被 stack view 填滿,由 stack view 的大小決定 scroll view content layout guide(捲動範圍)的大小。
如下圖所示,請選取 stack view & content layout guide(可按住 cmd 鍵多選),然後設定上下左右對齊的條件讓 stack view 和 content layout guide 的間距為 0。
此時 stack view 的大小將由裡面的 image view 決定,而每個 image view 的大小則由當初圖片的大小決定。
讓圖片的寬高等於螢幕的寬高
設定 image view 和 Frame Layout Guide
Equal Widths & Equal Heights。
為什麼要讓 image view 和 Frame Layout Guide
一樣大呢? 因為它代表 scroll view 本身 frame 的框框,當我們設定圖片跟它一樣大時,由於 scroll view 的大小等於螢幕的大小,圖片的大小也將等於螢幕的大小。
從下圖我們可以更清楚看出 Frame Layout Guide
& Content Layout Guide
的差異,Frame Layout Guide
是比較小的黑色框框,Content Layout Guide
的黃色框框則包含了整個捲動的內容。
Cool,現在第一頁的 peter pan 圖片完美地填滿螢幕,不過因為只有一張圖,所以還不能滑動。
加入其它圖片
利用 cmd + c & cmd + v 複製貼上其它四個 image view,然後將圖片依序改成 peter2,peter3,peter4 & peter5。
stack view 塞了 5 個 image view 後,寬度成為原本一個 image view 寬度的 5 倍。
點選 Content Layout Guide 也可查看 scroll view 捲動範圍的大小。
完整的 Auto Layout 條件截圖畫面如下。
完成以上步驟後,現在我們的 scroll view 已經可以捲動了,甚至不用跑模擬器,在 Interface Builder 也可以捲動。而且當我們點選 stack view 裡的圖片時,它還會自動捲動顯示對應的圖片呢。
讓 scroll view 分頁捲動。
勾選 scroll view 的 Paging Enabled,讓 scroll view 捲動時以它本身的大小為單位分頁,讓 scroll view 捲動停下時都能剛好看到完整的圖片。
之後如果想再增加圖片也很簡單,只要將 image view 加到 stack view 裡,即可多一頁分頁。
範例連結
不過如果要呈現的內容很多的話,建議改從程式搭配 collection view 或 scroll view 的寫法比較省記憶體。剛剛完全從 storyboard 製作的方法一開始就會生成每個頁面的內容,而不是捲動到相關頁面才生成,因此只適合頁面數量固定,而且不會太多頁的情境,比方 App 第一次打開時看到的教學介紹頁面,或是選擇分類時,列出一排十個 icon 選項的情境。
以程式實現水平滑動的分頁效果
剛剛以 storyboard 實現的畫面也可以用程式實現,程式如下。
import UIKit
class ViewController: UIViewController {
private let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.clipsToBounds = true
return scrollView
}()
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
private let imageNames = ["peter1", "peter2", "peter3", "peter4", "peter5"]
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
view.backgroundColor = .systemBackground
view.addSubview(scrollView)
scrollView.addSubview(stackView)
NSLayoutConstraint.activate([
// Constraint 1: scrollView top to view top
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
// Constraint 2: scrollView leading to view leading
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
// Constraint 3: scrollView trailing to view trailing
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// Constraint 4: scrollView bottom to view bottom
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// Constraint 5: stackView top to scrollView contentLayoutGuide top
stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
// Constraint 6: stackView leading to scrollView contentLayoutGuide leading
stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
// Constraint 7: stackView trailing to scrollView contentLayoutGuide trailing
stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
// Constraint 8: stackView bottom to scrollView contentLayoutGuide bottom
stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
])
for (index, imageName) in imageNames.enumerated() {
let imageView = UIImageView(image: UIImage(named: imageName))
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(imageView)
if index == 0 {
// Constraint 9: imageView width equal to scrollView frameLayoutGuide width
imageView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor).isActive = true
// Constraint 10: first imageView height equal to scrollView frameLayoutGuide height
imageView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor).isActive = true
}
}
}
}
進階題
模仿 NETFLIX App,滑動的內容包含圖片跟文字。
提示
stack view 裡裝的成員是 view,代表每個分頁。view 裡裝 image view & label。