利用 scroll view & container view 水平滑動切換頁面

彼得潘的 iOS App Neverland
10 min readAug 24, 2020

--

我們時常在 iOS App 上見到類似 Instagram 的畫面設計,點選 button 或水平滑動可切換頁面。

有很多方法可實現這樣的畫面,比方透過以下步驟實現。

  • 以 scroll view 實現水平捲動。
  • 在 scroll view 加入水平排列的 stack view,stack view 將裝著每一個分頁。
  • 在 stack view 加入多個 container view,每個 container view 連接的 view controller 負責不同的分頁。

接下來我們將以實現 iTunes 上精彩的電影和音樂為例,示範如何透過 scroll view & container view 水平滑動切換頁面。

加入切換分頁的 segment control

分別顯示電影和音樂頁面。

加入 scroll view

在 segmented control 下加入 scroll view。

勾選 scroll view 的 Paging Enabled。

Auto Layout 條件的設定如下。

在 scroll view 裡加入 horizontal stack view

設定 stack view 上下左右對齊 content layout guide, stack view 的 height 等於 frame layout guide 的 height。

在 stack view 裡加入兩個 container view

設定第一個 container view 的 width 等於 frame layout guide 的 width。

將 container view 預設連接的 view controller 刪除,等下我們會另外將 container view 各自連到顯示電影和音樂列表的 view controller。

結果

設定 stack view 的 Alignment & Distribution

Alignment 設為 Fill,Distribution 設為 Fill Equally

加入呈現電影和音樂頁面的 table view controller,將 container view 連接到 table view controller

從 container view 拉線到 table view controller 時記得要選 Embed。

將 2 個 table view controller 的 cell reuse ID 都設為 cell。

將裝著 container view 的 view controller 類別設為自訂的 HomeViewController(繼承 UIViewController)

將 2 個 table view controller 的類別分別設為自訂的 MovieTableViewController & MusicTableViewController (繼承 UITableViewController)

定義抓取 iTunes 資料的網路程式,在 MovieTableViewController & MusicTableViewController 呼叫抓資料的 function

參考範例的 ItunesService.swift,ItunesFeed.swift,MovieTableViewController & MusicTableViewController。

結果

現在我們的 App 已經可以水平滑動切換電影和音樂的分頁,只是還存在以下 2 個令人不安的缺點,讓我們一個個解決吧。

  • 水平滑動切換分頁時不會更新 segmented control。
  • 點選 segmented control 無法切換分頁。

水平滑動切換分頁時更新 segmented control

  • 設定 scroll view 的 delegate 為 HomeViewController。
  • 連接 segmented control 的 outlet (HomeViewController.swift)。
@IBOutlet weak var segmentedControl: UISegmentedControl!
  • 滑動停止時計算目前所在的分頁位置,更新 segmented control 的 selectedSegmentIndex。(HomeViewController.swift)
extension HomeViewController: UIScrollViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = Int(scrollView.contentOffset.x / scrollView.bounds.width)
segmentedControl.selectedSegmentIndex = index
}
}

點選 segmented control 切換分頁

  • 連接 segmented control 的 action (HomeViewController.swift)。
@IBAction func changePage(_ sender: UISegmentedControl) {
}
  • 連接 scroll view 的 outlet (HomeViewController.swift)。
@IBOutlet weak var scrollView: UIScrollView!
  • 點選 segmented control 時捲動 scroll view (HomeViewController.swift)。
@IBAction func changePage(_ sender: UISegmentedControl) {

let x = CGFloat(sender.selectedSegmentIndex) * scrollView.bounds.width
let offset = CGPoint(x: x, y: 0)
scrollView.setContentOffset(offset, animated: true)

}

結果

現在我們的 segmented control & scroll view 終於能連動了 ~。

One more thing,點選 cell 時顯示照片

當我們在 container view 的 controller 操作時,能不能通知裝著 container view 的 controller 呢 ? 比方在 MovieTableViewController & MusicTableViewController 的 cell 點選時,在 HomeViewController 顯示對應的照片。

當然可以,實現此功能的步驟如下:

  • 在 HomeViewController 加入 image view,outlet 取名為 imageView。
@IBOutlet weak var imageView: UIImageView!
  • 加入抓圖的套件 Kingfisher
  • 點選 cell 時更新圖片。(MovieTableViewController & MusicTableViewController)

當我們在 HomeViewController 上以 container view 容納 MovieTableViewController & MusicTableViewController 時,將產生剪不斷的親子關係。

  • MovieTableViewController & MusicTableViewController 的 parent 是 HomeViewController。
  • HomeViewController 的 children 是 MovieTableViewController & MusicTableViewController。

因此在 MovieTableViewController & MusicTableViewController 的程式裡我們可透過 parent 讀取 HomeViewController。(ps: 在 HomeViewController 也可透過 children 讀取 MovieTableViewController & MusicTableViewController)

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let controller = parent as? HomeViewController
let result = results[indexPath.row]
controller?.imageView.kf.setImage(with: result.artworkUrl100)
}

結果

--

--

彼得潘的 iOS App Neverland

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com