ios updateviewconstraints 制約の変更をどのようにアニメーション化するのですか?




viewdidlayoutsubviews (11)

2つの重要なメモ:

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

  2. 親ビュー (例えば、 self.view )でそれを特別に呼び出す必要があります。子ビューには、制約が付いていません。 そうすることで、制約を変更したビューに制約される可能性のある他のビューをアニメーション化するなど、 すべての制約付きビューが更新されます(たとえば、ビューAの最下部にビューBが付いていて、それをアニメートする)

これを試して:

目標-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()
}

私は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;
}

そして、バナーは予想どおりに動きますが、アニメーションはありません。

更新:アニメーションをカバーするWWDC12のビデオ「 マスターレイアウト自動レイアウトのベストプラクティス 」を再見ました。 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;
}

私は何度もチェックをしており、これはメインスレッドで実行されています。


// 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];
}];

一般に、制約を更新して、アニメーションブロック内でlayoutIfNeededを呼び出すlayoutIfNeededます。 .constantプロパティを変更したり、remove constraints(iOS 7)を追加したり、constraint(iOS 8&9)の.activeプロパティを変更することが.activeます。

サンプルコード:

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

サンプルセットアップ:

論争

アニメーションブロックの 、またはアニメーションブロックの内側で制約を変更する必要があるかどうかについてはいくつか質問があります(前の回答を参照)。

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

アニメーション:

サンプルプロジェクト

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


これに関する記事がありhttp://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-washttp://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was : 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];
        }];
    }
}

それが役に立てば幸い。


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

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

[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 Guideは多くのことを望みますが、読む価値があります。 私自身はこれを、 UISwitch一部として使用しています。これは、 UITextFieldのペアを使用してサブビューを切り替え、シンプルで繊細な崩壊アニメーション(0.2秒)です。 サブビューの制約は、上記のUIViewサブクラスのupdateConstraintsメソッドで処理されています。


ストーリーボード、コード、ヒント、いくつかのゴチチャ

他の答えはちょうどいいですが、最近の例を使って制約をアニメーション化するかなり重要な点を強調しています。 私は以下のことを理解する前に、多くのバリエーションを経験しました。

クラス変数にターゲットとする制約を強参照を保持するようにします。 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!
}()

いくつかの実験の後、私は、 ABOVEビュー(別名スーパービュー)から制約が定義されている2つのビューを取得しなければならないことに注意しました。 下の例では(MNGStarRatingとUIWebViewの両方が、制約を作成する2つのタイプのアイテムで、self.view内のサブビューです)。

フィルターチェイン

私はSwiftのフィルタ法を利用して、変曲点となる所望の制約を分離します。 1つはまた、はるかに複雑になる可能性がありますが、フィルタはここで素晴らしい仕事をします。

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. ドキュメント(新旧)、クイックヘルプ、ヘッダーを読むには1分ほどかかります。 AppleはAutoLayout制約をよりよく管理するために多くの変更を続けています(スタックビューを参照)。 または少なくともAutoLayout Cookbook 。 時には最善の解決策が古いドキュメンテーション/ビデオにあることに注意してください。

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

  4. 特定のレイアウト値を他の定数の変更を判断する基準としてハードコードしないで、代わりにビューの位置を決定できる値を使用します。 CGRectContainsRectは一例です

  5. 必要に応じて、制約定義に参加しているビューに関連付けられたレイアウトマージンを使用するのをためらってlet viewMargins = self.webview.layoutMarginsGuide :on example
  6. あなたがする必要はありません仕事をしないで、ストーリーボード上の制約を持つすべてのビューは、プロパティself.viewName.constraintsに接続されている制約があります
  7. 制約の優先順位を1000未満に変更します。私はストーリーボードで250(低)または750(高)に鉱山を設定しました。 (1000の優先順位をコード内の何かに変更しようとすると、1000が必要なのでアプリがクラッシュする)
  8. activateConstraintsとdeactivateConstraintsを使用しようとすぐには考えないでください(彼らには場所がありますが、学習している場合や、ストーリーボードを使用している場合は、おそらくあなたがやっていることを意味します〜
  9. 実際にコードに新しい制約を追加しない限り、addConstraints / removeConstraintsは使用しないでください。 ほとんどの場合、ストーリーボードのビューを希望の制約(レイアウトを画面外に置く)でレイアウトし、次にコード内で以前にストーリーボードで作成した制約をアニメーション化してビューを移動させました。
  10. 私は新しいNSAnchorLayoutクラスとサブクラスで制約を築くために多くの無駄な時間を費やしました。 これらはうまく動作しますが、私が必要としていたすべての制約がストーリーボードにすでに存在していることを理解するまでにはしばらく時間がかかりました。 コードに制約を作成する場合は、このメソッドを使用して制約を集約するのが最も確実です。

ストーリーボード使用時の回避策のクイックサンプル

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をどこに追加するかなどのよりシンプルなものを忘れた場合は、ほとんど何も起こりません:この場合、半分のベーキングソリューションがあるかもしれません:

注意 - 下のAutoLayoutセクションと元のガイドを読んでください。 これらのテクニックを使用してダイナミックアニメータを補う方法があります。

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
        }

    }

AutoLayout Guideのスニペット(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];

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

注意を払う

ドキュメントには、大きなアイデアにつながる小さなメモやコードが含まれていることがよくあります。 例えば、自動レイアウト制約をダイナミックアニメータに付けることは大きなアイデアです。

幸運と力があなたと一緒にありますように。


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.viewで.layoutIfNeeded()を呼び出し、self.calendarViewではなく

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


私はConstraintsをアニメーション化しようとしていましたが、良い説明を見つけるのは簡単ではありませんでした。

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

私はGitHubで例を作りました


迅速なソリューション:

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

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

私はすべての答えを読んで、私はすべての私のアプリケーションで正しくアニメーション化するために使用しているコードと階層の階層を共有したい、ここでいくつかのソリューションは動作していない、あなたは遅いデバイス例えば、この時点で5を確認する必要があります。

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.
}

スウィフト4ソリューション

UIView.animate

3つの簡単なステップ:

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

    heightAnchor.constant = 50
    
  2. そのレイアウトが汚れていること、およびautolayoutがレイアウトを再計算する必要があることを含むviewを教えてください:

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

    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という新しいアニメーションメカニズムが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はアニメーションをカプセル化したものなので、それを参照して後で呼び出すことができます。 しかし、アニメーションブロックでは、フレームを再計算するようにautolayoutに指示するだけなので、 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つのアニメーターは再利用不可能であることを覚えておいてください。実行した後、再実行することはできません。 ですから、アニメーターをインタラクティブなアニメーションを制御するために使用しない限り、アニメーターを維持するのは本当に良い理由ではないと思います。





autolayout