VFLを使ってみよう

前回(AutoLayoutをコードから使おう)でAuto Layoutをコードから使ってみました。
実は、Auto Layoutをコードで使う方法は前回のNSLayoutConstraintクラスの +constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:メソッドを使う方法以外にももう一つ方法があります。

それが「Visual Format Language (視覚的形式言語)」を使った書き方です。 (以下VFLと略して話を続けます)

まず、VFLのメリット、デメリットと基本の文法を説明したあとに、前回と同じレイアウトをVFLで作って見ようと思います。


メリット

  • 可読性が上がる

前回の+constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:メソッドを使った方法だと、制約を一つずつ登録しないと行けません。
しかも、一つの制約を設定するのに、7つの引数が必要で、コードが長くなりがちです。 制約は一つの要素に複数あることが多いため、この方法で制約を設定した場合にコードが長くなり可読性が悪くなってしまいます。

VFLは複数の制約をまとめて登録ができます。なので記述が少なくなってコードの見通しがよくなります。

デメリット

  • 記述方法が独特なので多少の学習コストがある

VFLはけっこう独特な書き方をするので、慣れるまで多少の時間が掛かるかもしれません。

  • 文字列で制約を設定するのでタイプミスしても実行されるまでエラーにならない

VFLは文字列で制約を設定していきます。なので、タイプミスをした場合にはコンパイラは判別できず、実行時にエラーがでてしまいます。

  • いくつかの制約はVFLでは設定できない

アスペクト比など、VFLでは設定できない制約がいくつかあります。


基本文法

VFLの基本文法を見ていきます。
設定する制約は文字列で作っていきます。

2つのビューの間の距離を指定

myViewというビューとmyButtonというボタンの距離を8ポイント話す制約は以下のように書きます

[myView]-30-[myButton]
幅を指定する

myViewの幅を300ポイントに指定するには以下のようにします。

[myView(300)]

buttonというボタンを50ポイント以上に設定するには以下のようにします。

[button(>=50)]

他のビューと同じ幅を指定することもできます。
myView1がmyView2と同じ幅になるように指定するには以下のようにします。

[myView1(==myView2)
2つのビューを隣接させる

myViewとmyButtonを隣接させる制約は以下のようにします。

[myView][myButton]
親ビューを指定する

親ビューは|の一文字で指定ができます。
親ビューから8ポイント離れたmyViewというビューを指定するには以下のようにします。

|-8-[myView]-8|
方向性を決める

制約の方向を指定することもできます。
V:をかけば垂直方向を指定します。 H:をかけば水平方向を指定します。

垂直方向で親ビューに対して、8ポイントずつの距離を離したmyViewを指定するには以下のようにします。

V:|-8-[myView]-8-|  

水平方向で親ビュー、myView、myButtonがそれぞれ8ポイントずつ距離が離れた制約を指定するには以下のようにします。

H:|-8-[myView]-8-[myButton]-8-|

制約の設定

VFLで指定した制約はNSLayoutConstraintクラスのconstraintsWithVisualFormat:options:metrics:views:メソッドを実行することで実際に設定することができます。

let views = ["myView" : myView]  
let formatString = "|-[myView(width)]-|"  
let metrics = ["width" : 200]

let constraints = NSLayoutConstraint.constraintsWithVisualFormat(formatString, options:.AlignAllTop , metrics: metrics, views: views)

self.view.addConstraints(constraints)

constraintsWithVisualFormat:options:metrics:views:をメソッドを実行するにはいくつか準備が必要です。

1:制約を指定したいビューの辞書を作ります。キーにVFLで指定する文字列を、値にビューのオブジェクトを指定します。

let views = ["myView" : myView]  

これを設定することでVFLの文字列で指定されたビューが実際のビューオブジェクトにマッピングされます。

let formatString = "|-[myView(width)]-|"

上記のVFLがあった時に、文字列の"myView"がオブジェクトのmyViewとつながります。

2:任意でメトリクス辞書を作ります。

必須ではないですが、メトリクス辞書を作ることもできます。
VFLに指定する定数を指定する目的で作られます。
これは文字列のキーに数値の値の辞書で作ります

let metrics = ["width" : 200]  

これを設定することで、VFLの数値を文字列で指定することができるようになります。

let formatString = "|-[myView(width)]-|"  

このVFLは

let formatString = "|-[myView(200)]-|"  

と同じ意味になります。

3:VFLの文字列を設定します。
VFLの基本文法に従って文字列でVFLを作成します。

let formatString = "|-[myView(width)]-|"  

4:constraintsWithVisualFormat:options:metrics:views:メソッドを実行する

いよいよ制約を
constraintsWithVisualFormat:options:metrics:views:メソッドで実行します。

このメソッドの形式は以下のようになっています。

class func constraintsWithVisualFormat(_ format: String,  
                               options opts: NSLayoutFormatOptions,
                               metrics metrics: [String : AnyObject]?,
                                 views views: [String : AnyObject]) -> [NSLayoutConstraint]

4つの引数があります。

format: 制約指定をVFLで指定する
options: VFLのオプションで、ビューオブジェクトのレイアウトの属性や方向を示します。
metrics:VFLに指定する定数の辞書を指定
views : VFLに指定するビューの辞書を指定

それぞれの引数を設定します。 最後に、self.view.addConstraints()で制約を追加すればOKです。

let constraints = NSLayoutConstraint.constraintsWithVisualFormat(formatString, options:.AlignAllTop , metrics: metrics, views: views)

self.view.addConstraints(constraints)  

(上の例のオプションで.AlignAllTopを指定していますが、ビューが一つだけなので、制約には何も影響しません。ご了承ください。)

前回のレイアウトをVFLで設定する

さて、VFLの使い方を見たところで、早速実際にVFLでAuto Layoutを設定してみます。

前回のビューのレイアウトは以下のようでした

赤のビュー

  • 上部と親ビューの上部の距離が88
  • 左側と親ビューの左側の距離が10
  • 下部と親ビューの下部の距離が20
  • 幅が親ビューの幅の40%

黄色ビュー

  • 上部と親ビューの上部の距離が88
  • 右側と親ビューの右側の距離が10
  • 下部と親ビューの下部の距離が20
  • 幅が赤色ビューと同じ

finish_layout_constraint

まず、ビューの辞書とマトリクスの辞書を設定します。
今回は制約の実行はビューのレイアウト設定が終わるviewDidLayoutSubviews()に書いてみたいと思います。

override func viewDidLayoutSubviews() {  
    let views = [
            "redView"       : redView,
            "yellowView"    : yellowView
        ]

    let metrics = ["width": self.view.frame.width * 0.4]
}

次に、赤色ビューの垂直方向の制約を作成します。

    //赤色ビューの垂直方向の制約
    let redViewVerticalConstrains =
        NSLayoutConstraint.constraintsWithVisualFormat("V:|-88-[redView]-20-|",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: nil,
            views: views)

黄色ビューの垂直方向の制約を作成します。

    //黄色ビューの垂直方向の制約
    let yellowViewVerticalConstrains =
            NSLayoutConstraint.constraintsWithVisualFormat("V:|-88-[yellowView]-20-|",
                options: NSLayoutFormatOptions(rawValue: 0),
                metrics: nil,
                views: views)

赤色ビューの水平方向の制約を作成します。

    //赤色ビューの水平方向の制約
    let redViewHorizonConstrains = NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[redView(width)]",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: metrics,
            views: views)

