Selami Lebih Dalam Desain Gerak: Transisi iOS Tingkat Lanjut
Diterbitkan: 2020-11-09Kreativitas membawa serta keinginan konstan untuk mengesankan pengguna. Selama berabad-abad, manusia telah mencoba memanipulasi sarana yang tersedia untuk menciptakan kembali interaksi yang paling alami, dengan mengambil alam sebagai contoh mendasar.
Ketika menemukan dunia, seseorang menjadi semakin sensitif terhadap detail halus dunia di sekitar mereka, yang memungkinkan mereka secara naluriah membedakan artifisial dari makhluk hidup. Garis ini kabur dengan perkembangan teknologi, di mana perangkat lunak bertujuan untuk menciptakan lingkungan di mana penggunanya menggambarkan pengalamannya di dunia yang dibuat secara artifisial sebagai alami.

Menggemakan alam dalam desain aplikasi
Artikel ini akan memperkenalkan proses mengaburkan batas menggunakan contoh transformasi bentuk dalam animasi interaktif elemen sehari-hari di sebagian besar aplikasi iOS. Salah satu cara meniru alam adalah melalui berbagai transformasi posisi objek dalam waktu. Contoh fungsi waktu animasi disajikan di bawah ini.

Dikombinasikan dengan penggunaan waktu yang tepat dengan menambahkan transformasi geometrik, kita bisa mendapatkan jumlah efek yang tak terbatas . Sebagai demonstrasi kemungkinan desain dan teknologi saat ini, aplikasi Motion Patterns telah dibuat, yang mencakup solusi populer, yang dikembangkan oleh perusahaan pengembangan perangkat lunak kami. Karena saya bukan seorang penulis, tetapi seorang programmer, dan tidak ada yang berbicara lebih baik daripada contoh langsung, saya tidak punya pilihan selain mengundang Anda ke dunia yang indah ini!
Sampel untuk ditemukan

Mari kita lihat bagaimana desain gerak dapat mengubah desain biasa menjadi sesuatu yang luar biasa! Pada contoh di bawah ini, di sisi kiri ada aplikasi yang hanya menggunakan animasi dasar iOS, sedangkan di sisi kanan ada versi aplikasi yang sama dengan beberapa perbaikan.
Efek "Menyelam Lebih Dalam"
Ini adalah transisi menggunakan transformasi antara dua keadaan tampilan . Dibangun berdasarkan koleksi, setelah memilih sel tertentu, transisi ke detail elemen terjadi dengan mengubah elemen individualnya *. Solusi tambahan adalah penggunaan transisi interaktif, yang memfasilitasi penggunaan aplikasi.
*sebenarnya menyalin / memetakan elemen data pada tampilan sementara yang mengambil bagian dalam transisi, antara awal dan akhir … tetapi saya akan menjelaskannya nanti di artikel ini …
Efek "Mengintip dari Tepi"
Menggunakan animasi tampilan gulir dalam aksinya mengubah gambar dalam bentuk 3D untuk efek kubus . Faktor utama yang bertanggung jawab atas efek tersebut adalah offset tampilan gulir.
Efek "Hubungkan Titik"
Ini adalah transisi antara adegan yang mengubah objek miniatur menjadi seluruh layar . Koleksi yang digunakan untuk tujuan ini bekerja secara bersamaan, perubahan pada satu layar sesuai dengan pergeseran pada layar lainnya. Selain itu, saat Anda memasukkan miniatur di latar belakang, efek paralaks muncul saat Anda menggeser di antara adegan.
Efek "Shift the Shape"
Jenis animasi terakhir adalah yang sederhana menggunakan perpustakaan Lottie. Ini adalah penggunaan paling umum untuk menganimasikan ikon. Dalam hal ini, ini adalah ikon pada bilah tab. Selain itu, dengan mengubah tab yang sesuai, animasi transisi ke arah tertentu digunakan untuk lebih mengintensifkan efek interaksi.
Selami lebih dalam: pola desain gerak pertama kami
Sekarang saatnya untuk langsung ke intinya… kita perlu masuk lebih dalam ke struktur mekanisme yang mengontrol contoh-contoh ini.
Dalam artikel ini, saya akan memperkenalkan Anda pada pola desain gerak pertama , yang kami beri nama 'Menyelam Lebih Dalam' dengan deskripsi abstrak penggunaannya, tanpa membahas detail spesifik. Kami berencana untuk membuat kode yang tepat dan seluruh repositori tersedia untuk semua orang di masa mendatang, tanpa batasan apa pun.
Arsitektur proyek dan penerapan pola desain pemrograman yang ketat bukanlah prioritas saat ini—kami fokus pada animasi dan transisi.
Dalam artikel ini, kita akan menggunakan dua set fitur yang disediakan untuk mengelola tampilan selama transisi adegan. Jadi, saya ingin menunjukkan bahwa artikel ini ditujukan untuk orang-orang yang relatif akrab dengan UIKit dan sintaks Swift.
https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning
https://developer.apple.com/documentation/uikit/uipercentdriveninteractivetransition
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html
Pertama: struktur
Untuk versi dasar yang mengimplementasikan solusi yang diberikan, beberapa kelas pembantu akan diperlukan, bertanggung jawab untuk menyediakan informasi yang diperlukan tentang pandangan yang terlibat dalam transisi, dan mengendalikan transisi itu sendiri dan interaksi.

