【iOS】UITableViewのtableFooterViewを使用する際のはまりどころ

はじめに

UITableViewを使用する際に繰り返しの要素はsecitoncellを用いて表示することが多いかと思います。 主にこれらがUITableViewのメイン要素となり、各sectionのサマリや補足情報をsectionHeaderViewsectionFooterViewに記載します。 そして、繰り返し要素そのものには直接関係しませんが、繰り返し情報の前提となる条件や全体に対するなんらかの補足情報、 もしくは広告を表示したいとなった場合、UITableView全体のheaderfooterであるtableHeaderView, tableFooterViewを使用することになるかと思います。

ただし、特にtableFooterViewでは独特の癖があり、UIViewControllerchildViewController化しtableFooterViewに追加しようとした際に少々難儀しました。 次回同様のことがあった際に、対応方法を即座に思い出せるよう記事として残します。

環境設定

以下の環境を使用しています。

  • Xcode10.3.0
  • Swift 5.0.1

注意点

footerに設定するView自体のレイアウトではauto layoutを使用しない

tableFooterViewheightを以下のようにautoLayoutを用いて指定すると、tableFooterViewが正常に表示されません。 例えば下記のような書き方をすると、レイアウトが崩れてしまいます。

footerView.translatesAutoresizingMaskIntoConstraints = false
footerView.heightAnchor.constraint(equalToConstant: 50.0)

なお、translatesAutoresizingMaskIntoConstraintsfalseにしていてもconstraintを使用していなければ正常に表示されました。

viewのレイアウトをしてからtableFooterViewに代入する

tableFooterViewに該当のviewを設定してから、footerView内のsubViewのレイアウトを変更しようとすると変更が正常に反映されません。例えば下記のコードのようにtableFooterViewsubviewとしてfooterInnerViewを追加して、footerInnerView.center = footerView.centerしてからtableFooterViewに設定した場合は、正しく中央に配置されますが、tableFooterViewに設定してから先ほどのレイアウトを行うと中央に正しく配置されません。

        footerView.backgroundColor = .black
        footerView.frame.size.height = footerHeight
        footerView.frame.size.width = UIScreen.main.bounds.width
        
        let footerInnerView = UIView(frame: CGRect(x: 0, y: 0, width: 100.0, height: 40.0))
        footerInnerView.backgroundColor = .yellow
        footerView.addSubview(footerInnerView)
        footerInnerView.center = footerView.center
        tableView.tableFooterView = footerView

f:id:Iganin:20190819160808p:plain
レイアウトしてからtableFooterViewとして設定

f:id:Iganin:20190819160838p:plain
tableFooterViewに設定してからレイアウト

ChildViewControllerをfooterViewに設定する際の注意点

child化したUIViewControllertableFooterViewに追加する際にも、同様に追加~レイアウトを行う順番に気をつけないとレイアウト崩れが発生するため注意が必要です。以下のコードではレイアウトがうまくいきますが、tableView.tableFooterView = footerViewの後にviewController.view.frame = footerView.frameを実行するとレイアウトが崩れます。

        // ChildViewController
        viewController.view.backgroundColor = .blue
        addChild(viewController)
        footerView.addSubview(viewController.view)
        viewController.didMove(toParent: self)
        viewController.view.frame = footerView.frame
        
        tableView.tableFooterView = footerView

また、レイアウトを以下のようにautoLayoutを用いて実現しようとするとレイアウトが崩れるため注意が必要です。

        // ChildViewController
        viewController.view.backgroundColor = .blue
        addChild(viewController)
        footerView.addSubview(viewController.view)
        viewController.didMove(toParent: self)
        
        viewController.view.translatesAutoresizingMaskIntoConstraints = false
        viewController.view.topAnchor.constraint(equalTo: footerView.topAnchor)
        viewController.view.leadingAnchor.constraint(equalTo: footerView.leadingAnchor)
        viewController.view.trailingAnchor.constraint(equalTo: footerView.trailingAnchor)
        viewController.view.bottomAnchor.constraint(equalTo: footerView.bottomAnchor)
        
        tableView.tableFooterView = footerView

UIViewControllerchild化は同じ手続きが多いため、以下のようなメソッドを用意している場合があると思います。 この場合、autoLayoutを使用してレイアウトを決定しているためレイアウトが崩れてしまいます。 UIViewControllerchildViewControllerとして追加した際のレイアウトがうまくいかなかった際は原因の一つとして疑ってみてください。

extension UIViewController {
    func addChildViewController(_ child: UIViewController, on containerView: UIView) {
        self.addChild(child)
        containerView.addSubview(child.view)
        child.didMove(toParent: self)
        child.view.translatesAutoresizingMaskIntoConstraints = false
        child.view.topAnchor.constraint(equalTo: containerView.topAnchor)
        child.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor)
        child.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor)
        child.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
    }
}

まとめ

tableFooterViewを使用する際には以下2点に気をつける必要があります。

  1. footerに設定するView自体のレイアウトではauto layoutを使用しない
  2. tableFooterViewにViewを追加する前にレイアウトを済ませておく

参考