黄色ビューの水平方向の制約を作成します。

    //黄色ビューの水平方向の制約
    let yellowViewHorizonConstrains = NSLayoutConstraint.constraintsWithVisualFormat("H:[yellowView(==redView)]-20-|",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: nil,
            views: views)

最後に4つの制約をself.viewに追加します。

    //制約の設定
    self.view.addConstraints(redViewVerticalConstrains
        + yellowViewVerticalConstrains
        + redViewHorizonConstrains
        + yellowViewHorizonConstrains)

これでOKです。

実行してみます。 finish1

finish2

まとめ

VFLという方法でAuto Layoutを設定する方法を見てみました。
記述が独特ですが、使いこなせばコードが簡潔にかけます。

最後に全コードを載せます。

//
//  VFLAutoLayoutViewController.swift
//  CodeOfAutoLayout
//
//  Created by satoutakeshi on 2016/01/11.
//  Copyright © 2016年 satoutakeshi. All rights reserved.
//

import UIKit

class VFLAutoLayoutViewController: UIViewController {

    let redView     = UIView(frame: CGRectZero)
    let yellowView  = UIView(frame: CGRectZero)

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

        redView.backgroundColor = UIColor.redColor()
        redView.translatesAutoresizingMaskIntoConstraints = false


        yellowView.backgroundColor = UIColor.yellowColor()
        yellowView.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(redView)
        self.view.addSubview(yellowView)
    }



    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    override func viewDidLayoutSubviews() {
        let views = [
            "redView"       : redView,
            "yellowView"    : yellowView
        ]


        let metrics = ["width": self.view.frame.width * 0.4]

        //赤色ビューの垂直方向の制約
        let redViewVerticalConstrains =
        NSLayoutConstraint.constraintsWithVisualFormat("V:|-88-[redView]-20-|",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: nil,
            views: views)

        //黄色ビューの垂直方向の制約
        let yellowViewVerticalConstrains =
            NSLayoutConstraint.constraintsWithVisualFormat("V:|-88-[yellowView]-20-|",
                options: NSLayoutFormatOptions(rawValue: 0),
                metrics: nil,
                views: views)

        //赤色ビューの水平方向の制約
        let redViewHorizonConstrains = NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[redView(width)]",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: metrics,
            views: views)

        //黄色ビューの水平方向の制約
        let yellowViewHorizonConstrains = NSLayoutConstraint.constraintsWithVisualFormat("H:[yellowView(==redView)]-20-|",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: nil,
            views: views)

        //制約の設定
        self.view.addConstraints(redViewVerticalConstrains
            + yellowViewVerticalConstrains
            + redViewHorizonConstrains
            + yellowViewHorizonConstrains)

    }

}

またサンプルコードは以下です。

https://github.com/SatoTakeshiX/AutolayoutTutorials
からプロジェクトをダウンロードして「CodeOfAutoLayout」をご確認ください。

参考