はじめに
UIPageViewController
などを使用して、UITableView
を含んだViewController
を横スワイプで遷移させることは、UITableView
の縦スクロールとジェスチャーがぶつかることがないのでシンプルな形で実装できるかと思います。
しかしながら、これを縦スワイプで遷移させるようにする場合はどのようにすればよいでしょうか?
UITableView
の縦スクロールと遷移させるための縦のスワイプを考える必要が出てきます。それに加えて、UITableView
内でもコンテンツを保持しているので、どのタイミングで遷移させるかも複雑になっていくかと思います。
そこで、それらの実装を手助けしてくれるのがHoverConversionになります。
アニメーションや遷移などをすべて内部で管理してくれます。
上のgifは、Twitterのユーザーをベースにしたクライントアプリのサンプルです。
ルートのページにユーザーのリストが表示されていて、コンテンツのページではそのユーザーのタイムラインが表示されています。
UITableView
の最上部または最下部まだスクロールすると、次のユーザーのタイムラインに遷移します。
使い方
HCNavigationController
、HCRootViewController
、HCPagingViewController
、HCContentViewController
とHCNextHeaderView
を使って実装していきます。
StoryboardまたはXibでのHCNavigationController
とHCRootViewController
を表示
StoryboardまたはXib上にUINavigationController
を設置し、下図のようにクラスとモジュールを設定してください。
そして、rootViewController
をHCRootViewController
を継承したViewControllerにしてください。

import HoverConversion
class HomeViewController: HCRootViewController {
//以下省略
}
コードでのHCNavigationController
とHCRootViewController
を表示
コードで設定する場合は、AppDelegate.swift
で下記のように設定してください。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let homeViewController = HomeViewController() //HCRootViewControllerを継承したViewController
let navigationController = HCNavigationController(rootViewController: homeViewController)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
return true
}
HCNavigationController
とHCRootViewController
をStoryboardかコードで設定することで、まずはユーザーのリストを表示することができます。
HCPagingViewController
を表示
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
でcellがタップされた際に、HCPagingViewController
を表示してみます。
ここでは、ポジションがわかるようHCPagingViewController
にindexPath
を渡してください。
またコンテンツを表示するために、HCPagingViewControllerDataSource
をself
にしてください。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
let viewController = HCPagingViewController(indexPath: indexPath)
viewController.dataSource = self
navigationController?.pushViewController(viewController, animated: true)
}
HCContentViewController
を表示
HCContentViewController
はHCPagingViewController
上で表示されるViewControllerになります。
HCPagingViewControllerDataSource
でindexPath
に合ったHCContentViewController
を返します。
import HoverConversion
class UserTimelineViewController: HCContentViewController {
//以下省略
}
extension HomeViewController: HCPagingViewControllerDataSource {
func pagingViewController(_ viewController: HCPagingViewController, viewControllerFor indexPath: IndexPath) -> HCContentViewController? {
guard 0 <= indexPath.row && indexPath.row < twitterManager.users.count else { return nil }
let vc = UserTimelineViewController() //HCContentViewControllerを継承したViewController
vc.user = twitterManager.users[indexPath.row]
return vc
}
}
func pagingViewController(_ viewController: HCPagingViewController, viewControllerFor indexPath: IndexPath) -> HCContentViewController?
はページングが完了するごとに呼ばれます。
nil
を返すと該当するindexPath
が次の遷移先にになった場合に、最上部(または最下部)までスクロールしても、その方向へのページングはしなくなります。
HCNextHeaderView
を表示
最下部までスクロールした際に現れる、次のViewControllerが何かを表示するためのViewです。
HCPagingViewControllerDataSource
でindexPath
に合ったHCNextHeaderView
を返します。
import HoverConversion
class NextHeaderView: HCNextHeaderView {
//以下省略
}
extension HomeViewController: HCPagingViewControllerDataSource {
func pagingViewController(_ viewController: HCPagingViewController, nextHeaderViewFor indexPath: IndexPath) -> HCNextHeaderView? {
guard 0 <= indexPath.row && indexPath.row < twitterManager.users.count else { return nil }
let view = NextHeaderView() //HCNextHeaderViewを継承したView
view.user = twitterManager.users[indexPath.row]
return view
}
}
nilを渡すとHCNextHeaderView
は表示されず、NavigationView
が表示される状態になります。
ここまでを実装することで、縦のページングに対応したUITableView
を含んだUIViewContoller
を実現することができます。
コンテンツをロードするために、ページングを止めたい
このTwitterクライアントの例であげると、過去のツイートを取得するために最上部(または最下部)までスクロールしたとしても、遷移をさせたくない場合があると思います。
その際には、HCContentViewController
のcanPaging
を使うことで解決します。
-
canPaging[.prev]
・・・前のページに遷移できるかどうか -
canPaging[.next]
・・・次のページに遷移できるかどうか
に任意のBoolを代入することで、ページングの可否を設定できます。
class UserTimelineViewController: HCContentViewController {
override func viewDidLoad() {
super.viewDidLoad()
canPaging[.prev] = false
canPaging[.next] = false
}
}
extension UserTimelineViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: IndexPath) {
let lastTweetsCount = tweets.count
if indexPath.row < 1 {
loadOlderTweets { //過去のツイートを取得するメソッド
self.canPaging[.prev] = lastTweetsCount == self.tweets.count
}
} else if lastTweetsCount - 2 < indexPath.row {
loadNewerTweets { //最新のツイートを取得するメソッド
self.canPaging[.next] = lastTweetsCount == self.tweets.count
}
}
}
}
補足
このように縦のページングに対応することでユーザーのリストに戻った際に、今見ていたViewControllerがどのcellのものだったがわかりにくくなってしまいます。
それを防ぐために
- バックボタンで戻った際には、該当のcellに向かってViewControllerが閉じるアニメーション
- エッジスワイプで戻った際には、該当のcellがハイライトされている状態になるアニメーション
をHoverConversion側で実装しています。
わかりやすい例としては、下のgifのようになります。
最後に
詳しい実装まわりについては、potatotips #33でお話させていただく予定です。