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()
- Buat objek yang menjelaskan transisi.
transitioningDelegate
ditandai sebagai weak
, jadi Anda harus menyimpan transition
secara terpisah dengan tautan yang strong
. - Kami mengatur transisi kami ke
transitioningDelegate
. - 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:
Masih perlu menempatkan bingkai untuk presentedView
. containerViewDidLayoutSubviews
adalah tempat terbaik, karena dengan cara ini kita dapat menanggapi rotasi layar:
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 {
UIViewPropertyAnimator tidak berfungsi di iOS 9Cara 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.