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