はじめに
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でお話させていただく予定です。



