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


夏なので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()
    }
}
```