目次

目次

【Swift】NSLayoutConstraint をコードで自在に

アバター画像
澁谷太智
アバター画像
澁谷太智
最終更新日2021/03/30 投稿日2021/03/30

はじめに

2020年度入社のiOSエンジニア、澁谷太智です。 今回は、制約をコード上で設定する方法についてご紹介しようと思います。

コード上で制約をつけられることは知っていましたが、記述量が多くて難しそうという印象が強く、なかなか手をつけられずにいました。研修中の課題で、コード上で制約をつける機会があったので、その時調べたことを記事にしようと思いました。

では、制約の付け方について、1つずつ丁寧に見ていきたいと思います。

TOC(Table Of Contents)

  • NSLayoutConstraintのイニシャライザについて
  • 実際に、制約をつけてみた
  • AspectRatio の制約の付け方
  • NSLayoutAnchorを使う利点・注意点
  • 最後に
  • 参考サイト一覧

作業環境

  • macOS Catalina
    • version 10.15.5
  • Xcode
    • version 12.3
  • iPhone12 Pro Max (シミュレーター)
    • iOS 14.3
  • Swift
    • version 5.3.2

NSLayoutConstraintのイニシャライザについて

Appleの公式ドキュメントが参考になるので、ご紹介します。 Apple Developer – Programmatically Creating Constraints

さらに、その他のConstraint (LabelのHaggingなど) に関する情報が載っている公式ドキュメントもご紹介します。 Apple Developer – Anatomy of a Constraint

まず、NSLayoutConstraintのイニシャライザを見てみます。少し見易くしたものを以下に記載します。

convenience init(
    item view1: Any, 
    attribute attr1: NSLayoutConstraint.Attribute, 
    relatedBy relation: NSLayoutConstraint.Relation, 
    toItem view2: Any?, 
    attribute attr2: NSLayoutConstraint.Attribute, 
    multiplier: CGFloat, 
    constant c: CGFloat
)

項目がてんこ盛りすぎて、私はスッとページを閉じました。

ここで、Appleの公式ドキュメントに載っている画像を見てみます。

view_formula_2x_from_appleDeveloper.png

(引用:Apple Programmatically Creating Constraints

この画像を見ても、初めて見たときは、なんのことやらでした。

では、イニシャライザとStoryboard上の設定を見比べてみましょう。

以下の画像は、制約をクリックした際に右端に表示される画面です。

スクリーンショット 2020-12-23 11.51.07.png

似たような項目があります。1つずつ見ていきましょう。

First Item

イニシャライザでは、 item view1: Anyattribute attr1: NSLayoutConstraint.Attribute にあたります。

  • item view1: Any : SuperView
  • attribute attr1: NSLayoutConstraint.Attribute : Trailing

Relation

イニシャライザでは、 relatedBy relation: NSLayoutConstraint.Relation にあたります。

  • relatedBy relation: NSLayoutConstraint.Relation : Equal

Second Item

イニシャライザでは、 toItem view2: Any?attribute attr2: NSLayoutConstraint.Attribute にあたります。

  • toItem view2: Any? : BlueView
  • attribute attr2: NSLayoutConstraint.Attribute : Trailing

Constant

イニシャライザでは、 constant c: CGFloat にあたります。

Multiplier

イニシャライザでは、 multiplier: CGFloat にあたります。

こう見比べていくと、イニシャライザで何を設定しているのか読み取りやすくなると思います。 Apple公式ドキュメントの画像と比較すると、さらに理解が深まるのではないかと思います。 では、実際に制約をつけてみましょう。

実際に、制約をつけてみた

パターン1 NSLayoutConstraint のイニシャライザを使用した場合

import UIKit

final class ViewController: UIViewController {

    private let newView: UIView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        //AutoresizingMask を Auto Layout変換 無効化
        newView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(newView)

        let leading = NSLayoutConstraint(
            item: newView,
            attribute: .leading,
            relatedBy: .equal,
            toItem: view,
            attribute: .leading,
            multiplier: 1,
            constant: 0
        )

        let trailing = NSLayoutConstraint(
            item: newView,
            attribute: .trailing,
            relatedBy: .equal,
            toItem: view,
            attribute: .trailing,
            multiplier: 1,
            constant: 0
        )

        let top = NSLayoutConstraint(
            item: newView,
            attribute: .top,
            relatedBy: .equal,
            toItem: view,
            attribute: .top,
            multiplier: 1,
            constant: 0
        )

        let bottom = NSLayoutConstraint(
            item: newView,
            attribute: .bottom,
            relatedBy: .equal,
            toItem: view,
            attribute: .bottom,
            multiplier: 1,
            constant: 0
        )

        // 制約を有効化します
        NSLayoutConstraint.activate([leading, trailing, top, bottom])

        view.backgroundColor = .black
        newView.backgroundColor = .blue
    }
}