Kelas dasar yang bertanggung jawab untuk mengelola transisi, proxy, adalah TransitionAnimation. Ini memutuskan ke arah mana transisi akan berlangsung dan mencakup fungsi standar yang diperlukan untuk melakukan tindakan yang disediakan oleh tim Apple.
/// Ini adalah kelas dasar untuk transisi yang memiliki perilaku berbeda dalam menampilkan dan menutup selama durasi yang ditentukan. buka kelas TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { /// Menunjukkan apakah itu menampilkan atau mengabaikan transisi. var penyajian: Bool = benar /// Interval waktu di mana seluruh transisi terjadi. durasi izin pribadi: TimeInterval /// Inisialisasi default dari animator transisi dengan nilai default. /// - Durasi parameter: Interval waktu di mana seluruh transisi terjadi. /// - Penyajian parameter: Indikator jika menampilkan atau mengabaikan transisi. public init(durasi: TimeInterval = 0.5, presentasi: Bool = true) { diri.durasi = durasi self.presenting = presentasi super.init() } /// Menentukan durasi transisi. /// - Parameter transisiContext: Konteks transisi saat ini. /// - Pengembalian: Durasi yang ditentukan pada inisialisasi animator. transisi fungsi publikDurasi (menggunakan transisiContext: UIViewControllerContextTransitioning?) -> TimeInterval { kembalikan diri.durasi } /// Jantung dari animator transisi, dalam fungsi ini transisi terjadi. /// - Penting: Mengganti fungsi ini dalam jenis transisi yang lebih konkret sangat penting untuk melakukan animasi. /// - Parameter transisiContext: Konteks transisi. fungsi publik animateTransition (menggunakan transisiContext: UIViewControllerContextTransitioning) { } }
Berdasarkan TransitionAnimator, kami membuat file TransformTransition, yang tugasnya adalah melakukan transisi tertentu, transisi dengan transformasi (pengasuhan)
/// Implementasi transisi transformasi. buka kelas TransformTransition: TransitionAnimator { /// Lihat model yang menampung semua spesifikasi transisi yang dibutuhkan. private var viewModel: TransformViewModel /// Inisialisasi default dari transisi transformasi. /// - Parameter viewModel: Lihat model transisi transformasi. /// - Durasi parameter: Durasi transisi. init(viewModel: TransformViewModel, durasi: TimeInterval) { self.viewModel = viewModel super.init(durasi: durasi, penyajian: viewModel.presenting) }
Komposisi kelas TransformTransition mencakup TransformViewModel, yang, seperti namanya, menginformasikan mekanisme model tampilan mana yang akan diterapkan transisi ini.
/// Lihat model transisi transformasi yang menyimpan informasi dasar tentangnya. kelas terakhir TransformViewModel { /// Menunjukkan apakah transisi transformasi menampilkan atau mengabaikan tampilan. biarkan presentasi: Bool /// Array model dengan spesifikasi tentang transformasi untuk setiap tampilan. biarkan model: [TransformModel] /// Inisialisasi default dari model tampilan transformasi. /// - Penyajian parameter: Menunjukkan apakah menampilkan atau mengabaikan transisi transformasi. /// - Model parameter: Array model dengan spesifikasi tentang transformasi untuk setiap tampilan. init(menyajikan: Bool, model: [TransformModel]) { self.presenting = presentasi diri.model = model } }
Model transformasi adalah kelas bantu yang menggambarkan elemen spesifik dari tampilan yang terlibat dalam transisi yang terletak di induknya, biasanya tampilan pengontrol yang dapat ditransformasikan.
Dalam kasus transisi, ini adalah langkah yang diperlukan karena transisi ini terdiri dari operasi pandangan khusus antara keadaan tertentu.
Kedua: implementasi
Kami memperluas model tampilan dari mana kami memulai transisi dengan Transformable, yang memaksa kami untuk mengimplementasikan fungsi yang akan menyiapkan semua elemen yang diperlukan. Ukuran fungsi ini dapat berkembang sangat cepat, jadi saya sarankan Anda memecahnya menjadi bagian-bagian yang lebih kecil, misalnya per elemen.

