Pertanyaan Bagaimana saya mengubah kendala animate?


Saya memperbarui aplikasi lama dengan AdBannerView dan ketika tidak ada iklan, itu meluncur dari layar. Ketika ada iklan itu meluncur di layar. Hal-hal dasar.

Gaya lama, saya mengatur bingkai di blok animasi. Gaya baru, saya punya IBOutlet ke kendala yang menentukan posisi Y, dalam hal ini jaraknya dari bagian bawah superview, dan ubah konstanta.

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

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

Dan spanduk bergerak, persis seperti yang diharapkan, tetapi tidak ada animasi.

MEMPERBARUI: Saya menonton kembali video WWDC12 "Praktik Terbaik untuk Menguasai Tata Letak Otomatis"yang meliputi animasi. Ini membahas cara memperbarui batasan menggunakan CoreAnimation.

enter image description here enter image description here

Saya sudah mencoba dengan kode berikut, tetapi mendapatkan hasil yang sama persis.

- (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;
}

Di samping catatan, saya sudah memeriksanya berkali-kali dan ini dieksekusi pada utas utama.


832
2017-09-27 13:23


asal


Jawaban:


Dua catatan penting:

  1. Anda perlu menelepon layoutIfNeeded dalam blok animasi. Apple benar-benar merekomendasikan Anda untuk memanggilnya satu kali sebelum blok animasi untuk memastikan bahwa semua operasi tata letak yang tertunda telah selesai

  2. Anda perlu menyebutnya secara khusus di tampilan orang tua (misalnya. self.view), bukan tampilan anak yang memiliki batasan yang melekat padanya. Melakukannya akan diperbarui semua pandangan terbatas, termasuk menghidupkan pandangan lain yang mungkin dibatasi pada pandangan bahwa Anda mengubah batasan (mis. Lihat B dilampirkan ke bagian bawah Tampilan A dan Anda baru saja mengubah offset teratas View A dan Anda ingin View B untuk bernyawa dengannya)

Coba ini:

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

Swift 3

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

1505
2017-09-30 19:00



Saya menghargai jawaban yang diberikan, tapi saya pikir akan lebih baik untuk membawanya sedikit lebih jauh.

Animasi blok dasar dari dokumentasi

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

tapi ini benar-benar skenario yang sangat sederhana. Bagaimana jika saya ingin menghidupkan kendala subview melalui updateConstraints metode?

Blok animasi yang memanggil metode subview 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];

Metode updateConstraints ditimpa dalam subclass UIView dan harus memanggil super pada akhir metode.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

Panduan AutoLayout daun banyak yang diinginkan tetapi sangat berharga untuk dibaca. Saya sendiri menggunakan ini sebagai bagian dari UISwitch yang mengubah subview dengan sepasang UITextFields dengan animasi runtuhnya yang sederhana dan halus (0,2 detik). Kendala untuk subview sedang ditangani di UIView subclass updateConstraints method seperti yang dijelaskan di atas.


96
2018-01-22 00:35



Umumnya, Anda hanya perlu memperbarui kendala dan panggilan layoutIfNeeded di dalam blok animasi. Ini bisa mengubah .constant milik sebuah NSLayoutConstraint, menambahkan menghilangkan batasan (iOS 7), atau mengubah .active properti kendala (iOS 8 & 9).

Kode sampel:

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

Penyiapan Sampel:

Xcode Project so sample animation project.

Kontroversi

Ada beberapa pertanyaan tentang apakah kendala harus diubah sebelum blok animasi, atau dalam itu (lihat jawaban sebelumnya).

Berikut ini adalah percakapan Twitter antara Martin Pilkington yang mengajar iOS, dan Ken Ferry yang menulis Tata Letak Otomatis. Ken menjelaskan bahwa meskipun mengubah konstanta di luar blok animasi mungkin saat ini bekerja, itu tidak aman dan mereka harus benar-benar berubah dalam blok animasi. https://twitter.com/kongtomorrow/status/440627401018466305

Animasi:

Proyek Contoh

Berikut ini proyek sederhana yang menunjukkan bagaimana tampilan dapat dianimasikan. Ini menggunakan Objective C dan menjiwai tampilan dengan mengubah .active milik beberapa kendala. https://github.com/shepting/SampleAutoLayoutAnimation


58
2017-11-13 00:33



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

32
2017-12-23 14:29



Storyboard, Kode, Tips, dan beberapa Gotchas

Jawaban lain baik-baik saja tetapi yang satu ini menyoroti beberapa terobosan yang cukup penting dari menganimasi kendala menggunakan contoh terbaru. Saya mengalami banyak variasi sebelum saya menyadari yang berikut:

Buat batasan yang ingin Anda targetkan ke variabel Kelas untuk menahan referensi yang kuat. Dalam Swift saya menggunakan variabel malas:

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!
}()

