web-dev-qa-db-ja.com

拘束の変更をアニメートする方法

古いアプリをAdBannerViewで更新していますが、広告が表示されていない場合は画面からスライドします。広告があるとそれは画面上でスライドします。基本的なもの.

古いスタイルで、アニメーションブロックにフレームを設定しました。新しいスタイル、私はY位置を決定する自動レイアウト制約へのIBOutletを持っています、この場合それはスーパービューの底からの距離です、そして定数を修正します:

- (void)moveBannerOffScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = -32;
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = 0;
    }];
    bannerIsVisible = TRUE;
}

そして、バナーは予想通りに動きますが、no animationです。

更新: 私は再視聴しました WWDC 12トーク自動レイアウトをマスターするためのベストプラクティス アニメーションをカバーしています。 CoreAnimation を使用して制約を更新する方法について説明します。

私は以下のコードを試しましたが、まったく同じ結果が得られます。

- (void)moveBannerOffScreen {
    _addBannerDistanceFromBottomConstraint.constant = -32;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    _addBannerDistanceFromBottomConstraint.constant = 0;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = TRUE;
}

ちなみに、私は何度もチェックしてきましたが、これはmainスレッドで実行されています。

904
DBD

2つの重要な注意事項

  1. アニメーションブロック内でlayoutIfNeededを呼び出す必要があります。実際には、保留中のすべてのレイアウト操作が完了していることを確認するために、アニメーションブロックの前に一度呼び出すことをお勧めします。

  2. 制約を付けた子ビューではなく、 親ビュー self.viewなど)で明示的に呼び出す必要があります。そうすることで、 all 制約ビューを更新します。これには、制約を変更したビューに制約されている可能性のある他のビューのアニメーション化も含まれます。オフセットして、ビューBをそれに合わせてアニメートしたい場合)

これを試して:

Objective-C

- (void)moveBannerOffScreen {
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = -32;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen { 
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = 0;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = TRUE;
}

スイフト3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}
1602
g3rv4

私は提供された答えに感謝します、しかし私はそれをもう少しそれを取ることはいいことだと思います。

ドキュメンテーションからの基本的なブロックアニメーション

[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
     // Make all constraint changes here
     [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];

しかし、これは非常に単純化したシナリオです。 updateConstraintsメソッドでサブビューの制約をアニメートしたい場合はどうすればいいですか?

サブビューのupdateConstraintsメソッドを呼び出すアニメーションブロック

[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
    [self.view layoutIfNeeded];
} completion:nil];

UpdateConstraintsメソッドはUIViewサブクラスでオーバーライドされ、メソッドの最後にsuperを呼び出す必要があります。

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

AutoLayoutガイド は望ましいことをたくさん残しますが、読む価値があります。私自身、これをUISwitchの一部として使用しています。これは、単純で微妙な折りたたみアニメーション(長さ0.2秒)を使って1組のUITextFieldでサブビューを切り替えるものです。サブビューの制約は、上記のUIViewサブクラスのupdateConstraintsメソッドで処理されています。

104

通常、制約を更新し、アニメーションブロック内でlayoutIfNeededを呼び出すだけです。これは、NSLayoutConstraint.constantプロパティの変更、削除制約の追加(iOS 7)、または制約の.activeプロパティの変更(iOS 8および9)のいずれかです。

サンプルコード:

[UIView animateWithDuration:0.3 animations:^{
    // Move to right
    self.leadingConstraint.active = false;
    self.trailingConstraint.active = true;

    // Move to bottom
    self.topConstraint.active = false;
    self.bottomConstraint.active = true;

    // Make the animation happen
    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];
}];

サンプル設定:

Xcode Project so sample animation project.

論争

制約を変更する必要があるかどうかbeforeアニメーションブロック、またはinsideit(前の回答を参照)。

以下は、iOSを教えるMartin PilkingtonとAuto Layoutを書いたKen Ferryとの間のTwitterの会話です。ケンは、アニメーションブロックの外で定数を変更すると現在動作するかもしれませんが、安全ではなく、実際に変更する必要があると説明していますinsideアニメーションブロック。 https://Twitter.com/kongtomorrow/status/440627401018466305

アニメーション:

サンプルプロジェクト

これは、ビューをアニメーション化する方法を示す簡単なプロジェクトです。 Objective Cを使用し、いくつかの制約の.activeプロパティを変更することでビューをアニメーション化します。 https://github.com/shepting/SampleAutoLayoutAnimation

67
Steven Hepting
// Step 1, update your constraint
self.myOutletToConstraint.constant = 50; // New height (for example)

// Step 2, trigger animation
[UIView animateWithDuration:2.0 animations:^{

    // Step 3, call layoutIfNeeded on your animated view's parent
    [self.view layoutIfNeeded];
}];
33
John Erck

