LoginSignup
19
18

More than 5 years have passed since last update.

UITableViewのスクロールが最下部(または最上部)に達したときに、UIViewControllerを縦方向にページングする

Posted at

はじめに

UIPageViewControllerなどを使用して、UITableViewを含んだViewControllerを横スワイプで遷移させることは、UITableViewの縦スクロールとジェスチャーがぶつかることがないのでシンプルな形で実装できるかと思います。
しかしながら、これを縦スワイプで遷移させるようにする場合はどのようにすればよいでしょうか?
UITableViewの縦スクロールと遷移させるための縦のスワイプを考える必要が出てきます。それに加えて、UITableView内でもコンテンツを保持しているので、どのタイミングで遷移させるかも複雑になっていくかと思います。

そこで、それらの実装を手助けしてくれるのがHoverConversionになります。
アニメーションや遷移などをすべて内部で管理してくれます。

上のgifは、Twitterのユーザーをベースにしたクライントアプリのサンプルです。
ルートのページにユーザーのリストが表示されていて、コンテンツのページではそのユーザーのタイムラインが表示されています。
UITableViewの最上部または最下部まだスクロールすると、次のユーザーのタイムラインに遷移します。

使い方

HCNavigationControllerHCRootViewControllerHCPagingViewControllerHCContentViewControllerHCNextHeaderViewを使って実装していきます。

StoryboardまたはXibでのHCNavigationControllerHCRootViewControllerを表示

StoryboardまたはXib上にUINavigationControllerを設置し、下図のようにクラスとモジュールを設定してください。

そして、rootViewControllerHCRootViewControllerを継承したViewControllerにしてください。

スクリーンショット 2016-09-20 9.33.22.png

HomeViewController.swift
import HoverConversion

class HomeViewController: HCRootViewController {
    //以下省略
}

コードでのHCNavigationControllerHCRootViewControllerを表示

コードで設定する場合は、AppDelegate.swiftで下記のように設定してください。

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
}

HCNavigationControllerHCRootViewControllerをStoryboardかコードで設定することで、まずはユーザーのリストを表示することができます。

HCPagingViewControllerを表示

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)でcellがタップされた際に、HCPagingViewControllerを表示してみます。
ここでは、ポジションがわかるようHCPagingViewControllerindexPathを渡してください。
またコンテンツを表示するために、HCPagingViewControllerDataSourceselfにしてください。

HomeViewController.swift
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を表示

HCContentViewControllerHCPagingViewController上で表示されるViewControllerになります。
HCPagingViewControllerDataSourceindexPathに合ったHCContentViewControllerを返します。

UserTimelineViewController.swift
import HoverConversion

class UserTimelineViewController: HCContentViewController {
    //以下省略
}
HomeViewController.swift
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です。

HCPagingViewControllerDataSourceindexPathに合ったHCNextHeaderViewを返します。

.swift
import HoverConversion

class NextHeaderView: HCNextHeaderView {
    //以下省略
}
HomeViewController.swift
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クライアントの例であげると、過去のツイートを取得するために最上部(または最下部)までスクロールしたとしても、遷移をさせたくない場合があると思います。
その際には、HCContentViewControllercanPagingを使うことで解決します。

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

19
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
18