UITableViewのリサイズに合わせて一番下のセルを常に一番下に表示する方法

公開日: : iPhoneアプリ開発

20160807-113229.jpg

一番下のセルを表示している状態でUITableViewの下端を上に移動させると一番下のセルが隠れてしまいますが、隠れないように下端を移動させる方法を紹介したいと思います。

もし他に良い方法があればTwitterなどでぜひ教えて下さい。 ⇒ akio@東京アプリ開発講座8/13(@akio0911)さん | Twitter

    

スポンサード リンク

前提と下準備

今回は下記のような画面を作って検証を行いました。

UITableViewのサイズを変更するため、UITableViewControllerではなくUIViewControllerを使用し、UITableViewを1つ載せてあります。

20160807-141457.jpg

    

また、UITableViewの下端の制約に対して操作を行えるよう、以下のようにアウトレットを作成しておきます。

20160807-141743.jpg

    

方法その1 制約アニメーションだけ行う

まずは方法その1。Toggleボタンがタップされるたびに、UITableViewの下端の制約のconstantを変更してアニメーションさせます。

コードは以下のとおり。

@IBAction func pressToggleButton(sender: AnyObject) {
    animation1()
}

let tableViewResizeDy: CGFloat = 200.0

private func animation1() {
    if tableViewBottom.constant == 0.0 {
        tableViewBottom.constant = tableViewResizeDy
    } else {
        tableViewBottom.constant = 0
    }

    UIView.animateWithDuration(0.25) { [weak self] in
        guard let `self` = self else { return }
        self.view.layoutIfNeeded()
    }
}

    

しかしこの方法だと以下のように下の方のセルが隠れてしまいます。ちなみに薄緑の領域はUITableViewの下に敷いてあるViewです。

20160807-141016.jpg

なぜこうなるのかというと、UITableView自体のframe(覗き窓の大きさ)が変わっただけであり、contentOffset(覗き窓に表示したいコンテンツの左上の座標)が変わっていないからでしょう。だから一番上に表示されているセルはRow37のままになっています。

    

方法その2 制約アニメーションしながらscrollToRowAtIndexPath

次は方法その2。制約アニメーションしながらscrollToRowAtIndexPathでスクロールさせます。

コードは以下のとおり。

@IBAction func pressToggleButton(sender: AnyObject) {
    animation2()
}

let tableViewResizeDy: CGFloat = 200.0

/// 制約アニメーションさせながらscrollToRowAtIndexPath
private func animation2() {
    if tableViewBottom.constant == 0.0 {
        tableViewBottom.constant = tableViewResizeDy

        if let lastIndexPath = tableView.indexPathsForVisibleRows?.last {
            tableView.scrollToRowAtIndexPath(lastIndexPath, atScrollPosition: .Bottom, animated: true)
        }
    } else {
        tableViewBottom.constant = 0
    }

    UIView.animateWithDuration(0.25) { [weak self] in
        guard let `self` = self else { return }
        self.view.layoutIfNeeded()
    }
}

    

しかしこの方法でも同じ結果になってしまいます。指定したNSIndexPathを元に、scrollToRowAtIndexPathを呼び出した時点(=制約アニメーション前)のcontentOffsetを使ってスクロールしているからだと思われます。

20160807-141016.jpg

    

方法その3 制約アニメーションした後にscrollToRowAtIndexPath

制約アニメーション前に一番下のセルのNSIndexPathを保持しておき、制約アニメーション終了後にscrollToRowAtIndexPathを使って保持しておいたセルまでスクロールさせます。

コードは以下のとおり。

@IBAction func pressToggleButton(sender: AnyObject) {
    animation3()
}

let tableViewResizeDy: CGFloat = 200.0

/// 制約アニメーションした後にscrollToRowAtIndexPath
private func animation3() {
    if tableViewBottom.constant == 0.0 {
        tableViewBottom.constant = tableViewResizeDy
    } else {
        tableViewBottom.constant = 0
    }

    let lastIndexPath = self.tableView.indexPathsForVisibleRows?.last
    UIView.animateWithDuration(0.25, animations: { [weak self] in
        guard let `self` = self else { return }
        self.view.layoutIfNeeded()
    }, completion: {  [weak self] _ in
        guard let `self` = self else { return }
        if let lastIndexPath = lastIndexPath {
            self.tableView.scrollToRowAtIndexPath(lastIndexPath, atScrollPosition: .Bottom, animated: true)
        }
    })
}

    

最終的には目的の場所までスクロールしてくれますが、いったん下の方のセル群が隠れてしまうので、見た目としてはピョコッとした動作になってしまいます。

20160807-185811.jpg

    

方法その4 制約とcontentOffsetを同時にアニメーションさせる

