自動レイアウトが使用されている場合、CALayer のアンカー ポイントを調整するにはどうすればよいですか? 質問する

自動レイアウトが使用されている場合、CALayer のアンカー ポイントを調整するにはどうすればよいですか? 質問する

注記: この質問がされてから状況は変化しました。ここ最近の概要を知りたい場合は。


自動レイアウトの前は、フレームを保存し、アンカー ポイントを設定し、フレームを復元することで、ビューを移動せずにビューのレイヤーのアンカー ポイントを変更できました。

自動レイアウトの世界では、フレームを設定する必要はなくなりましたが、制約はビューの位置を希望の位置に戻すというタスクには適していないようです。制約をハックしてビューの位置を変更することはできますが、回転やその他のサイズ変更イベントが発生すると、これらは再び無効になります。

次の優れたアイデアは、「レイアウト属性 (左と幅) の無効なペアリング」を作成するため機能しません。

layerView.layer.anchorPoint = CGPointMake(1.0, 0.5);
// Some other size-related constraints here which all work fine...
[self.view addConstraint:
    [NSLayoutConstraint constraintWithItem:layerView
                                 attribute:NSLayoutAttributeLeft
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:layerView 
                                 attribute:NSLayoutAttributeWidth 
                                multiplier:0.5 
                                  constant:20.0]];

ここでの私の意図は、調整されたアンカー ポイントを持つビューの左端layerViewを、その幅の半分に 20 (スーパービューの左端からのインセット距離) を加えた値に設定することでした。

自動レイアウトでレイアウトされたビューで、ビューの位置を変更せずにアンカー ポイントを変更することは可能ですか? ハードコードされた値を使用して、回転ごとに制約を編集する必要がありますか? そうならないことを願っています。

ビューに変換を適用したときに正しい視覚効果が得られるように、アンカー ポイントを変更する必要があります。

ベストアンサー1

[編集:警告:以降の議論全体は、iOS 8 によって時代遅れになるか、少なくとも大幅に緩和される可能性があります。iOS 8 では、ビュー変換が適用されたときにレイアウトをトリガーするという間違いが起こらなくなる可能性があります。

自動レイアウトとビュー変換

自動レイアウトは、ビュー変換とはまったくうまく連携しません。私の理解する限り、その理由は、変換 (デフォルトの恒等変換以外) を持つビューのフレームをいじってはいけないということなのですが、それがまさに自動レイアウトが行うことです。自動レイアウトの動作は、layoutSubviews実行時にすべての制約を一気に調べ、それに応じてすべてのビューのフレームを設定するというものです。

言い換えれば、制約は魔法ではなく、単なる ToDo リストです。ToDolayoutSubviewsリストは で実行されます。フレームを設定することで実行されます。

これをバグとみなさずにはいられません。この変換をビューに適用すると、

v.transform = CGAffineTransformMakeScale(0.5,0.5);

ビューの中心が以前と同じ場所にあって、サイズが半分になった状態で表示されることを期待しています。しかし、制約によっては、まったくそのように表示されない場合もあります。

[実は、ここで 2 つ目の驚きがあります。ビューに変換を適用すると、レイアウトが即座に開始されます。これは別のバグのように思えます。あるいは、これが最初のバグの核心かもしれません。私が期待するのは、レイアウト時間まで、たとえばデバイスが回転するまで、変換を回避できることです。レイアウト時間までフレーム アニメーションを回避できるのと同じです。しかし、実際にはレイアウト時間は即時であり、これは間違っているようです。]

解決策1: 制約なし

現在の解決策の 1 つは、ビューに半永久的な変換を適用する場合 (一時的に変更するだけではない)、そのビューに影響するすべての制約を削除することです。残念ながら、この方法では通常、自動レイアウトがまだ実行されているため、ビューを画面から消えてしまいます。ビューを配置する場所を指定する制約がなくなるからです。そのため、制約を削除するだけでなく、ビューをtranslatesAutoresizingMaskIntoConstraintsYES に設定します。ビューは、自動レイアウトの影響を受けずに、従来の方法で動作するようになりました。(当然、自動レイアウトの影響を受けますが、暗黙の自動サイズ変更マスク制約により、その動作は自動レイアウト前とまったく同じになります。

解決策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: 代わりにレイヤー変換を使用する

ビュー変換の代わりにレイヤー変換を使用します。レイヤー変換はレイアウトをトリガーしないため、制約との即時の競合は発生しません。

たとえば、次の単純な「throb」ビュー アニメーションは、自動レイアウトではうまく動作しない可能性があります。

[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];

おすすめ記事