忍者ブログ
  • 2024.04
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 2024.06
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/05/19 16:13 】 |
コードから制約を生成する
今やiOSでは当たり前となったAutoLayout。
制約(NSLayoutConstraint)を適切に指定することで、レイアウトを指定することができ、画面サイズの違う端末でも意図通りに表示できるようになる。


AutoLayoutによる位置・サイズの決定は、viewWillAppearとviewDidAppearの間で行われる。



【画面初期化の流れ】

viewDidLoad実行
   ↓
viewWillAppear実行
   ↓
AutoLayout実行(内部)
   ↓
ビューのlayoutSubviews実行
   ↓
viewDidLayoutSubviews実行(もう一度呼ばれる)
   ↓
画面が構成される(内部)
   ↓
画面遷移開始(ここからユーザの眼に触れる)
   ↓
画面遷移終了
   ↓
viewDidAppear実行



間違って(あるいは面倒くさがって)ビューコントローラ上のコードで画面レイアウトを作ろうとして、画面表示前(viewDidLoadなど)でビューのframeを直接いじっても、
その後にAutoLayout制約が適用されて上書きされてしまう。
(じゃあviewDidAppearの後にframeをいじろう、と考えたくなるところだが、それだとちょっと間に合わない。画面遷移をしながら表示するビューの場合、frameをいじる前のビューが画面に映ってしまう(=手遅れ)なのだ。


だから、レイアウトを処理・変更する必要があるビューは、必ずカスタム定義をして
layoutSubviews
メソッドを実装しておく必要がある。
このメソッドは、viewWillAppearの後に呼ばれる。
また、画面を再構成する必要があると自動的に判断されたときにも呼ばれるし、明示的に呼びたい場合は、ビューコントローラで
[view setNeedsLayout];
を実行すればよい。




じゃあどうするか。
制約適用直後に画面が映ってしまうのはさけられない。
ということはつまり、制約適用時に求める画面が構成されるようにしておけばいい。


だから、、、

【制約を動的に作成する】

ということになる。


つまり、AutoLayoutを使うようになった時点で、画面構成はすべて制約で作るようにするべきなのだ。
frameとかboundsとかを直接いじることはなくなってくるはずだ(少なくとも、定数値を代入するようなことはないはずだ。せっかくAutoLayoutを指定していても台無しになる)。


さて、そうすると、どうやってコード上で制約を記述するのか、ということを覚えなければならない。ストーリーボードではいつも作ってるけど、コーディングはどうやって…?


公式のAutoLayoutマニュアル


制約クラスは
NSLayoutConstraint

制約のインスタンスを作るメソッドは2種類ある

1.constraintsWithVisualFormat: options: metrics: views:



VisualFormat
VisualFormatとは、制約を人間に見やすく記述できるDSL。
これを使用して、ビューのサイズや位置を指定する。storyboardとほぼ同じことができる

公式のVisual Format Language仕様

いくつか例示する。
なお、view1,view2などは、制約を作りたいビューのメンバ変数名(詳細は後述)

view1の横幅を10にする
H:[view1(==10)]

view1の高さを20以上にする
V:[view1(>=20)]

view1の高さを20以上100以下にする
V:[view1(>=20,<=100)]

view1とview2の幅を同じにする
H:[view1(==view2)]

※比較演算子は、==,>=,<= の3つしか使えない
※カッコで囲わないと、パーサーがビュー名の区切りを判断できない。
※HもVも書かないときはH(水平方向)になる

view1とview2を12px空けて横に並べる
H:[view1]-12-[view2]

view1とview2をデフォルトの間隔を空けて縦に並べる
V:[view1]-[view2]

view1とview2を間隔無しで並べる
H:[view1][view2]

superviewとの間隔を指定する。パイプ(|)がsuperviewを表す
H:|-50-[view1]-50-|


options
指定できる内容は以下
NSLayoutFormatAlignAllLeft
NSLayoutFormatAlignAllRight
NSLayoutFormatAlignAllTop
NSLayoutFormatAlignAllBottom
NSLayoutFormatAlignAllLeading
NSLayoutFormatAlignAllTrailing
NSLayoutFormatAlignAllCenterX
NSLayoutFormatAlignAllCenterY
NSLayoutFormatAlignAllBaseline
NSLayoutFormatAlignAllLastBaseline
NSLayoutFormatAlignAllFirstBaseline

NSLayoutFormatDirectionLeadingToTrailing
NSLayoutFormatDirectionLeftToRight
NSLayoutFormatDirectionRightToLeft

何もいらなければ0を指定。


metrics
調べてない

views
VisualFormat内に登場したビュー名と、ビューへの参照を格納したNSDictionary。
上記の例だと、
NSDictionary viewsDictionary = [[NSDictionary alloc]initWithObjects:@[view1,view2] forKeys:@[@"view1",@"view2"]];
のような感じ。
ちなみにこれと同じことが
NSDictionaryOfVariableBindings(view1, view2);
と書くことができる。


戻り値
記述した制約をすべて満たせるNSLayoutConstraintの配列が返ってくる。


最終的なコード例
view1の横幅を40にし、view2を水平に12px空けて並べ、それぞれsuperviewと左右端を合わせる。(制約が4つ作られる)

NSDictionary viewsDictionary = NSDictionaryOfVariableBindings(view1, view2);
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view1(==40)]-12-[view2]|" options:0 metrics:nil views:viewsDictionary]];






例外を捕まえる
VisualFormatは実行時にパースされるので、書き間違えていると例外が飛んでくる。
ちゃんとキャッチしてチェックしよう。


@try{
NSDictionary viewsDictionary = NSDictionaryOfVariableBindings(view1);
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:[view1==%d)]" options:0 metrics:nil views:viewsDictionary]];
}@catch(id exception) {
NSLog(@"%@", [exception description]);
}

Unable to parse constraint format:
Expected a ']' here. That is how you give the end of a view.
V:|[view1==123)]|
^
こんな感じで出力される







2.constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:

item
制約を付けるビュー。

attribute
以下のものがある。
実態は前のメソッドのoptionに指定するNSLayoutFormat***と同じ。
NSLayoutAttributeLeft = 1,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeBaseline,
NSLayoutAttributeLastBaseline = NSLayoutAttributeBaseline,
NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),

relatedBy
比較演算子。以下の3つ
NSLayoutRelationLessThanOrEqual = -1,
NSLayoutRelationEqual = 0,
NSLayoutRelationGreaterThanOrEqual = 1,


toItem
対象となるビューを指定する。
align系なら、どのビューからの距離か、を指定するときに使う。
size系なら、あのビューのサイズの○倍、というような指定の時に使う。
不要ならnilを指定。

attribute
toItem側のattribute。
itemのtopとtoItemのtopが○○px離れてる、というような制約のときに使用。
なければ0。

multiplier
係数。計算式に使う。後述

constant
定数。計算式に使う。後述


コード例
view1の高さを123にする場合。

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil attribute:0
multiplier:1.f constant:123]
];



view1の上端をview2の上端から25px以上空ける制約

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:self.view2 attribute:NSLayoutAttributeTop
multiplier:1.f constant:25]
];



view1の幅をview2の高さの2倍より10大きいサイズにする制約

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view2 attribute:NSLayoutAttributeHeight
multiplier:2.f constant:10]
];




PR
【2014/12/05 14:01 】 | iPhone | 有り難いご意見(0)
<<iOS通知のカスタムアクション実装 | ホーム | UITabBarをタップしたときのdelegateメソッド>>
有り難いご意見
貴重なご意見の投稿














<<前ページ | ホーム | 次ページ>>