UITableViewのサイズが変わっても一番下のセルが一番下に表示されていて欲しい = contentOffsetを制約アニメーション分だけずらさなければならないということであり、制約アニメーション終了時のcontentOffsetは制約アニメーション量を使って事前に計算できるので、制約とcontentOffsetを同時にアニメーションさせます。

コードは以下のとおり。

@IBAction func pressToggleButton(sender: AnyObject) {
    animation4()
}

let tableViewResizeDy: CGFloat = 200.0

private func animation4() {
    enum ContentOffsetAnimation {
        case On(dy: CGFloat)
        case Off
    }
    let contentOffsetAnimation: ContentOffsetAnimation

    if tableViewBottom.constant == 0.0 {
        tableViewBottom.constant = tableViewResizeDy
        contentOffsetAnimation = .On(dy: tableViewResizeDy)
    } else {
        tableViewBottom.constant = 0
        contentOffsetAnimation = .Off
    }

    UIView.animateWithDuration(0.25) { [weak self] in
        guard let `self` = self else { return }

        self.view.layoutIfNeeded()

        if case .On(let dy) = contentOffsetAnimation {
            self.tableView.contentOffset.y += dy
        }
    }
}

    

結果は以下のとおり。UITableViewの縮小時・拡大時の双方で、一番下のセルが常に一番下に表示されています。そして動きもスムーズです。

20160807-190641.jpg

20160807-190814.jpg

    

@akio0911はこう思った。

メッセージングアプリなどでスタンプ選択リストなどを表示する際に使える方法だと思います。

ぜひ活用してみてください!

この記事が気に入ったら「いいね!」しよう

follow us in feedly

Feedlyで最新記事を購読

Twitterで更新情報をゲット!

LINEでご感想・ご要望お送りください!
(スマホでLINEを起動 > 友だち追加 > QRコード)

関連記事

I20160217-170931.jpg

2016年2月15日以降、Missing iOS Distribution Signing identity for XXX というエラーが出た場合の解決方法

2016年2月15日以降、AppStoreへの申請やipaファイルのエクスポートで「Missin

記事を読む

I20160513-010707.jpg

Swiftで計算型プロパティをネストさせてみる

Swiftの計算型プロパティ(Computed property、コンピューテッドプロパティ)を

記事を読む

20160508-170745.jpg

SwiftTaskで失敗したタスクによって異なるエラー処理を行う方法

SwiftTaskで複数のタスクを.successでチェーンさせ、失敗したタスクによって

記事を読む

I20160228-162014.jpg

詳解 Swift 改訂版のKindle版が発売。リフローに対応!

今日気付いたんですが、「詳解 Swift 改訂版」のKindle版がいつの間にか発売されていまし

記事を読む

20150410-122409.jpg

複数バージョンのXcodeを共存してMacにインストールする方法

いよいよXcode 6.3が正式にリリースされましたね。 今回は複数バージョンのXco

記事を読む

20141225-223049.jpg

【Xcode6】Auto Layoutで制約のFirst ItemとSecond Itemを逆にする方法

一度追加した制約のFirst ItemとSecond Itemを入れ替える方法について紹介します

記事を読む

20140916-130550

Swiftの列挙型、switch文、網羅性チェックが素晴らしい!

アップルの新プログラミング言語「Swift」をちょっとずついじってるんですが、列挙型とswitch文

記事を読む

I20150722-185359.jpg

【書籍】12のパターンで学ぶ本「iOSアプリ開発 AutoLayout徹底攻略」

     Auto Layoutの使い方について解説されている本「iOSアプリ開発 A

記事を読む

I20150808-104713.jpg

【Xcode】シミュレーターリストの表示がおかしくなった時の解決方法

Xcodeのシミュレーターリストの表示が、上のスクリーンショットのようにおかしくなってしまっ

記事を読む

I20151126-170621.jpg

Swift 2のprintで改行しない方法

Swift 1時代はprintlnで改行あり、printで改行なしでしたが、Swift 2ではp

記事を読む

I20170228-002742.jpg
約3ヶ月で体脂肪率を6.2%も落とせたキッカケについて

(右上の赤枠内がダイエット開始前、左下の赤枠内が3ヶ月後の数値

I20161224-174949.jpg
「季節の野菜を直接配送!季節のスムージー」を買ってみた

「FiNCモール」で、「季節の野菜を直接配送!季節のスムージー」を

I20161002-152537.jpg
【メンズネイル】東京・新宿のネイルサロンでマットネイルしてもらった

2016年7月18日、東京・新宿のメンズOKなネイルサロン「Tot

I20160925-163452.jpg
タブバーアイコン非選択時の色を変更する方法【iOS 10】

UITabBarControllerで、タブバーアイコン非選択時の

I20160924-123726.jpg
iPad Pro 9.7インチ SIMフリーモデルにFREETELのSIMを入れて使ってみた

今までiPad miniで使っていたFREETELのSIM

→もっと見る

PAGE TOP ↑