LoginSignup
2
3

More than 5 years have passed since last update.

Auto Layout をちょっと楽に記述できる AutolayoutHelper を作った

Last updated at Posted at 2017-06-16

はじめに

最近 NSLayoutAnchor を利用していますが、isActive = truetranslatesAutoresizingMaskIntoConstraints = false をよく忘れます :sob:

また以前は SnapKit を利用していて、制約が壊れた際のログが以下のように出力されるので、どのファイルのどの行当たりを見ればいいかが一目瞭然でとても助かっていました。

(
    "<SnapKit.LayoutConstraint:0x6100000a32a0@ViewController.swift#90 UIView:0x7fee58409540.left == UIView:0x7fee58400c60.left + 22.0>",
    "<SnapKit.LayoutConstraint:0x6100000a3540@ViewController.swift#92 UIView:0x7fee58409540.right == UIView:0x7fee58400c60.right - 22.0>",
    "<SnapKit.LayoutConstraint:0x6100000a35a0@ViewController.swift#93 UIView:0x7fee58409540.width == 300.0>",
    "<NSLayoutConstraint:0x618000097bb0 'UIView-Encapsulated-Layout-Width' UIView:0x7fee58400c60.width == 768   (active)>"
)

AutolayoutHelper

ということで NSLayoutAnchor を薄くラップしつつ、デバッグしやすいライブラリを作ってみました。

インストール

github "sora0077/AutolayoutHelper" "master"

※ 今のところ Carthage のみの対応です。

使い方

  • import すると UIView, UILayoutGuide など NSLayoutAnchor を持っている型に autolayout, (autoresizing) プロパティが生えます。
  • 基本的には NSLayoutAnchor とほぼ同じインターフェースです。
    • 追加として、よく使うサイズ指定や親ビューにフィットする指定のために size, edges などが追加されています。

例)

import UIKit
import AutolayoutHelper

class ViewController: UIViewController {
    class View: UIView {}

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let orangeBox = UIView()
        orangeBox.backgroundColor = .orange
        view.addSubview(orangeBox)
        orangeBox.autolayout.edges.equal(to: view.autoresizing.edges, insets: (x: 50, y: 100))

        let redBox = UIView()
        redBox.backgroundColor = .red
        view.addSubview(redBox)
        redBox.autolayout.top.equal(to: orangeBox.autolayout.top, constant: 50)
        redBox.autolayout.left.equal(to: orangeBox.autolayout.left)
        let sizeConstraint =
            redBox.autolayout.size.equal(to: orangeBox.autolayout.size, insets: (x: 50, y: 0), priority: .defaultHigh)
        redBox.autolayout.bottom.equal(to: orangeBox.autolayout.bottom)

        print(sizeConstraint.width, sizeConstraint.height)  // NSLayoutConstraint, NSLayoutConstraint
    }
}

Simulator Screen Shot 2017.06.16 20.11.10.png

デバッグ

もし制約が満たせていない場合、以下のようなログ表示になります。

(
    "<NSLayoutConstraint:0x600000098e70 '@ViewController.swift#22' H:|-(50)-[UIView:0x7f981ec05270](LTR)   (active, names: '|':UIView:0x7f981ec05670 )>",
    "<NSLayoutConstraint:0x600000098f10 '@ViewController.swift#22' UIView:0x7f981ec05270.trailing == UIView:0x7f981ec05670.trailing - 50   (active)>",
    "<NSLayoutConstraint:0x6000000990a0 '@ViewController.swift#23' UIView:0x7f981ec05270.width == 30   (active)>",
    "<NSLayoutConstraint:0x608000095db0 'UIView-Encapsulated-Layout-Width' UIView:0x7f981ec05670.width == 414   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000000990a0 '@ViewController.swift#23' UIView:0x7f981ec05270.width == 30   (active)>

実装

登場するのは基本的に Layout<Anchors> だけです。

public struct Layout<Anchors> {
    let anchors: Anchors
}

この型に対して制約付きの extension を必要な分だけ定義しています。
例えば size に対する実装は以下になります。

// UIView.autolayout
var size: Layout<(width: NSLayoutDimension, height: NSLayoutDimension)> { get }
public extension Layout where Anchors == (width: NSLayoutDimension, height: NSLayoutDimension) {
    @discardableResult
    func equal(to other: Layout,
               multiplier: CGFloat = 1,
               constant: CGFloat = 0,
               priority: LayoutPriority = .required,
               file: StaticString = #file,
               line: UInt = #line
        ) -> (width: NSLayoutConstraint, height: NSLayoutConstraint) {
        return (
            width: anchors.width.constraint(equalTo: other.width, ...),
            height: anchors.height.constraint(equalTo: other.height, ...),
        )
    }
}

Anchors が2つの NSLayoutDimension であるときに同じ型を引数に持つ equal メソッドを定義しています。

全体は こちら です。

補足

使い方で出てきた autoresizing プロパティですが、実態は以下のように translatesAutoresizingMaskIntoConstraints を変更しているかどうかの違いです。
これについては、あまり良いインターフェースではない気がするので今後無くすかもしれません。

public extension AutolayoutExtension where Self: UIView {
    var autolayout: Extension<Self> {
        if translatesAutoresizingMaskIntoConstraints {
            translatesAutoresizingMaskIntoConstraints = false
        }
        return Extension(base: self)
    }

    var autoresizing: Extension<Self> {
        return Extension(base: self)
    }
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3