パターン2 NSLayoutAnchor を使用した場合

import UIKit

final class ViewController: UIViewController {

    private let newView: UIView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        //AutoresizingMask を Auto Layout変換 無効化
        newView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(newView)

        let leading = newView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0)
        let trailing = newView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
        let top = newView.topAnchor.constraint(equalTo:  view.topAnchor, constant: 0)
        let bottom = newView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)

        //制約を有効化します
        NSLayoutConstraint.activate([leading, trailing, top, bottom])

        view.backgroundColor = .black
        newView.backgroundColor = .blue
    }
}

2つの方法、どちらで制約を付けても結果は同じです。パターン2に関してはiOS9から使えるようになりました。 コード量が減って、パターン2の方が見やすいですね。

上記2つのコードで、制約の有効化に、 NSLayoutConstraint.activate([NSLayoutConstraint]) を使用しています。 公式ドキュメントを見てみると、

Typically, using this method is more efficient than activating each constraint individually.

個別に制約を有効化するより、 NSLayoutConstraint.activate([NSLayoutConstraint]) を使用した方が効率が良いと書いてあります。

複数の制約を設定するときは、 isActive で個々に設定するのではなく、NSLayoutConstraint.activate([NSLayoutConstraint]) で一気に設定するようにしましょう。

スクリーンショット

Simulator Screen Shot - iPhone 12 Pro Max - 2020-12-24 at 13.07.00.png

※ translatesAutoresizingMaskIntoConstraints について

AutoresizingMaskの設定値をAuto Layoutの制約に変換するかどうかを決めるBool値です。 Auto Layout登場以前はAutoresizingMaskという仕組みで、ビューの動的なサイズ変更を実現していたそうです。 このBool値を予め false にしておかないと、自分で設定した制約とコンフリクトを起こしてしまいます。 (参考サイト: Qiita – Auto Layoutをコードで書いて学ぶ(Swift 2.2))

AspectRatio の制約の付け方

本記事では、 AspectRatio (縦 : 横)9 : 16 で設定しようと思います。

パターン1 NSLayoutConstraint のイニシャライザを使用した場合

let ratio: CGFloat = 9 / 16

let aspectRatio: NSLayoutConstraint = NSLayoutConstraint(
    item: newView,
    attribute: .height,
    relatedBy: .equal,
    toItem: newView,
    attribute: .width,
    multiplier: ratio,
    constant: 0
)

aspectRatio?.isActive = true
  • itemtoItem には、AspectRatio を設定したいViewを記述します。
  • attribute は、縦横比なので、widthheight を設定します。

    • 縦:横(縦 / 横)で計算したい場合 itemattributeheight toItemattributewidth
  • multiplier には、比率の計算結果を入力します。

コード

import UIKit

final class ViewController: UIViewController {

    private let newView: UIView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        newView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(newView)

        let leading = newView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0)
        let trailing = newView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
        let top = newView.topAnchor.constraint(equalTo:  view.topAnchor, constant: 0)

        let ratio: CGFloat = 9 / 16

        let aspectRatio = NSLayoutConstraint(
            item: newView,
            attribute: .height,
            relatedBy: .equal,
            toItem: newView,
            attribute: .width,
            multiplier: ratio,
            constant: 0
        )

        // 制約を有効化
        NSLayoutConstraint.activate([leading, trailing, top, aspectRatio])

        view.backgroundColor = .black
        newView.backgroundColor = .blue
    }
}

上記コードでは、大きさをSuperViewより小さいサイズに設定する為、BottomAnchor を設定していません。設定してしまうと、警告文が表示されます。 以下がBottomAnchorを設定した上で、AspectRatioを設定した時に出る警告文です。

アバター画像

澁谷太智

目次