[編集:警告:以降の議論全体は、iOS 8 によって時代遅れになるか、少なくとも大幅に緩和される可能性があります。これにより、ビューの変換が適用されたときにレイアウトをトリガーするという間違いを犯すことはなくなります。]
Autolayout と View Transform
Autolayout は、ビューの変換ではまったくうまく機能しません。私が知る限り、その理由は、(デフォルトの恒等変換以外の) 変換を持つビューのフレームをいじってはいけないということですが、それはまさに autolayout が行うことです。自動レイアウトが機能する方法はlayoutSubviews
、実行時にすべての制約を通過し、それに応じてすべてのビューのフレームを設定することです。
つまり、制約は魔法ではありません。それらは単なるやることリストです。layoutSubviews
やることリストが完成する場所です。そして、フレームを設定することでそれを行います。
これをバグと見なさざるを得ません。この変換をビューに適用すると:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
ビューの中心が以前と同じ場所に表示され、半分のサイズで表示されることを期待しています。しかし、その制約によっては、それは私が見ているものとはまったく異なる場合があります.
[実際には、ここで 2 つ目の驚きがあります。ビューに変換を適用すると、すぐにレイアウトがトリガーされます。これは私には別のバグのようです。あるいは、それが最初のバグの核心なのかもしれません。私が期待するのは、少なくともレイアウト時間まで変換を回避できることです。たとえば、レイアウト時間までフレームアニメーションを回避できるのと同じように、デバイスが回転します。しかし、実際にはレイアウト時間は即時であり、これは間違っているようです。]
解決策 1: 制約なし
現在の解決策の 1 つは、半永久的な変換をビューに適用する場合 (そして、何らかの方法で一時的に振るだけでなく)、ビューに影響を与えるすべての制約を削除することです。残念ながら、自動レイアウトは引き続き行われ、ビューを配置する場所を指定する制約がないため、通常、これによりビューが画面から消えてしまいます。したがって、制約を削除するだけでなく、ビューtranslatesAutoresizingMaskIntoConstraints
を YES に設定しました。ビューは古い方法で機能するようになり、自動レイアウトの影響を受けなくなりました。(明らかに自動レイアウトの影響を受けますが、暗黙的な自動サイズ変更マスクの制約により、その動作は自動レイアウトの前と同じようになります。)
解決策 2: 適切な制約のみを使用する
それが少し極端に思える場合、別の解決策は、意図した変換で正しく機能するように制約を設定することです。たとえば、ビューのサイズがその内部の固定幅と高さだけで決まり、純粋にその中心で配置されている場合、スケール変換は期待どおりに機能します。このコードでは、サブビュー ( otherView
) の既存の制約を削除し、これらの 4 つの制約に置き換えて、幅と高さを固定し、純粋に中心で固定します。その後、スケール変換が機能します。
NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
if (con.firstItem == self.otherView || con.secondItem == self.otherView)
[cons addObject:con];
[self.view removeConstraints:cons];
[self.otherView removeConstraints:self.otherView.constraints];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:self.otherView.center.x]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:self.otherView.center.y]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.width]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.height]];
要するに、ビューのフレームに影響を与える制約がない場合、自動レイアウトはビューのフレームに触れないということです。これは、変換が関係しているときに求めているものです。
解決策 3: サブビューを使用する
上記の両方のソリューションの問題は、ビューを配置するための制約の利点が失われることです。そこで、それを解決するソリューションを紹介します。ホストとしてのみ機能する非表示のビューから開始し、コンストレイントを使用して配置します。その中に、実際のビューをサブビューとして配置します。制約を使用してサブビューをホスト ビュー内に配置しますが、それらの制約を、変換を適用したときに反撃しない制約に制限します。
以下に図を示します。

白いビューはホスト ビューです。透明で見えないふりをすることになっています。赤いビューはそのサブビューで、その中心をホスト ビューの中心に固定することによって配置されます。これで、赤のビューを問題なく中心を中心にスケーリングおよび回転できます。実際、図はそのようにしたことを示しています。
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
その間、ホスト ビューの制約により、デバイスを回転させてもホスト ビューが適切な場所に保持されます。
解決策 4: 代わりにレイヤー変換を使用する
ビュー変換の代わりに、レイヤー変換を使用します。これはレイアウトをトリガーしないため、制約との即時の競合を引き起こしません。
たとえば、次の単純な「ドキドキ」ビュー アニメーションは、自動レイアウトで壊れる可能性があります。
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
最終的にビューのサイズに変更はありませんでしたが、単に設定するtransform
とレイアウトが発生し、制約によってビューが飛び回る可能性があります。(これはバグのように感じますか?) しかし、Core Animation で同じことを行うと (CABasicAnimation を使用し、アニメーションをビューのレイヤーに適用します)、レイアウトは発生せず、正常に動作します。
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.autoreverses = YES;
ba.duration = 0.3;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
[v.layer addAnimation:ba forKey:nil];