[iOS] AutoLayoutのNSConstraintを動的に変更したい

2016/02/6

こんにちは。きんくまです。

Zippy電卓で、ユーザーの方からレビューで投稿がありました。

電卓側と、履歴表示が左右逆の方がもっと使い易い気がします。

iPad版は電卓部分と履歴部分が左右レイアウトになっています。
なので、レイアウトを左右どちらにも変更できる(入れ替える)機能を追加して、アップデートを申請しました。
うまくいけば来週のどこかでアップデートができると思います。

zippy_calc_history_layout_demo

今回はどうやって実装したかを書いてみようかと思います。

デモの完成版

Zippy電卓はiPhone版とiPad版でAutoLayoutをいろいろ変更したりしているので、これの説明するためのデモプロジェクトを作成しました。
完成版はこうなります。

dynamic_autolayout_demo

下のボタンを押すと、左右のレイアウトがきりかわります。

dynamic_autolayout1 dynamic_autolayout2

AutoLayoutでポイントとなるのは、左右と中央部分のNSConstraintです。

auto_layout_xcode1

ここに左右のレイアウトが逆になった時のNSConstraintも追加しておきます。ただし、Installedにしません。
だから、6つのConstraintがあります。3つはInstalledで、残りはInstalledのチェックがはずれている状態。
一番下のチェックがはずれていることに注目です。

auto_layout_xcode4

リスト上では、Installedされていないものはこんな感じに薄く表示されます。

auto_layout_xcode2

auto_layout_xcode3

ここまでInsterface Builderで仕込みができたあと、あとはコードで変更する仕組みを作ればOKです。

import UIKit

class ViewController: UIViewController {
    
    //for Layout 1
    @IBOutlet weak var yellowLeadingConstraint: NSLayoutConstraint!
    @IBOutlet weak var yellowTrailingBlueLeadingConstraint: NSLayoutConstraint!
    @IBOutlet weak var blueTrailingConstraint: NSLayoutConstraint!
    
    //for Layout 2
    @IBOutlet weak var blueLeadingConstraint: NSLayoutConstraint!
    @IBOutlet weak var blueTrailingYellowLeadingConstraint: NSLayoutConstraint!
    @IBOutlet weak var yellowTrailingConstraint: NSLayoutConstraint!
    
    var isLayout1:Bool = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillLayoutSubviews() {
        updateViewConstraints()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func layoutSegmentValueChanged(sender: AnyObject) {
        let segment:UISegmentedControl = sender as! UISegmentedControl
        isLayout1 = segment.selectedSegmentIndex == 0
        
        updateViewConstraints()
    }
    
    override func updateViewConstraints() {
        if isLayout1 {
            //At first, deactivate layout2
            blueLeadingConstraint.active = false
            blueTrailingYellowLeadingConstraint.active = false
            yellowTrailingConstraint.active = false
            
            //Second, activate layout1
            yellowLeadingConstraint.active = true
            yellowTrailingBlueLeadingConstraint.active = true
            blueTrailingConstraint.active = true
            
            /**
             * You can also deactivate and activate constraints,
             * using NSLayoutConstraint's class methods.
             */
//            NSLayoutConstraint.deactivateConstraints([
//                blueLeadingConstraint,
//                blueTrailingYellowLeadingConstraint,
//                yellowTrailingConstraint
//            ])
//            NSLayoutConstraint.activateConstraints([xxxx,  xxxx,  xxx])
            
        }else{
            //At first, deactivate layout1
            yellowLeadingConstraint.active = false
            yellowTrailingBlueLeadingConstraint.active = false
            blueTrailingConstraint.active = false
            
            //Second, activate layout2
            blueLeadingConstraint.active = true
            blueTrailingYellowLeadingConstraint.active = true
            yellowTrailingConstraint.active = true
        }
        
        super.updateViewConstraints()
    }
}

Constraintのvalueを変更する場合は問題ないんですが、今回のように大きくレイアウトを変更する場合は updateViewConstraints でやった方がいいみたいです。
Interface Builder上でのInstalledのチェックのON/OFFは、コードではactiveをtrue/falseにすればいいみたい。
で、いろいろと試してみたところ、先にかかっているConstraintをオフにしたあとに、新しいレイアウトをオンにしないと、エラーを吐いてしまいました。順番が大事。

あと、viewWillAppearでupdateViewConstraintsを呼び出したときは、アプリ起動時だけAutoLayout切り替えがうまくいきませんでした。起動したあとに、画面を回転させたり、別画面に移ったりしたらきちんと動作するんですが、なぜだかわかりません、、。
で、viewWillLayoutSubviewsで呼び出すと、アプリ起動時もうまくいきました。

>> 今回のプロジェクトファイルです

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る