AutoLayoutの制約を動的に変更した話

追記 2016/03/26
iOS9.2で意図した通りに動作しなくなったので、
この話の続きを書きました。
— ここまで追記 —

解像度(主に高さ)が異なると、
ToyTone for iOS(App Store)みたいなアプリの場合は、
だいぶ面倒なことになる。

つまり、こんな感じになる必要がある。
20150903-1

正攻法としては、
いきなりコントロールを配置しないで、
まずは、コンテナとしてテーブルビューのごとくUIViewを配置して、
コンテナの高さの制約は、SuperViewの高さに係数を掛けたものを設定し、
そのコンテナにコントロールを配置するのが一般的と思われる。

今思うと、そうすれば良かったと思うけど、
早く形にしたかったのでコードでなんとかした。

まず、左側のラベルについて、SuperViewの上辺から距離を設定して、
ラベルの右側にあるコントロールの配置は、左側のラベルを基準にするようにした。

次にコードはこんな感じ。

import UIKit

class MainContainerView: UIView {
    var containerHeightAtDesigned: CGFloat = 0.0
    var containerHeightAtUpdated: CGFloat = 0.0
    var constantsAtDesigned: [UIView : CGFloat] = [:]

    required init(coder aDecoder: NSCoder) {
        super.init( coder: aDecoder )

        containerHeightAtDesigned = self.frame.height // memo: デザイン時の高さ
        //println( "MainContainerView.init, frame: \(self.frame)" )
        for c in self.constraints() {
            if let label = c.firstItem as? UILabel {
                if c.firstAttribute == .Top {
                    constantsAtDesigned[label] = c.constant
                }
            }
        }
    }

    override func updateConstraints() {
        //println( "updateConstraints: \(self.frame)" )

        containerHeightAtUpdated = self.frame.height
        let constraints = self.constraints()
        for var i = 0; i<constraints.count; i++ {
            let c = constraints[i] as! NSLayoutConstraint
            if let label = c.firstItem as? UILabel {
                if c.firstAttribute == .Top {
                    var val = constantsAtDesigned[label]!
                    val = val * (self.frame.height / containerHeightAtDesigned)
                    //println( "\(label.text) -> \(c.constant) -> \(val)" )
                    c.constant = val
                    //println()
                }
            }
        }
        
        super.updateConstraints()
    }
    
    override func layoutSubviews() {
        if containerHeightAtUpdated != self.frame.height {
            //println( "layoutSubviews: \(self.frame)" )
            setNeedsUpdateConstraints()
        }

        //println( "layoutSubviews: Here!" )
        super.layoutSubviews()
    }
}

MainContainerViewというのは、
この右側にあるViewControllerにある最上位のUIViewに設定したクラスである。

20150903-2

これにより、デザイン時の制約を元に、実行時に制約が変更される。

参考にしたページ

UIViewControllerのライフサイクル
http://qiita.com/mo_to_44/items/0ca628b4cc74c8c5599d

めでたし、めでたし。

Leave a Comment