1. ホーム
  2. ios

[解決済み] オートレイアウトの場合、CALayerのアンカーポイントを調整するには?

2022-06-29 13:29:48

質問

注意事項 : この質問がなされたときから、状況は変化しています。 をご覧ください。 をご覧ください。


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

自動レイアウトの世界では、フレームを設定することはありませんが、制約は、ビューの位置を私たちが望む場所に調整するタスクに適していないように思われます。ビューを再配置するために制約をハックすることができますが、回転または他のサイズ変更イベントで、これらは再び無効になります。

レイアウト属性(leftとwidth)の無効な組み合わせ(")を作成するので、次の明るいアイデアはうまくいきません。

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 (スーパービューの左端から挿入したい距離) に設定することです。

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

ビューにトランスフォームを適用したときに、正しい視覚効果を得るために、アンカー ポイントを変更する必要があります。

どのように解決するのですか?

[編集 警告 iOS 8 では、ビュー変換が適用されたときにレイアウトをトリガーするような間違いはもう起こらないかもしれません]。

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

Autolayout はビューのトランスフォームとまったくうまく機能しません。その理由は、私の知る限り、変形(デフォルトの同一変形以外)を持つビューのフレームをいじることは想定されていないからです - しかし、それはまさに autolayout が行うことなのです。autolayout が機能する方法は layoutSubviews で、ランタイムはすべての制約を颯爽と通り抜け、それに応じてすべてのビューのフレームを設定します。

言い換えれば、制約は魔法ではなく、単なるTo-Doリストです。 layoutSubviews は、To-Doリストが実行される場所です。そして、それはフレームを設定することによって行われます。

私はこれをバグと見なすのを助けることができません。もし私がこのトランスフォームをビューに適用したら。

v.transform = CGAffineTransformMakeScale(0.5,0.5);

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

[実は、ここで 2 つ目の驚きがあります。ビューにトランスフォームを適用すると、すぐにレイアウトがトリガーされます。これは別のバグのように思えます。あるいは、最初のバグの核心かもしれません。私が期待するのは、少なくともレイアウト時間までは、デバイスを回転させるなどして変形を回避することができるということです。しかし、実際にはレイアウト時刻は即座であり、それは間違っているようです。]

解決策 1: 制約を設けない

現在の 1 つの解決策は、ビューに半永久的な変形を適用する場合 (単に一時的に動かすのではなく)、それに影響するすべての制約を削除することです。残念ながら、これは通常、ビューを画面から消してしまいます。なぜなら、自動レイアウトはまだ行われ、ビューをどこに置くかを教えてくれる制約がないからです。そこで、制約を取り除くことに加えて、私はビューの translatesAutoresizingMaskIntoConstraints をYESに設定しました。これでビューは旧来の方法で動作し、事実上、オートレイアウトの影響を受けなくなりました。(それは は明らかにautolayoutの影響を受けますが、暗黙のautoresizingマスクの制約により、その動作はautolayout以前と同じになります)。

解決策 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]];

結果として、ビューのフレームに影響を与える制約がない場合、autolayoutはビューのフレームに触れないということです。

解決策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];