Muncul! Ditranskripsikan di iOS

Halo, Habr! Semua orang suka aplikasi responsif. Bahkan lebih baik ketika mereka memiliki animasi yang relevan. Dalam artikel ini saya akan memberi tahu dan menunjukkan dengan semua "daging" cara menunjukkan, menyembunyikan, memutar, memutar, dan melakukan semuanya dengan benar dengan layar pop-up.



Awalnya, saya ingin menulis artikel yang menyatakan bahwa pada iOS 10 muncul UIViewPropertyAnimator nyaman yang menyelesaikan masalah animasi yang terputus. Sekarang mereka dapat dihentikan, dibalik, dilanjutkan atau dibatalkan. Apple menyebut antarmuka ini Fluid .

Tapi kemudian saya menyadari: sulit untuk berbicara tentang mengganggu animasi pengendali tanpa deskripsi bagaimana transisi ini dianimasikan dengan benar. Karena itu, akan ada dua artikel. Dalam hal ini, kita akan mencari cara untuk menunjukkan dan menyembunyikan layar dengan benar, dan tentang gangguan di berikutnya.

Bagaimana cara kerjanya?


UIViewController memiliki properti transitioningDelegate . Ini adalah protokol dengan fungsi yang berbeda, masing-masing mengembalikan objek:


  • animationController untuk animasi,
  • interactionController untuk menginterupsi animasi,
  • presentationController untuk tampilan: hierarki, bingkai, dll.


Berdasarkan semua ini, kami akan membuat panel sembulan:



Pengontrol memasak


Anda dapat menghidupkan transisi untuk pengontrol modal dan untuk UINavigationController (berfungsi melalui UINavigationControllerDelegate ).
Kami akan mempertimbangkan transisi modal. Pengaturan pengontrol sebelum pertunjukan agak tidak biasa:


 class ParentViewController: UIViewController { private let transition = PanelTransition() // 1 @IBAction func openDidPress(_ sender: Any) { let child = ChildViewController() child.transitioningDelegate = transition // 2 child.modalPresentationStyle = .custom // 3 present(child, animated: true) } } 

  1. Buat objek yang menjelaskan transisi. transitioningDelegate ditandai sebagai weak , jadi Anda harus menyimpan transition secara terpisah dengan tautan yang strong .
  2. Kami mengatur transisi kami ke transitioningDelegate .
  3. Untuk mengontrol metode tampilan di presentationController Anda perlu menentukan .custom for modalPresentationStyle. .

Kontroler yang ditunjukkan tidak tahu sama sekali bagaimana itu ditampilkan. Dan itu bagus.


Tampilkan dalam setengah layar


Mari kita mulai kode untuk PanelTransition dengan presentationController . Anda bekerja dengannya jika Anda membuat pop-up melalui UIPopoverController . PresentationController mengontrol tampilan pengontrol: bingkai, hierarki, dll. Dia memutuskan bagaimana menampilkan popover di iPad: dengan bingkai mana, sisi mana dari tombol untuk ditampilkan, menambah buram ke latar belakang jendela dan menjadi gelap di bawahnya.



Struktur kami mirip: kami akan menggelapkan latar belakang, meletakkan bingkai tidak di layar penuh:



Untuk memulainya, dalam metode presentationController(forPresented:, presenting:, source:) , kembalikan kelas PresentationController :


 class PanelTransition: NSObject, UIViewControllerTransitioningDelegate { func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return presentationController = PresentationController(presentedViewController: presented, presenting: presenting ?? source) } 

Mengapa 3 pengendali ditransmisikan dan apa itu sumber?

Source adalah pengontrol di mana kami menyebutnya animasi pertunjukan. Tetapi controller yang akan berpartisipasi dalam tranche adalah yang pertama dari hierarki dengan definesPresentationContext = true . Jika pengontrol berubah, maka pengontrol yang menunjukkan nyata akan berada di parameter presenting.


Sekarang Anda dapat mengimplementasikan kelas PresentationController . Sebagai permulaan, mari membingkai pengontrol masa depan. Ada metode frameOfPresentedViewInContainerView untuk frameOfPresentedViewInContainerView . Biarkan pengontrol menempati bagian bawah layar:


 class PresentationController: UIPresentationController { override var frameOfPresentedViewInContainerView: CGRect { let bounds = containerView!.bounds let halfHeight = bounds.height / 2 return CGRect(x: 0, y: halfHeight, width: bounds.width, height: halfHeight) } } 

Anda dapat memulai proyek dan mencoba menampilkan layar, tetapi tidak ada yang terjadi. Ini karena kami sekarang mengelola hierarki tampilan sendiri dan kami perlu menambahkan tampilan pengontrol secara manual:


 // PresentationController.swift override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() containerView?.addSubview(presentedView!) } 

Masih perlu menempatkan bingkai untuk presentedView . containerViewDidLayoutSubviews adalah tempat terbaik, karena dengan cara ini kita dapat menanggapi rotasi layar:


 // PresentationController.swift override func containerViewDidLayoutSubviews() { super.containerViewDidLayoutSubviews() presentedView?.frame = frameOfPresentedViewInContainerView } 

Sekarang kamu bisa lari. Animasi akan menjadi standar untuk UIModalTransitionStyle.coverVertical , tetapi bingkai akan menjadi setengah ukuran.


Gelapkan latar belakang


Tugas selanjutnya adalah menggelapkan pengontrol latar belakang untuk fokus pada apa yang ditampilkan.


Kami akan mewarisi dari PresentationController dan menggantinya dengan kelas baru di file PanelTransition . Di kelas baru hanya akan ada kode untuk peredupan.


 class DimmPresentationController: PresentationController 

Buat tampilan yang akan kami overlay di atas:


 private lazy var dimmView: UIView = { let view = UIView() view.backgroundColor = UIColor(white: 0, alpha: 0.3) view.alpha = 0 return view }() 

Kami akan mengubah tampilan alpha sesuai dengan animasi transisi. Ada 4 metode:


  • presentationTransitionWillBegin
  • presentationTransitionDidEnd
  • dismissalTransitionWillBegin
  • dismissalTransitionDidEnd

Yang pertama adalah yang paling sulit. Tambahkan dimmView ke hierarki, letakkan bingkai dan mulai animasinya:


 override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() containerView?.insertSubview(dimmView, at: 0) performAlongsideTransitionIfPossible { [unowned self] in self.dimmView.alpha = 1 } } 