Setelah beberapa percobaan, saya mencatat bahwa seseorang HARUS mendapatkan batasan dari tampilan ATAS (alias superview) dua pandangan di mana batasan didefinisikan. Dalam contoh di bawah ini (baik MNGStarRating dan UIWebView adalah dua jenis item yang saya buat batasan antara, dan mereka subview dalam self.view).

Filter Chaining

Saya memanfaatkan metode filter Swift untuk memisahkan kendala yang diinginkan yang akan berfungsi sebagai titik infleksi. Satu juga bisa menjadi jauh lebih rumit tetapi filter melakukan pekerjaan yang bagus di sini.

Batasan Animasi Menggunakan Swift

Nota Bene - Contoh ini adalah solusi storyboard / kode dan mengasumsikan   satu telah membuat batasan standar di storyboard. Satu kemudian bisa   menganimasikan perubahan menggunakan kode.

Dengan asumsi Anda membuat properti untuk memfilter dengan kriteria yang akurat dan sampai ke titik perubahan tertentu untuk animasi Anda (tentu saja Anda juga dapat memfilter array dan mengulang melalui jika Anda memerlukan beberapa kendala):

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!
}()

....

Lain kali...

@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

        }
    }
}

Banyak tikungan yang salah

Catatan ini benar-benar seperangkat tips yang saya tulis sendiri. Saya melakukan semua larangan secara pribadi dan menyakitkan. Semoga panduan ini dapat membantu orang lain.

  1. Watch out for zPositioning. Terkadang ketika tidak ada yang tampaknya terjadi, Anda harus menyembunyikan beberapa tampilan lain atau menggunakan tampilan debugger untuk menemukan tampilan animasi Anda. Saya bahkan menemukan kasus di mana Runtime Buatan Pengguna Atribut hilang dalam xml storyboard dan mengarah ke animasi view being covered (saat bekerja).

  2. Selalu luangkan waktu sebentar untuk membaca dokumentasi (baru dan lama), Cepat Bantuan, dan header. Apple terus membuat banyak perubahan menjadi lebih baik mengelola batasan AutoLayout (lihat tampilan tumpukan). Atau setidaknya itu AutoLayout Cookbook. Perlu diingat bahwa terkadang solusi terbaik ada dalam dokumentasi / video yang lebih lama.

  3. Bermain-main dengan nilai-nilai dalam animasi dan pertimbangkan untuk menggunakan varian animateWithDuration lainnya.

  4. Jangan hardcode nilai tata letak spesifik sebagai kriteria untuk menentukan perubahan pada konstanta lainnya, sebagai gantinya gunakan nilai yang memungkinkan Anda untuk tentukan lokasi tampilan. CGRectContainsRectadalah satu contoh

  5. Jika diperlukan, jangan ragu untuk menggunakan margin tata letak terkait pandangan yang berpartisipasi dalam definisi batasan let viewMargins = self.webview.layoutMarginsGuide: adalah contoh
  6. Jangan lakukan pekerjaan yang tidak perlu Anda lakukan, semua pandangan dengan batasan pada storyboard memiliki kendala yang melekat pada properti self.viewName.constraints
  7. Ubah prioritas Anda untuk setiap batasan menjadi kurang dari 1000. Saya setel menambang hingga 250 (rendah) atau 750 (tinggi) di papan cerita; (jika Anda mencoba untuk mengubah prioritas 1000 untuk apa pun di kode maka aplikasi akan crash karena 1000 diperlukan)
  8. Pertimbangkan untuk tidak segera mencoba menggunakan ActivConstraints dan nonaktifkanKonstraints (mereka memiliki tempat mereka tetapi ketika hanya belajar atau jika Anda menggunakan storyboard menggunakan ini mungkin berarti Anda melakukan terlalu banyak ~ mereka memiliki tempat meskipun seperti yang terlihat di bawah)
  9. Pertimbangkan untuk tidak menggunakan addConstraints / removeConstraints kecuali Anda benar-benar menambahkan kendala baru dalam kode. Saya menemukan bahwa kebanyakan kali saya tata letak tampilan di papan cerita dengan batasan yang diinginkan (penempatan tampilan layar off), kemudian dalam kode, saya menghidupkan batasan yang sebelumnya dibuat di storyboard untuk memindahkan pandangan sekitar.
  10. Saya menghabiskan banyak waktu yang terbuang untuk membangun kendala dengan yang baru Kelas dan subclass NSAnchorLayout. Ini bekerja dengan baik tetapi Butuh waktu beberapa saat untuk menyadari bahwa semua kendala yang saya butuhkan sudah ada di storyboard. Jika Anda membangun batasan dalam kode maka pasti menggunakan metode ini untuk menggabungkan batasan Anda:

