Cellなどの高さ計算の処理をViewのstructで行う

Cellなどの高さ計算の処理をViewのstructで行うちょっとしたtipというかメモ的な記事です。最近、これでうまくいっているので自分に刻み付ける目的もあって書きます。

セルに複数行の文字を出力したい場合、セルの高さ計算が必要です。iOS8からself-sizingが加わりましたが、表示時は良いのですが画面に表示するセルが多くなった時に、セルの高さ計算がうまくいかずスクロールがかくつくという問題に直面し、結局計算をしています。

その際の計算処理をどこにどう書くかということです。

僕はMVVMで開発していて、モデルの情報は一旦 ItemViewModel というようなViewModelにもたせます。その段階で表示時の見た目を用意したりできるのですが、そうすると複数の違う見せ方をしたい時に複数のビューの見た目を持つのはやっぱりおかしいです。ViewのことはViewにやらせるのが良いと思って書き換えました。

構造

具体的にいうと下記のような構造になります。まずはシンプルに高さ計算が必要ない場合の例です。ViewModelを渡していますが、これは別に表示情報であればなんでも良いです。

final class UserCell: UITableViewCell {
    struct Layout {
        static func height(vm: UserViewModel) -> CGFloat {
            return 68.0
        }
    }

    ...
}

利用時は、このようになります。

let vm = ...
return UserCell.Layout.height(vm)

高さ計算が必要な場合は下記のようになります。

<br />final class ItemCell: UITableViewCell {
    struct Layout {
        static let LeftAreaMargin: CGFloat = 67.0
        static let RightAreaMargin: CGFloat = 14.0
        static let TopAreaHeight: CGFloat = 41.0

        static let NameFont = UIFont.boldSystemFontOfSize(12)

        static let TitleTopHeight: CGFloat = 8.0
        static var TitleMaximumSize = CGSize(width: UIScreen.mainScreen().bounds.width - Layout.LeftAreaMargin - Layout.RightAreaMargin, height: CGFloat.max)
        static let TitleFont = UIFont.boldSystemFontOfSize(16.0)
        static let TitleAttributes = [NSFontAttributeName: Layout.TitleFont]

        static let BottomHeight: CGFloat = 18.0

        static func height(vm: ItemViewModel) -> CGFloat {
            return height(NSAttributedString(string: vm.title.value, attributes: Layout.TitleAttributes))
        }

        static func height(attributedTitle: NSAttributedString) -> CGFloat {
            var height: CGFloat = 0.0
            height += Layout.TopAreaHeight
            height += Layout.TitleTopHeight
            height += Layout.TitleHeight(attributedTitle)
            height += Layout.BottomHeight
            return ceil(height)
        }

        static func TitleHeight(attributedTitle: NSAttributedString) -> CGFloat {
            let options : NSStringDrawingOptions = .UsesLineFragmentOrigin | .UsesFontLeading
            let rect: CGRect = attributedTitle.boundingRectWithSize(Layout.TitleMaximumSize, options: options, context: nil)
            return ceil(rect.size.height)
        }
    }

    ...
}

わかりやすいようにシンプルに必要な部分を抜き出して書きましたが、実際はもう少し計算処理があったりします。利用時はこのようになります。

let vm = ...
return ItemCell.Layout.height(vm)

シンプルな場合と同じです。

補足的な

高さ計算のメソッドをstaticなfuncにしていますが、これは引数を取るからです。引数を取らない場合もありますが、取る場合もあるのでfuncに統一しています。Layoutというstructをわざわざ作らなくても良い気がしますが、これを作っているのは高さ計算の処理を一つの場所にまとめやすいからです。計算済みのセルの高さをどこでキャッシュするかという問題ですが、これは利用側でやれば良いと思っています。

こんな感じです。誰かのヒントになれば。また、もっといい感じのやり方があるよ!というのがあれば是非、コメントください〜。

Pocket
LINEで送る

You may also like...

  • Imada

    はじめまして、
    私、今田と申します。現在は東京農工大学農学部の4年生です。

    この度は、アプリ及びWebサービス開発のご協力願いのため、ご連絡させて頂きました。

    現在、私はビジネスプランを実現するためのエンジニアを探しております。
    私は、国際交流が大好きで、よく留学生やワーキングホリデーで来日する外国人と交流していました。
    そんな中で、彼らは意外にも日本人との接点が少ないことがわかりました。
    彼らの中には、
    「寮に住んでるけど、友達がいなくて一人ぼっちで寂しい」
    「日本人の友達が欲しい!」
    という人が意外にも多かったのです。せっかく日本が好きで来日したのに、日本人の友達もできずに寂しくしている彼らをほっておけませんでした。そんな彼らのために、フラットな関係で現地の人と交流できるサービスを作りたいと思いました。

    これまで、ニーズを検証するために、東京で外国人100人以上の方々にインタビュー調査を行いました。すると、旅行者の人も、
    「ホテルスタッフやウェイトレス以外の、素の日本人と話してみたい」
    というようなニーズもあることがわかりました。
    そういったニーズに答えられるサービスだと思っています。

    既に、ある老人ホームやホームステイ受け入れ団体との関係も構築しています。私自身はシステム開発の技術がないので、現在はこういったエコシステムの構築に力を入れています。
    何としても、困っている外国人のニーズに応えたく、実現させたいです。
    そして将来的には、「世界中どこへ行っても友達ができる」、そういうような世界を作りたいです。

    突然こんなメッセージを送ってしまい、申し訳ございません。
    お忙しいとは思いますが、お返事頂けると幸いです。

    • morizotter

      コメントありがとうございます。私は少し時間がなくて開発の仕事は受けられないですが、他に熱意を受け止めてくれる人もいると思います。頑張ってくださいー。