Animasi diluncurkan menggunakan fungsi bantu:


 private func performAlongsideTransitionIfPossible(_ block: @escaping () -> Void) { guard let coordinator = self.presentedViewController.transitionCoordinator else { block() return } coordinator.animate(alongsideTransition: { (_) in block() }, completion: nil) } 

Kami menetapkan frame untuk dimmView di containerViewDidLayoutSubviews (sebagai yang terakhir kali):


 override func containerViewDidLayoutSubviews() { super.containerViewDidLayoutSubviews() dimmView.frame = containerView!.frame } 

Animasi dapat terganggu dan dibatalkan, dan jika dibatalkan, maka dimmView harus dihapus dari hierarki:


 override func presentationTransitionDidEnd(_ completed: Bool) { super.presentationTransitionDidEnd(completed) if !completed { self.dimmView.removeFromSuperview() } } 

Proses sebaliknya dimulai pada metode sembunyikan. Tetapi sekarang Anda harus menghapus dimmView hanya jika animasi telah selesai.


 override func dismissalTransitionWillBegin() { super.dismissalTransitionWillBegin() performAlongsideTransitionIfPossible { [unowned self] in self.dimmView.alpha = 0 } } override func dismissalTransitionDidEnd(_ completed: Bool) { super.dismissalTransitionDidEnd(completed) if completed { self.dimmView.removeFromSuperview() } } 

Sekarang latar belakangnya redup.


Kami mengontrol animasinya


Kami menunjukkan controller dari bawah


Sekarang kita dapat menghidupkan tampilan controller. Di kelas PanelTransition , kembalikan kelas yang akan mengontrol animasi tampilan:


 func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PresentAnimation() } 

Menerapkan protokolnya sederhana:


 extension PresentAnimation: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let animator = self.animator(using: transitionContext) animator.startAnimation() } func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { return self.animator(using: transitionContext) } } 

Kode kuncinya sedikit lebih rumit:


 class PresentAnimation: NSObject { let duration: TimeInterval = 0.3 private func animator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { // transitionContext.view    ,   let to = transitionContext.view(forKey: .to)! let finalFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!) //   ,     PresentationController //      to.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height) let animator = UIViewPropertyAnimator(duration: duration, curve: .easeOut) { to.frame = finalFrame //   ,     } animator.addCompletion { (position) in //  ,      transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } return animator } } 

UIViewPropertyAnimator tidak berfungsi di iOS 9

Cara mengatasinya cukup sederhana: Anda tidak perlu menggunakan animator dalam kode animateTransition , tetapi UIView.animate… api yang lama UIView.animate… Misalnya, seperti ini:


 func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let to = transitionContext.view(forKey: .to)! let finalFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!) to.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height) UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { to.frame = finalFrame }) { (_) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } }    ,   `interruptibleAnimator(using transitionContext:)` 

Jika Anda tidak membuat interruptible, maka metode interruptibleAnimator dapat dihilangkan. Diskontinuitas akan dipertimbangkan dalam artikel selanjutnya, berlangganan.


Sembunyikan pengontrol ke bawah


Semuanya sama, hanya dalam arah yang berlawanan. Di Panel Transition buat kelas DismissAnimation :


 func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissAnimation() } 

Dan kami menyadarinya. Kelas DismissAnimation :


 class DismissAnimation: NSObject { let duration: TimeInterval = 0.3 private func animator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { let from = transitionContext.view(forKey: .from)! let initialFrame = transitionContext.initialFrame(for: transitionContext.viewController(forKey: .from)!) let animator = UIViewPropertyAnimator(duration: duration, curve: .easeOut) { from.frame = initialFrame.offsetBy(dx: 0, dy: initialFrame.height) } animator.addCompletion { (position) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } return animator } } extension DismissAnimation: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let animator = self.animator(using: transitionContext) animator.startAnimation() } func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { return self.animator(using: transitionContext) } } 

Di tempat ini Anda dapat bereksperimen dengan para pihak:
- skenario alternatif dapat muncul di bawah;
- di sebelah kanan - navigasi menu cepat;
- di atas - pesan informasi:



Pizza Dodo , Snack , dan Savey


Lain kali kita menambahkan penutupan interaktif dengan gerakan, dan kemudian kita membuat animasinya terganggu. Jika Anda tidak bisa menunggu, maka proyek lengkapnya sudah ada di github.


Dan di sini bagian kedua artikel itu tiba.

Berlangganan saluran Dodo Pizza Mobile.

Source: https://habr.com/ru/post/id463527/


All Articles