Contoh Cepat Solusi untuk HINDARI saat menggunakan Storyboards

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

Jika Anda lupa salah satu dari tips ini atau yang lebih sederhana seperti di mana harus menambahkan tata letak Jika Diperlukan, kemungkinan besar tidak akan terjadi: Dalam hal ini Anda mungkin memiliki solusi setengah matang seperti ini:

NB - Luangkan waktu sejenak untuk membaca Bagian AutoLayout di bawah ini dan   panduan asli. Ada cara untuk menggunakan teknik-teknik ini untuk melengkapi   Animator Dinamis Anda.

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
        }

    }

Snippet dari Panduan AutoLayout (perhatikan cuplikan kedua adalah untuk menggunakan OS X). BTW - Ini tidak lagi dalam panduan saat ini sejauh yang saya bisa lihat.  Teknik yang disukai terus berkembang.

Perubahan Animasi Dibuat oleh Tata Letak Otomatis

Jika Anda membutuhkan kontrol penuh atas perubahan animasi yang dibuat oleh Tata Letak Otomatis, Anda harus membuat kendala Anda berubah secara terprogram. Konsep dasarnya sama untuk iOS dan OS X, tetapi ada beberapa perbedaan kecil.

Di aplikasi iOS, kode Anda akan terlihat seperti berikut:

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

Di OS X, gunakan kode berikut saat menggunakan animasi yang didukung oleh layer:

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

Saat Anda tidak menggunakan animasi yang didukung oleh layer, Anda harus menganimasikan konstanta menggunakan animator kendala:

[[constraint animator] setConstant:42];

Bagi mereka yang belajar secara visual lebih baik, periksa ini lebih awal video dari Apple.

Perhatikan baik-baik

Seringkali dalam dokumentasi ada catatan kecil atau potongan kode yang mengarah ke ide-ide yang lebih besar. Misalnya melampirkan kendala tata letak otomatis untuk animator dinamis adalah ide besar.

Good Luck dan May the Force bersamamu.


10
2017-12-11 01:49



Solusi Swift 4

UIView.animate

Tiga langkah sederhana:

  1. Ubah batasan, misalnya:

    heightAnchor.constant = 50
    
  2. Katakan yang mengandung view bahwa tata letaknya kotor dan autolayout harus menghitung ulang tata letak:

    self.view.setNeedsLayout()
    
  3. Dalam blok animasi, beri tahu tata letak untuk menghitung ulang tata letak, yang setara dengan pengaturan bingkai secara langsung (dalam hal ini autolayout akan menetapkan bingkai):

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

Lengkapi contoh paling sederhana:

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

Sidenote

Ada langkah ke 0 opsional - sebelum mengubah batasan yang mungkin ingin Anda panggil self.view.layoutIfNeeded() untuk memastikan bahwa titik awal untuk animasi adalah dari negara dengan batasan lama diterapkan (jika ada beberapa perubahan kendala lain yang seharusnya tidak disertakan dalam animasi):

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

Karena dengan iOS 10 kami mendapat mekanisme menjiwai baru - UIViewPropertyAnimator, kita harus tahu bahwa pada dasarnya mekanisme yang sama berlaku untuk itu. Langkah-langkahnya pada dasarnya sama:

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

Sejak animator adalah enkapsulasi animasi, kita dapat tetap mengacu dan memanggilnya nanti. Namun, karena di blok animasi kita hanya mengatakan autolayout untuk menghitung ulang frame, kita harus mengubah batasan sebelum memanggil startAnimation. Karenanya sesuatu seperti ini mungkin terjadi:

// 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()

Urutan mengubah kendala dan memulai animator adalah penting - jika kita hanya mengubah batasan dan membiarkan animator kita untuk beberapa titik kemudian, siklus redraw berikutnya dapat meminta perhitungan ulang otomatis dan perubahan tidak akan dianimasikan.

Juga, ingat bahwa animator tunggal tidak dapat digunakan kembali - setelah Anda menjalankannya, Anda tidak dapat "menjalankan kembali" itu. Jadi saya kira tidak ada alasan yang bagus untuk menjaga animator, kecuali kami menggunakannya untuk mengendalikan animasi interaktif.


7
2018-02-16 11:09



Solusi cepat:

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

6
2017-08-11 08:42



Solusi Kerja 100% Swift 3.1

Saya telah membaca semua jawaban dan ingin berbagi kode dan hierarki garis yang telah saya gunakan di semua aplikasi saya untuk menganimasikannya dengan benar, Beberapa solusi di sini tidak berfungsi, Anda harus memeriksanya pada perangkat yang lebih lambat misalnya iPhone 5 pada saat ini.

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
2018-06-12 08:41