/// Protokol untuk kelas yang ingin melakukan transisi transformasi. protokol Transformable: ViewModel { /// Menyiapkan model tampilan yang terlibat dalam transisi. /// - Parameter fromView: Tampilan dari mana transisi dimulai /// - Parameter toView: Tampilan transisi mana. /// - Penyajian parameter: Menunjukkan apakah itu menampilkan atau mengabaikan. /// - Pengembalian: Array struktur yang menyimpan semua informasi yang dibutuhkan siap untuk mengubah transisi untuk setiap tampilan. func prepareTransitionModels(fromView: UIView, toView: UIView, presenting: Bool) -> [TransformModel] }
Asumsinya bukan untuk mengatakan bagaimana mencari data pandangan yang berpartisipasi dalam transformasi. Dalam contoh saya, saya menggunakan tag yang mewakili tampilan tertentu. Anda memiliki kebebasan dalam bagian implementasi ini.
Model transformasi tampilan tertentu (TransformModel) adalah model terkecil di seluruh daftar. Mereka terdiri dari informasi transformasi kunci seperti tampilan awal, tampilan transisi, bingkai awal, bingkai akhir, pusat awal, pusat akhir, animasi bersamaan, dan operasi akhir. Sebagian besar parameter tidak perlu digunakan selama transformasi, sehingga mereka memiliki nilai defaultnya sendiri. Untuk hasil yang minimal, cukup menggunakan yang memang diperlukan saja.
/// Inisialisasi default dari model transformasi dengan nilai default. /// - Parameter initialView: Tampilan dari mana transisi dimulai. /// - Parameter phantomView: Tampilan yang ditampilkan selama transisi transformasi. /// - Parameter initialFrame: Bingkai tampilan yang memulai transisi transformasi. /// - Parameter finalFrame: Frame tampilan yang akan ditampilkan di akhir transisi transformasi. /// - Parameter initialCenter: Diperlukan ketika titik pandang tengah awal berbeda dari pusat tampilan awal. /// - Parameter finalCenter: Diperlukan ketika titik tengah akhir tampilan berbeda dari pusat tampilan akhir. /// - Parameter paralelAnimation: Animasi tampilan tambahan yang dilakukan selama transisi transformasi. /// - Penyelesaian parameter: Blok kode dipicu setelah transisi transformasi. /// - Catatan: Hanya tampilan awal yang diperlukan untuk melakukan transisi transformasi versi paling minimalis. init(initialView: UIView, phantomView: UIView = UIView(), initialFrame: CGRect = CGRect(), finalFrame: CGRect = CGRect(), pusat awal: CGPoint? = nihil, pusat akhir: CGPoint? = nihil, parallelAnimation: (() -> Void)? = nihil, selesai: (() -> Void)? = nihil) { self.initialView = initialView self.phantomView = phantomView self.initialFrame = initialFrame self.finalFrame = finalFrame self.parallelAnimation = parallelAnimation self.completion = selesai self.initialCenter = initialCenter ?? CGPoint(x: initialFrame.midX, y: initialFrame.midY) self.finalCenter = finalCenter ?? CGPoint(x: finalFrame.midX, y: finalFrame.midY) }
Perhatian Anda mungkin telah ditangkap oleh phantom View. Inilah saatnya saya akan menjelaskan alur kerja untuk transisi iOS. Dalam bentuk sesingkat mungkin…

Saat pengguna ingin pindah ke adegan berikutnya, iOS menyiapkan pengontrol tertentu dengan menyalin pengontrol awal (biru) dan target (hijau) ke memori. Selanjutnya, konteks transisi dibuat melalui koordinator transisi yang berisi wadah, tampilan 'bodoh' yang tidak mengandung fungsi khusus apa pun, selain mensimulasikan tampilan transisi antara dua adegan.
Prinsip utama bekerja dengan transisi adalah tidak menambahkan tampilan nyata apa pun ke konteks transisi, karena pada akhir transisi, semua konteks tidak dialokasikan, bersama dengan tampilan yang ditambahkan ke penampung. Ini adalah tampilan yang hanya ada selama transisi dan kemudian dihapus.
Oleh karena itu, penggunaan tampilan phantom yang merupakan replika dari tampilan nyata merupakan solusi penting untuk transisi ini.

