13

私が以前に持っていた質問に基づいています。

ラベルを変換しようとする単純なボタン。私はそれを0.5だけ縮小したいのですが、それは機能しますが、何らかの理由でオブジェクトも移動します。ラベルは左上にジャンプしてから変形します。

- (IBAction)btnTest:(id)sender
{

    [UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        lblTest.transform = CGAffineTransformScale(lblTest.transform, 0.5f,0.5f);
    }completion:^(BOOL finished) {
        if(finished){
            NSLog(@"DONE");
        }
    }];
}
4

1 に答える 1

18

自動レイアウトを使用しているという質問から推測しています。自動レイアウトでは、先頭および/または上部の制約がある場合、 でスケーリングした後CGAffineTransformMakeScale、先頭/上部の制約が再適用され、コントロールが移動します制約がまだ満たされていることを確認するため。

自動レイアウトをオフにする (これは簡単な答えです) か、次のことができます。

  • まで待ちviewDidAppearます (IB で定義された制約が適用され、コントロールが必要な場所に配置され、そのcenterプロパティが信頼できるため);

  • 問題のコントロールができたので、次のようにプロパティの値を使用してcenter、先頭と上部の制約をNSLayoutAttributeCenterXand制約に置き換えます。NSLayoutAttributeCenterYcenterconstantNSLayoutConstraint

したがって:

// don't try to do this in `viewDidLoad`; do it in `viewDidAppear`, where the constraints
// have already been set

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self replaceLeadingAndTopWithCenterConstraints:self.imageView];
}

// Because our gesture recognizer scales the UIView, it's quite important to make
// sure that we don't have the customary top and leading constraints, but rather
// have constraints to the center of the view. Thus, this looks for leading constraint
// and if found, removes it, replacing it with a centerX constraint. Likewise if it
// finds a top constraint, it replaces it with a centerY constraint.
//
// Having done that, we can now do `CGAffineTransformMakeScale`, and it will keep the
// view centered when that happens, avoiding weird UX if we don't go through this
// process.

- (void)replaceLeadingAndTopWithCenterConstraints:(UIView *)subview
{
    CGPoint center = subview.center;

    NSLayoutConstraint *leadingConstraint = [self findConstraintOnItem:subview
                                                             attribute:NSLayoutAttributeLeading];
    if (leadingConstraint)
    {
        NSLog(@"Found leading constraint");

        [subview.superview removeConstraint:leadingConstraint];

        [subview.superview addConstraint:[NSLayoutConstraint constraintWithItem:subview
                                                                      attribute:NSLayoutAttributeCenterX
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:subview.superview
                                                                      attribute:NSLayoutAttributeTop
                                                                     multiplier:1.0
                                                                       constant:center.x]];
    }

    NSLayoutConstraint *topConstraint = [self findConstraintOnItem:subview
                                                         attribute:NSLayoutAttributeTop];

    if (topConstraint)
    {
        NSLog(@"Found top constraint");

        [subview.superview removeConstraint:topConstraint];

        [subview.superview addConstraint:[NSLayoutConstraint constraintWithItem:subview
                                                                      attribute:NSLayoutAttributeCenterY
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:subview.superview
                                                                      attribute:NSLayoutAttributeLeft
                                                                     multiplier:1.0
                                                                       constant:center.y]];
    }
}

- (NSLayoutConstraint *)findConstraintOnItem:(UIView *)item attribute:(NSLayoutAttribute)attribute
{
    // since we're looking for the item's constraints to the superview, let's
    // iterate through the superview's constraints

    for (NSLayoutConstraint *constraint in item.superview.constraints)
    {
        // I believe that the constraints to a superview generally have the
        // `firstItem` equal to the subview, so we'll try that first.

        if (constraint.firstItem == item && constraint.firstAttribute == attribute)
            return constraint;

        // While it always appears that the constraint to a superview uses the
        // subview as the `firstItem`, theoretically it's possible that the two
        // could be flipped around, so I'll check for that, too:

        if (constraint.secondItem == item && constraint.secondAttribute == attribute)
            return constraint;
    }

    return nil;
}

実装の詳細は、スケーリングするコントロールの制約をどのように定義したかによって異なる場合があります (私の場合、リーディングとトップはスーパービューに基づいていたため、簡単になりました)。これらの制約を削除し、中心に基づいて新しい制約を追加します。

上記のように、問題の制約を探して繰り返したくない場合は、IBOutlet代わりにトップとリーディングの制約に対して を定義すると、プロセスが大幅に簡素化されます。このサンプル コードは、さまざまな理由で を参照に使用できなかったプロジェクトから取得したIBOutletものNSLayoutConstraintです。ただしIBOutlet、制約の参照を使用する方が確実に簡単な方法です (自動レイアウトを使用する場合)。

たとえば、Interface Builder に移動すると、問題の制約を強調表示controlし、アシスタント エディターにドラッグして次のようにしますIBOutlet

制約を作成 IBOutlet

これを行うと、すべての制約を繰り返すのではなく、次のように言うことができます。

if (self.imageViewVerticalConstraint)
{
    [self.view removeConstraint:self.imageViewVerticalConstraint];

    // create the new constraint here, like shown above
}

率直に言って、Interface Builder にこれらのような制約をすぐに定義できる機能があればいいのにと思います (つまり、「コントロールをスーパービューの左に導く」制約、「コントロールの中心をスーパービューの左に」という制約ではなく) が、私はIBでできるとは思わないので、プログラムで制約を変更しています。しかし、このプロセスを経ることで、コントロールをスケーリングできるようになり、制約のためにコントロールが動き回ることがなくなりました。


0x7fffffff が指摘したように、レイヤーに a を適用すると、制約が自動的に適用されないため、ビューCATransform3DMakeScaleに適用した場合のように移動することはありません。CGAffineTransformMakeScaleただし、制約を再適用するために何かを行うと (setNeedsLayoutまたはUIViewオブジェクトを変更すると、制約が再適用される可能性があります)、ビューが移動します。したがって、制約が再適用される前にレイヤーの変換を同一に戻すと、「こっそり侵入」できる可能性がありますが、自動レイアウトをオフにするか、制約を修正するのがおそらく最も安全です。

于 2013-04-30T01:40:53.837 に答える