Material Components for iOS のFlexibleHeaderを使ってみた


Material Components for iOS

Material ComponentsはGoogleが提供している、Material Designの実装を助けてくれるコンポーネントライブラリです。
詳しくは、次のページを読みましょう
https://github.com/material-components/material-components-ios

FlexibleHeaderとは

Google純正アプリの詳細画面などでみられる、スクロールに合わせヘッダー部分の高さが動的に変わるデザインに用いられています。

Flexible header
https://material.io/develop/ios/components/flexible-headers/

Swifterの方は、次のShrineというDemoアプリを参考にするのがおすすめです。
https://github.com/material-components/material-components-ios/tree/develop/demos/Shrine

StoryBoardを使って実装して見る

手順は次のようになります。

  • 画面全体のContainerとなるViewContorllerを作る
    • MDCFlexibleHeaderContainerViewControllerを継承した、UIViewContorllerを作る
  • ContentとなるViewContorllerを作る
    • Headerを除いた部分、TableViewやCollectionViewなどScrollViewを実装したUIViewContorllerを作る
  • HeaderとなるViewを作る

画面全体のContainerとなるViewContorllerを作る

MDCFlexibleHeaderContainerViewControllerを継承したViewControllerを作ります。
StoryBoardから生成することを想定しているので、viewDidLoad()で初期セットを行なっています。getContentViewController()は後ほど作成する、「ContentとなるViewContorller」です。

MDCFlexibleHeaderContainerViewControllerのプロパティ:contentViewControllerにセットします。

class FlexibleHeaderViewController: MDCFlexibleHeaderContainerViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Content部分のViewControllerをセット
        let contentVC: ContentViewController = getContentViewController()
        self.contentViewController = contentVC
        // Header部分のViewを制御するため、headerViewControllerをContentにセット
        contentVC.headerViewController = self.headerViewController
        contentVC.setupHeaderView()
    }
}

ContentとなるViewContorllerを作る

ContentとなるViewControllerは、HeaderのViewとそれを制御するためのMDCFlexibleHeaderViewControllerを参照できるようにします。

class ContentViewController: UITableViewController {
    var headerViewController: MDCFlexibleHeaderViewController?
    var headerContentView: HeaderView =  HeaderView.instantiateFromNib()
}

Headerのスクロールを連動させるために、スクロールのDelegateであるscrollViewDidScrollをつなげます。

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        headerViewController!.scrollViewDidScroll(scrollView)
    }

今回の例では「画面全体のContainerとなるViewController」からコールしていますが、Header部分のViewのセットです。HeaderのViewのセットと、その内容となるViewをセットしています。

    func setupHeaderView() {
        let headerView = headerViewController!.headerView
        // ヘッダー部分のサイズセット
        headerView.trackingScrollView = self.tableView
        headerView.maximumHeight = 200
        headerView.minimumHeight = 72
        headerView.minMaxHeightIncludesSafeArea = false
        headerView.backgroundColor = UIColor.blue
        headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        // ヘッダー部分のコンテンツセット
        headerContentView.frame = (headerView.bounds)
        headerContentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        headerView.addSubview(headerContentView)
    }

HeaderとなるViewを作る

Header部分に表示するViewを作成しましょう。

デメリット

次の公式ページにも説明がありますが、UINavigationControllerとの併用する場合、NavigationBarを自前で制御しないといけません。

スクロール操作できるスペースにも注意が必要です。Header部分を縦スクロールしても動作しません。この辺り、TableViewのみで実装した場合と操作感が変わってしまう恐れがあります。