Dalam hal ini, kami memiliki transisi yang mengubah satu tampilan menjadi tampilan lain dengan mengubah bentuk dan ukurannya. Untuk melakukan ini, di awal transisi, saya membuat PhantomView dari elemen yang diberikan dan menambahkannya ke wadah. FadeView adalah tampilan tambahan untuk menambahkan kelembutan pada transisi keseluruhan.
/// Inti transisi transformasi, tempat transisi dilakukan. Ganti `TransitionAnimator.animateTransition(...)`. /// - Parameter transisiContext: Konteks transisi transformasi saat ini. menimpa fungsi terbuka animateTransition (menggunakan transisiContext: UIViewControllerContextTransitioning) { penjaga biarkan toViewController = transisiContext.view(forKey: .to), biarkan fromViewController = transisiContext.view(forKey: .from) else { kembalikan Log.unexpectedState() } biarkan containerView = transisiContext.containerView biarkan durasi = transisiDurasi (menggunakan: transisiContext) biarkan fadeView = toViewController.makeFadeView(opacity: (!presenting).cgFloatValue) biarkan model = viewModel.models biarkan disajikanView = presentasi? toViewController : dariViewController model.forEach { $0.initialView.isHidden = true } presentView.isHidden = true containerView.addSubview(toViewController) jika menyajikan { containerView.insertSubview(fadeView, belowSubview: toViewController) } kalau tidak { containerView.addSubview(fadeView) } containerView.addSubviews(viewModel.models.map { $0.phantomView })
Pada langkah berikutnya, saya mengubahnya ke bentuk target melalui transformasi, dan bergantung pada apakah itu presentasi atau penarikan, ia melakukan operasi tambahan untuk membersihkan tampilan tertentu – ini adalah keseluruhan resep untuk transisi ini.
biarkan animasi: () -> Void = { [lemah diri] di guard let self = self else { kembalikan Log.unexpectedState() } fadeView.alpha = self.presenting.cgFloatValue model.untukSetiap { biarkan pusat = self.presenting ? $0.finalCenter : $0.initialCenter let transform = self.presenting ? $0.presentTransform : $0.dismissTransform $0.phantomView.setTransformAndCenter(transformasi, tengah) } model.compactMap { $0.parallelAnimation }.forEach { $0() } } biarkan selesai: (Bool) -> Void = { _ in transisiContext.completeTransition(!transitionContext.transitionWasCancelled) presentView.isHidden = false model.compactMap { $0.completion }.forEach { $0() } model.forEach { $0.initialView.isHidden = false } if !self.presenting && transisiContext.transitionWasCancelled { toViewController.removeFromSuperview() fadeView.removeFromSuperview() } } UIView.animate(denganDurasi: durasi, penundaan: 0, menggunakanSpringWithDamping: 1, awalSpringVelocity: 0,5, pilihan: .curveEaseOut, animasi: animasi, selesai: selesai)
Ketiga: bahan khusus
Setelah menggabungkan semua fungsi, kelas dan protokol, hasilnya akan terlihat seperti ini:
Komponen terakhir dari transisi kita adalah interaktivitas penuhnya. Untuk tujuan ini kita akan menggunakan Gerakan Pan yang ditambahkan di tampilan pengontrol, TransitionInteractor…
/// Mediator untuk menangani transisi interaktif. kelas akhir TransitionInteractor: UIPercentDrivenInteractiveTransition { /// Menunjukkan jika transisi telah dimulai. var hasStarted = false /// Menunjukkan jika transisi harus selesai. var shouldFinish = false }
… yang juga kami inisialisasi di badan pengontrol.
/// Menangani gerakan pan pada item tampilan koleksi, dan mengelola transisi. @objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { biarkan persenThreshold: CGFloat = 0,1 biarkan terjemahan = gestureRecognizer.translation(dalam: tampilan) biarkan verticalMovement = translation.y / view.bounds.height biarkan ke atasPergerakan = fminf(Mengambang(Pergerakan vertikal), 0.0) biarkan ke atasMovementPercent = fminf(abs(upwardMovement), 0.9) biarkan kemajuan = CGFloat(upwardMovementPercent) guard let interactor = interactionController else { return } beralih gestureRecognizer.state { kasus .bemula: interactor.hasStarted = true biarkan tapPosition = gestureRecognizer.location(di: collectionView) showDetailViewControllerFrom(lokasi: tapPosition) kasus .berubah: interactor.shouldFinish = kemajuan > persenThreshold interactor.update(kemajuan) kasus .dibatalkan: interactor.hasStarted = false interaksi.batal() kasus .berakhir: interactor.hasStarted = false interactor.shouldFinish ? interaksi.selesai() : interaksi.batal() bawaan: merusak } }
Interaksi siap harus sebagai berikut:
Jika semuanya berjalan sesuai rencana, aplikasi kita akan mendapatkan lebih banyak di mata penggunanya.

Ditemukan hanya puncak gunung es
Lain kali, saya akan menjelaskan implementasi isu-isu terkait desain gerak dalam edisi-edisi berikut.
Aplikasi, desain, dan kode sumber adalah milik Miquido, dan dibuat dengan penuh semangat oleh desainer dan pemrogram berbakat untuk penggunaan yang tidak bertanggung jawab atas implementasi kami. Kode sumber terperinci akan tersedia di masa mendatang melalui akun github kami — kami mengundang Anda untuk mengikuti kami!
Terima kasih atas perhatian Anda dan sampai jumpa!