Swift 4のソリューション

UIView.animate

3つの簡単なステップ

  1. 制約を変更します。例:

    heightAnchor.constant = 50
    
  2. そのレイアウトが汚れていて、自動レイアウトがレイアウトを再計算する必要があることを含むviewに伝えます。

    self.view.setNeedsLayout()
    
  3. アニメーションブロックで、レイアウトを再計算するようにレイアウトに指示します。これは、フレームを直接設定するのと同じです(この場合、自動レイアウトはフレームを設定します)。

    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }
    

最も簡単な例:

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

サイドノート

オプションの0番目のステップがあります - 制約を変更する前にself.view.layoutIfNeeded()を呼び出して、アニメーションの開始点が古い制約が適用された状態であることを確認します(他の制約の変更が含まれていない場合)。アニメーション):

otherConstraint.constant = 30
// this will make sure that otherConstraint won't be animated but will take effect immediately
self.view.layoutIfNeeded()

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

UIViewPropertyAnimator

IOS 10では、新しいアニメーションメカニズムUIViewPropertyAnimatorを手に入れたので、基本的に同じメカニズムがそれに適用されることを知っておくべきです。手順は基本的に同じです。

heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}
animator.startAnimation()

animatorはアニメーションのカプセル化なので、それを参照し続けて後で呼び出すことができます。しかし、アニメーションブロックでは自動レイアウトにフレームを再計算するように指示しているだけなので、startAnimationを呼び出す前に制約を変更する必要があります。したがって、このようなことが可能です。

// prepare the animator first and keep a reference to it
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}

// at some other point in time we change the constraints and call the animator
heightAnchor.constant = 50
self.view.setNeedsLayout()
animator.startAnimation()

制約を変更してアニメータを起動する順序は重要です。制約を変更してアニメータを後で使用する場合は、次の再描画サイクルで自動レイアウト再計算を呼び出すことができ、変更はアニメートされません。

また、1人のアニメーターは再利用できません - 一度実行すると「再実行」することはできません。それで、私たちがインタラクティブなアニメーションをコントロールするためにそれを使わない限り、私はアニメーターをあちこちに保つために本当に良い理由がないと思います。

19
Milan Nosáľ

ストーリーボード、コード、ヒント、そしていくつかのGotcha

他の答えは大丈夫ですが、これは最近の例を使用して制約をアニメートすることのいくつかのかなり重要な落とし穴を強調しています。次のことに気付く前に、さまざまなバリエーションを経験しました。

強力な参照を保持するために、ターゲットとする制約をクラス変数にします。 Swiftでは、遅延変数を使用しました。

lazy var centerYInflection:NSLayoutConstraint = {
       let temp =  self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first
        return temp!
}()

いくつかの実験の後、私はビュー(別名スーパービュー)から制約が定義されている2つのビューから制約を取得しなければなりません。私はその間に制約を作成しています、そしてそれらはself.view内のサブビューです。

フィルタチェーン

私はSwiftのフィルター法を利用して、変曲点として機能する目的の制約を分離します。もっと複雑になるかもしれませんが、filterはここではいい仕事をします。

Swiftを使用した制約のアニメーション化

Nota Bene - この例はストーリーボード/コードソリューションであり、ストーリーボードでデフォルトの制約が設定されていると仮定しています。コードを使用して変更をアニメートすることができます。

正確な基準でフィルタリングし、アニメーションの特定の変曲点に到達するプロパティを作成するとします(もちろん、配列をフィルタリングして複数の制約が必要な場合はループスルーすることもできます)。

lazy var centerYInflection:NSLayoutConstraint = {
    let temp =  self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first
    return temp!
}()

…….

今度いつか...

@IBAction func toggleRatingView (sender:AnyObject){

    let aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)

    self.view.layoutIfNeeded()


    //Use any animation you want, I like the bounce in springVelocity...
    UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.75, options: [.CurveEaseOut], animations: { () -> Void in

        //I use the frames to determine if the view is on-screen
        if CGRectContainsRect(self.view.frame, self.ratingView.frame) {

            //in frame ~ animate away
            //I play a sound to give the animation some life

            self.centerYInflection.constant = aPointAboveScene
            self.centerYInflection.priority = UILayoutPriority(950)

        } else {

            //I play a different sound just to keep the user engaged
            //out of frame ~ animate into scene
            self.centerYInflection.constant = 0
            self.centerYInflection.priority = UILayoutPriority(950)
            self.view.setNeedsLayout()
            self.view.layoutIfNeeded()
         }) { (success) -> Void in

            //do something else

        }
    }
}

多くの間違ったターン

