LoginSignup
53
57

More than 5 years have passed since last update.

UITableViewをスクロールしたときに良い感じにNavigationBarを非表示にしたい

Last updated at Posted at 2015-08-09

夏なのでSwiftの勉強がてらに今風なTableViewを作りたい!

やりたいこと

InstagramやFacebookのiOSアプリみたいに、画面を下方向にスクロールさせた時だけNavigationBarを非表示にしたい(ニュッと上に消えるやつ)

簡単では?

これだけで、スワイプのときにNavigationBarを非表示にすることはできるが…

navigationController?.hidesBarsOnSwipe = true

課題

  • 表示コンテンツが画面内に全て収まってる場合でもスワイプするとNavigationBarが非表示になるのがいけてない
  • 例えばcellが1個だけのときでもスワイプするとNavigationBarが非表示になる

解決方法

  • 表示コンテンツのheightの合計 >= 画面サイズのheight のときだけhidesBarsOnSwipeをtrueにする

やってみる

  • cellのheightは固定、sectionなし、header、footerなしのシンプルな構成でやってみる
  • 表示コンテンツのheightは、tableView.contentSize.height + NavigationBarのheight + StatusBarのheight で計算

1. 画面サイズ取得

  • 画面表示のheightを viewWillAppearで取得
ScrollingHidesNavBarController.swift
import UIKit

class ScrollingHidesNavBarController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var addBtn: UIBarButtonItem!
  
    private var list = ["0"] // tableview data 
    private let cellHeight = 100.0
    private var screenHeight = 0.0

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        // get frame height
        screenHeight = Double(self.view.frame.height)

        // reload tableview
        tableView.reloadData()
    }

    // UITableViewのDelegateなど
    // ...
}

2. 表示コンテンツのheightの合計を計算し、navigationController.hidesBarsOnSwipeを設定するメソッド

ScrollingHidesNavBarController.swift
    func setNavBarVisibility() {

        if let navbarHeight = self.navigationController?.navigationBar.frame.size.height {
            // 表示コンテンツの高さを計算
            let contentsHeight = Double(tableView.contentSize.height) +
                                 Double(navbarHeight) +
                                 Double(UIApplication.sharedApplication().statusBarFrame.height)

            if contentsHeight >= screenHeight {
                // 表示コンテンツの高さが画面サイズの高さ以上ならhidesBarsOnSwipeにtrueをセット
                navigationController?.hidesBarsOnSwipe = true
            } else {
                navigationController?.hidesBarsOnSwipe = false
            }
        }
    }

3. cellが追加/削除される毎に2のメソッドを実行

ScrollingHidesNavBarController.swift
class ScrollingHidesNavBarController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    // MARK: - IBOutlet
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var addBtn: UIBarButtonItem!

    // MARK: - IBAction
    @IBAction func addBtnTapped(sender: UIBarButtonItem) {
         // わかりにくいけど、ここでtableViewのデータを追加
        let newElement = String(cellCnt)
        list.append(newElement)

        // tableView.reloadDataしたあとにnavigationController.hidesBarsOnSwipeを設定
        tableView.reloadData()
        setNavBarVisibility()
    }

    // 略
    // ...

    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

         // スワイプでcellを削除
        if editingStyle == .Delete {
            list.removeAtIndex(indexPath.row)

            // ここでtableView.reloadDataしたあとにnavigationController.hidesBarsOnSwipeを設定
            tableView.reloadData()
            setNavBarVisibility()
        }
    }

できた?

  • 一見できたように見えるが、NavigationBarが非表示の状態で、セルを削除した結果hidesBarsOnSwipeがfalseになると、NavigationBarが戻ってこない
  • hidesBarsOnSwipeをfalseにする前にnavigationController?.navigationBarHiddenをfalseにしてNavigationBarを強引に表示すると大丈夫

4. hidesBarsOnSwipeをfalseにするときは、NavigationBarを強制的に表示

ScrollingHidesNavBarController.swift
    func setNavBarVisibility() {
        if let navbarHeight = self.navigationController?.navigationBar.frame.size.height {
            // 表示コンテンツの高さを計算
            let contentsHeight = Double(tableView.contentSize.height) +
                                 Double(navbarHeight) +
                                 Double(UIApplication.sharedApplication().statusBarFrame.height)

            if contentsHeight >= screenHeight {
                // 表示コンテンツの高さが画面サイズの高さ以上ならhidesBarsOnSwipeにtrueをセット
                navigationController?.hidesBarsOnSwipe = true
            } else {
                // NavigationBar非表示の際に hidesBarsOnSwipe = false がセットされるとNavigationBarがずっと非表示になるので
                // 強制的にNavigationBarを表示しておく
                navigationController?.navigationBarHidden = false
                navigationController?.hidesBarsOnSwipe = false
            }
        }
    }

まとめ

とりあえずこれで、

  • TableViewを下にスクロールすると、NavigationBar非表示
  • 表示するコンテンツが画面サイズに収まってるときはNavigationBarを常に表示

ができるようになった気がする。

課題

  • とりあえずtableView.reloadData()呼んどけ、みたいになってるけどこれでいいのか…

ソースコード

とりあえず全体がわかるように貼っておきます。

ScrollingHidesNavBarController.swift
import UIKit


class ScrollingHidesNavBarController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    // MARK: - IBOutlet
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var addBtn: UIBarButtonItem!

    // MARK: - IBAction
    @IBAction func addBtnTapped(sender: UIBarButtonItem) {
        let newElement = String(cellCnt)
        list.append(newElement)

        // reload tableView and set visibility of navbar
        tableView.reloadData()
        setNavBarVisibility()
    }

    // MARK: - Properties
    private var list = ["0"]
    private let cellHeight = 100.0
    private var screenHeight = 0.0
    private var cellCnt = 0

    // MARK:- Functions
    func setNavBarVisibility() {
        if let navbarHeight = self.navigationController?.navigationBar.frame.size.height {
            // get height of contents
            let contentsHeight = Double(tableView.contentSize.height) +
                                 Double(navbarHeight) +
                                 Double(UIApplication.sharedApplication().statusBarFrame.height)

            if contentsHeight >= screenHeight {
                // set hidesBarsOnSwipe true if height of contents is equal to or higher than frame height
                navigationController?.hidesBarsOnSwipe = true
            } else {
                // show NavBar before setting hidesBarsOnSwipe false
                // if NavBar is hidden and hidesBarsOnSwipe is set false, NavBar is invisible
                navigationController?.navigationBarHidden = false
                navigationController?.hidesBarsOnSwipe = false
            }
        }
    }


    // MARK: - VC Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()

        // delete separators of vacant cell
        tableView.tableFooterView = UIView()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        // get frame height
        screenHeight = Double(self.view.frame.height)

        // reload tableView and set visibility of navbar
        tableView.reloadData()
        setNavBarVisibility()
    }


    // MARK: - didReceiveMemoryWarning
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    // MARK: - UITableViewDelegate
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        // set height of cell
        return CGFloat(cellHeight)
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        // deselect the cell selected
        tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }

    func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // set cells removable
        return true
    }

    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        if editingStyle == .Delete {
            list.removeAtIndex(indexPath.row)

            // reload tableView and set visibility of navbar
            tableView.reloadData()
            setNavBarVisibility()
        }
    }


    // MARK: - UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // set the number of cell
        cellCnt = list.count
        return cellCnt
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        if indexPath.section == 0 {
            let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell

            cell.textLabel?.text = list[indexPath.row]
            return cell
        }

        return UITableViewCell()
    }
}
53
57
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
53
57