これらのメモは本当に私が自分自身のために書いたヒントのセットです。私は個人的にそして痛々しいことにすべての禁止事項をやりました。うまくいけば、このガイドは他の人を倹約することができます。

  1. ZPositioningに気をつけてください。何も起きていないように見える場合は、他のビューを非表示にするか、ビューデバッガを使用してアニメーションビューを見つける必要があります。ストーリーボードのxmlでユーザー定義のランタイム属性が失われ、(動作中に)アニメーションビューが隠されることになったことさえあります。

  2. ドキュメント(新旧)、クイックヘルプ、およびヘッダーを読むには、少々時間がかかります。 AppleはAutoLayout制約をよりよく管理するために多くの変更を続けています(スタックビューを参照)。または少なくとも AutoLayout Cookbook 。時には最善の解決策が古いドキュメント/ビデオにあることを覚えておいてください。

  3. アニメーションの値をいろいろ試して、他のanimateWithDurationバリアントの使用を検討してください。

  4. 特定のレイアウト値を他の定数への変更を判断するための基準としてハードコードしないでください。代わりに、ビューの場所を判断できる値を使用してください。 CGRectContainsRectはその一例です

  5. 必要に応じて、制約定義let viewMargins = self.webview.layoutMarginsGuideに参加しているビューに関連付けられているレイアウト余白を使用することを躊躇しないでください。
  6. あなたがする必要のない仕事をしないでください、ストーリーボード上の制約を持つすべてのビューは、プロパティself.viewName.constraintsに結び付けられた制約を持っています
  7. 制約条件の優先順位を1000未満に変更します。ストーリーボードで、私の設定を250(最低)または750(最高)にします。 (あなたがコード内の何かに1000の優先順位を変更しようとすると、1000が必要であるため、アプリはクラッシュします)
  8. ActivateConstraintsとdeactivateConstraintsをすぐに使用しようとしないことを検討してください(それらはそれらの場所を持っていますが、これらを使用してストーリーボードを使用しているかのような場合
  9. 実際にコードに新しい制約を追加しない限り、addConstraints/removeConstraintsを使用しないでください。ほとんどの場合、ストーリーボード内のビューを目的の制約でレイアウトし(ビューを画面外に配置し)、コード内で、ストーリーボードで以前に作成した制約をアニメートしてビューを移動します。
  10. 新しいNSAnchorLayoutクラスとサブクラスを使用して制約を構築するために多くの時間を費やしました。これらはうまく機能しますが、私が必要とするすべての制約がストーリーボードにすでに存在していることに気付くまでに時間がかかりました。コードで制約を構築する場合は、最も確実にこのメソッドを使用して制約を集約してください。

ストーリーボード使用時のAVOIDに対する解決策のクイックサンプル

private var _nc:[NSLayoutConstraint] = []
    lazy var newConstraints:[NSLayoutConstraint] = {

        if !(self._nc.isEmpty) {
            return self._nc
        }

        let viewMargins = self.webview.layoutMarginsGuide
        let minimumScreenWidth = min(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height)

        let centerY = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.webview.centerYAnchor)
        centerY.constant = -1000.0
        centerY.priority = (950)
        let centerX =  self.ratingView.centerXAnchor.constraintEqualToAnchor(self.webview.centerXAnchor)
        centerX.priority = (950)

        if let buttonConstraints = self.originalRatingViewConstraints?.filter({

            ($0.firstItem is UIButton || $0.secondItem is UIButton )
        }) {
            self._nc.appendContentsOf(buttonConstraints)

        }

        self._nc.append( centerY)
        self._nc.append( centerX)

        self._nc.append (self.ratingView.leadingAnchor.constraintEqualToAnchor(viewMargins.leadingAnchor, constant: 10.0))
        self._nc.append (self.ratingView.trailingAnchor.constraintEqualToAnchor(viewMargins.trailingAnchor, constant: 10.0))
        self._nc.append (self.ratingView.widthAnchor.constraintEqualToConstant((minimumScreenWidth - 20.0)))
        self._nc.append (self.ratingView.heightAnchor.constraintEqualToConstant(200.0))

        return self._nc
    }()

これらのヒントの1つを忘れた場合、またはlayoutIfNeededを追加する場所などのより単純なヒントを忘れた場合は、ほとんど何も起こらないでしょう。この場合、次のようなハーフベイクの解決策があります。

注意 - 下のオートレイアウトセクションとオリジナルのガイドを読んでください。あなたのダイナミックアニメーターを補うためにこれらのテクニックを使う方法があります。

UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 1.0, options: [.CurveEaseOut], animations: { () -> Void in

            //
            if self.starTopInflectionPoint.constant < 0  {
                //-3000
                //offscreen
                self.starTopInflectionPoint.constant = self.navigationController?.navigationBar.bounds.height ?? 0
                self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)

            } else {

                self.starTopInflectionPoint.constant = -3000
                 self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)
            }

        }) { (success) -> Void in

            //do something else
        }

    }

オートレイアウトガイドのスニペット(2番目のスニペットはOS Xを使用するためのものです)。 BTW - これは私が見ることができる限り現在のガイドにはもうありません。好ましいテクニックは進化し続けています。

自動レイアウトによる変更のアニメーション化

自動レイアウトによる変更のアニメーション化を完全に制御する必要がある場合は、プログラムで制約を変更する必要があります。基本的な概念はiOSとOS Xの両方で同じですが、いくつかの小さな違いがあります。

IOSアプリでは、コードは次のようになります。

[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
     // Make all constraint changes here
     [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];

OS Xでは、レイヤー付きアニメーションを使用するときは次のコードを使用します。

[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
     [context setAllowsImplicitAnimation: YES];
     // Make all constraint changes here
     [containerView layoutSubtreeIfNeeded];
}];

レイヤバックアニメーションを使用していない場合は、コンストレインのアニメーターを使用して定数をアニメートする必要があります。

[[constraint animator] setConstant:42];

視覚的によく学ぶ人のために、この初期の{ Appleからのビデオ をチェックしてください。

注意を払う

ドキュメンテーションの中には、より大きなアイデアにつながる小さなメモやコードの断片があることがよくあります。たとえば、動的なアニメーターに自動レイアウト制約を付けることは大きなアイデアです。

幸運を祈り、力があなたと共にあるように。

12
Tommie C.

迅速な解決策:

yourConstraint.constant = 50
UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded)
6
Nazariy Vlizlo

ワーキングソリューション100% Swift 3.1

私はすべての答えを読み、それらを正しくアニメーション化するために私のすべてのアプリケーションで使用したコードと行の階層を共有したいと思います。ここでの解決策はうまくいかない。

self.view.layoutIfNeeded() // Force lays of all subviews on root view
UIView.animate(withDuration: 0.5) { [weak self] in // allowing to ARC to deallocate it properly
       self?.tbConstraint.constant = 158 // my constraint constant change
       self?.view.layoutIfNeeded() // Force lays of all subviews on root view again.
}
5
C0mrade

私は制約をアニメートしようとしていました、そして、良い説明を見つけることは本当に容易ではありませんでした。

他の答えが言っていることは全く本当です:あなたは[self.view layoutIfNeeded];の中でanimateWithDuration: animations:を呼び出す必要があります。ただし、もう1つの重要な点は、アニメーション化するすべてのNSLayoutConstraintへのポインタを用意することです。

GitHubで例を作成しました

4
Gabriel.Massana

Xcode 8.3.3を使ったSwift 3のための実用的でテスト済みのソリューション:

self.view.layoutIfNeeded()
self.calendarViewHeight.constant = 56.0

UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
        self.view.layoutIfNeeded()
    }, completion: nil)

Self.calendarViewHeightはcustomView(CalendarView)と呼ばれる制約であることを忘れないでください。 self.calendarViewではなくself.viewで.layoutIfNeeded()を呼び出しました。

この助けを願っています。

3
Edoardo Vicoli

これについて記事の話があります: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

それで、彼はこのようにコーディングしました:

- (void)handleTapFrom:(UIGestureRecognizer *)gesture {
    if (_isVisible) {
        _isVisible = NO;
        self.topConstraint.constant = -44.;    // 1
        [self.navbar setNeedsUpdateConstraints];  // 2
        [UIView animateWithDuration:.3 animations:^{
            [self.navbar layoutIfNeeded]; // 3
        }];
    } else {
        _isVisible = YES;
        self.topConstraint.constant = 0.;
        [self.navbar setNeedsUpdateConstraints];
        [UIView animateWithDuration:.3 animations:^{
            [self.navbar layoutIfNeeded];
        }];
    }
}

それが役に立てば幸い。

2
D.D.

制約アニメーションのコンテキストでは、keyboard_opened通知内で制約をすぐにアニメーション化した特定の状況について説明します。

制約は、テキストフィールドからコンテナの上部までの上部スペースを定義しました。キーボードが開いたら、定数を2で割るだけです。

私はキーボード通知内で一貫した滑らかな拘束アニメーションを直接達成することができませんでした。アニメーション化せずに、ビューの約半分は新しい位置にジャンプします。

キーボードを開くと、レイアウトが追加される可能性があります。単純なdispatch_afterブロックを10ミリ秒の遅延で追加すると、アニメーションは毎回実行されます - ジャンプしません。

1
ivanferdelja