Halo, Habr! Nama saya Nikita, saya bekerja pada SDK seluler di ABBYY, dan saya juga berurusan dengan komponen UI untuk pemindaian dan nyaman melihat dokumen multi-halaman pada smartphone. Komponen ini mengurangi waktu untuk mengembangkan aplikasi berdasarkan
teknologi ABBYY Mobile Capture dan terdiri dari beberapa bagian. Pertama, kamera untuk memindai dokumen; kedua, layar editor dengan hasil pengambilan (yaitu, mengambil foto secara otomatis) dan layar untuk memperbaiki batas-batas dokumen.
Sudah cukup bagi pengembang untuk memanggil beberapa metode - dan sekarang dalam aplikasinya kamera sudah tersedia yang secara otomatis memindai dokumen. Tetapi, di samping kamera yang dikonfigurasi, Anda perlu memberi pelanggan akses yang mudah ke hasil pemindaian, mis. mengambil foto secara otomatis. Dan jika klien memindai kontrak atau piagam, maka mungkin ada banyak foto seperti itu.
Dalam posting ini saya akan berbicara tentang kesulitan yang muncul selama implementasi layar editor dengan hasil penangkapan dokumen. Layar itu sendiri adalah dua
UICollectionView
, saya akan menyebutnya besar dan kecil. Saya akan menghilangkan kemungkinan menyesuaikan batas-batas dokumen dan pekerjaan lain dengan dokumen secara manual, dan saya akan fokus pada animasi dan fitur tata letak selama gulir. Di bawah ini di GIF Anda dapat melihat apa yang terjadi pada akhirnya. Tautan ke repositori akan berada di akhir artikel.
Sebagai referensi, saya sering memperhatikan aplikasi sistem Apple. Ketika Anda dengan hati-hati melihat animasi dan solusi antarmuka lainnya dari aplikasi mereka, Anda mulai mengagumi sikap penuh perhatian mereka terhadap berbagai hal sepele. Sekarang kita akan melihat aplikasi
Foto (iOS 12) sebagai referensi. Saya akan menarik perhatian Anda ke fitur spesifik dari aplikasi ini, dan kemudian kami akan mencoba mengimplementasikannya.
Kami akan membahas sebagian besar
UICollectionViewFlowLayout
penyesuaian
UICollectionViewFlowLayout
, melihat bagaimana teknik umum seperti paralaks dan korsel diimplementasikan, dan membahas masalah yang terkait dengan animasi khusus ketika memasukkan dan menghapus sel.
Ulasan Fitur
Untuk menambahkan spesifik, saya akan menjelaskan hal-hal kecil apa yang menyenangkan saya dalam aplikasi
Foto , dan kemudian saya akan menerapkannya dalam urutan yang sesuai.
- Efek paralaks dalam koleksi besar
- Elemen-elemen koleksi kecil terpusat.
- Ukuran item yang dinamis dalam koleksi kecil
- Logika penempatan unsur-unsur sel kecil tidak hanya bergantung pada contentOffset, tetapi juga pada interaksi pengguna
- Animasi khusus untuk dipindahkan dan dihapus
- Indeks sel "aktif" tidak hilang saat mengubah orientasi
1. Parallax
Apa itu paralaks?
Pengguliran Parallax adalah teknik dalam grafik komputer di mana gambar latar belakang bergerak melewati kamera lebih lambat daripada gambar latar depan, menciptakan ilusi kedalaman dalam adegan 2D dan menambah kesan pencelupan dalam pengalaman virtual.
Anda dapat melihat bahwa saat menggulir, bingkai sel bergerak lebih cepat daripada gambar yang ada di dalamnya.
Ayo mulai! Buat subkelas sel, masukkan UIImageView ke dalamnya.
class PreviewCollectionViewCell: UICollectionViewCell { private(set) var imageView = UIImageView()β override init(frame: CGRect) { super.init(frame: frame) addSubview(imageView) clipsToBounds = true imageView.snp.makeConstraints { $0.edges.equalToSuperview() } } }
Sekarang Anda perlu memahami cara menggeser
imageView
, menciptakan efek paralaks. Untuk melakukan ini, Anda perlu mendefinisikan kembali perilaku sel selama pengguliran. Apple:
Hindari subkelas UICollectionView
. Tampilan koleksi memiliki sedikit atau tidak ada tampilan tersendiri. Sebagai gantinya, ini menarik semua pandangannya dari objek sumber data Anda dan semua informasi terkait tata letak dari objek tata letak. Jika Anda mencoba untuk meletakkan item dalam tiga dimensi, cara yang tepat untuk melakukannya adalah dengan mengimplementasikan tata letak khusus yang mengatur transformasi 3D dari setiap sel dan melihatnya dengan tepat.
Ok, mari kita buat
objek layout kita.
UICollectionView
memiliki
collectionViewLayout
propertiViewLayout, dari mana ia mempelajari informasi tentang penentuan posisi sel.
UICollectionViewFlowLayout
adalah implementasi dari abstrak
UICollectionViewLayout
, yang merupakan properti
collectionViewLayout
.
UICollectionViewLayout
sedang menunggu seseorang untuk UICollectionViewLayout
dan menyediakan konten yang sesuai. UICollectionViewFlowLayout
adalah kelas konkret dari UICollectionViewLayout
yang memiliki keempat anggotanya diimplementasikan, dengan cara sel-sel akan diatur dalam cara grid.
Buat subkelas dari
UICollectionViewFlowLayout
dan timpa
layoutAttributesForElements(in:)
. Metode mengembalikan array
UICollectionViewLayoutAttributes
, yang menyediakan informasi tentang cara menampilkan sel tertentu.
Koleksi meminta atribut setiap kali perubahan
contentOffset
, serta ketika tata letak tidak valid. Selain itu, kami akan membuat atribut khusus dengan menambahkan properti
parallaxValue
, yang menentukan seberapa banyak bingkai gambar yang tertunda dari bingkai sel. Untuk subclass atribut, Anda harus mengganti
NSCopiyng
untuk mereka. Apple:
Jika Anda subkelas dan mengimplementasikan atribut tata letak khusus, Anda juga harus mengganti metode isEqual: yang diwarisi untuk membandingkan nilai properti Anda. Di iOS 7 dan yang lebih baru, tampilan koleksi tidak menerapkan atribut tata letak jika atribut tersebut tidak berubah. Ini menentukan apakah atribut telah berubah dengan membandingkan objek atribut lama dan baru menggunakan metode isEqual:. Karena implementasi default metode ini hanya memeriksa properti yang ada di kelas ini, Anda harus mengimplementasikan versi metode Anda sendiri untuk membandingkan properti tambahan apa pun. Jika semua properti khusus Anda sama, hubungi super
dan kembalikan nilai yang dihasilkan pada akhir implementasi Anda.
Bagaimana cara mengetahui
parallaxValue
? Mari kita hitung berapa banyak yang Anda butuhkan untuk memindahkan pusat sel sehingga berdiri di tengah. Jika jarak ini lebih besar dari lebar sel, maka palu di atasnya. Kalau tidak, bagi jarak ini dengan lebar
sel . Semakin dekat jarak ini dengan nol, semakin lemah efek paralaksnya.
class ParallaxLayoutAttributes: UICollectionViewLayoutAttributes { var parallaxValue: CGFloat? }β class PreviewLayout: UICollectionViewFlowLayout { var offsetBetweenCells: CGFloat = 44β override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true }β override class var layoutAttributesClass: AnyClass { return ParallaxLayoutAttributes.self }β override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return super.layoutAttributesForElements(in: rect)? .compactMap { $0.copy() as? ParallaxLayoutAttributes } .compactMap(prepareAttributes) }β private func prepareAttributes(attributes: ParallaxLayoutAttributes) -> ParallaxLayoutAttributes { guard let collectionView = self.collectionView else { return attributes }β let width = itemSize.width let centerX = width / 2 let distanceToCenter = attributes.center.x - collectionView.contentOffset.x let relativeDistanceToCenter = (distanceToCenter - centerX) / widthβ if abs(relativeDistanceToCenter) >= 1 { attributes.parallaxValue = nil attributes.transform = .identity } else { attributes.parallaxValue = relativeDistanceToCenter attributes.transform = CGAffineTransform(translationX: relativeDistanceToCenter * offsetBetweenCells, y: 0) } return attributes } }



Ketika koleksi menerima atribut yang diperlukan, sel
menerapkannya . Perilaku ini dapat ditimpa dalam subkelas sel. Mari kita
imageView
pada nilainya tergantung pada
parallaxValue
. Namun, untuk pengalihan gambar dengan
contentMode == .aspectFit
agar berfungsi dengan benar, ini tidak cukup, karena bingkai gambar tidak sesuai dengan bingkai
imageView
, di mana konten dipotong ketika
clipsToBounds == true
. Pasang topeng yang sesuai dengan ukuran gambar dengan
contentMode
sesuai dan kami akan memperbaruinya jika perlu. Sekarang semuanya berfungsi!
extension PreviewCollectionViewCell {β override func layoutSubviews() {β super.layoutSubviews() guard let imageSize = imageView.image?.size else { return } let imageRect = AVMakeRect(aspectRatio: imageSize, insideRect: bounds)β let path = UIBezierPath(rect: imageRect) let shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath layer.mask = shapeLayer } override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {β guard let attrs = layoutAttributes as? ParallaxLayoutAttributes else { return super.apply(layoutAttributes) } let parallaxValue = attrs.parallaxValue ?? 0 let transition = -(bounds.width * 0.3 * parallaxValue) imageView.transform = CGAffineTransform(translationX: transition, y: .zero) } }
2. Elemen-elemen koleksi kecil terpusat

Semuanya sangat sederhana di sini. Efek ini dapat dicapai dengan mengatur
inset
besar di kiri dan kanan. Ketika menggulir ke kanan / kiri, perlu untuk mulai
bouncing
hanya ketika sel terakhir telah meninggalkan konten yang terlihat. Artinya, konten yang terlihat harus sama dengan ukuran sel.
extension ThumbnailFlowLayout {β var farInset: CGFloat { guard let collection = collectionView else { return .zero } return (collection.bounds.width - itemSize.width) / 2 } var insets: UIEdgeInsets { UIEdgeInsets(top: .zero, left: farInset, bottom: .zero, right: farInset) }β override func prepare() { collectionView?.contentInset = insets super.prepare() } }

Lebih lanjut tentang pemusatan: ketika koleksi selesai bergulir, tata letak meminta
contentOffset
untuk berhenti. Untuk melakukan ini,
targetContentOffset(forProposedContentOffset:withScrollingVelocity:)
. Apple:
Jika Anda ingin perilaku pengguliran untuk snap ke batas-batas tertentu, Anda dapat menimpa metode ini dan menggunakannya untuk mengubah titik di mana untuk berhenti. Misalnya, Anda mungkin menggunakan metode ini untuk selalu berhenti menggulir pada batas antara item, sebagai lawan berhenti di tengah item.
Untuk membuat semuanya indah, kita akan
selalu berhenti di tengah sel terdekat. Menghitung pusat sel terdekat adalah tugas yang agak sepele, tetapi Anda harus berhati-hati dan mempertimbangkan
contentInset
.
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { guard let collection = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) } let cellWithSpacing = itemSize.width + config.distanceBetween let relative = (proposedContentOffset.x + collection.contentInset.left) / cellWithSpacing let leftIndex = max(0, floor(relative)) let rightIndex = min(ceil(relative), CGFloat(itemsCount)) let leftCenter = leftIndex * cellWithSpacing - collection.contentInset.left let rightCenter = rightIndex * cellWithSpacing - collection.contentInset.leftβ if abs(leftCenter - proposedContentOffset.x) < abs(rightCenter - proposedContentOffset.x) { return CGPoint(x: leftCenter, y: proposedContentOffset.y) } else { return CGPoint(x: rightCenter, y: proposedContentOffset.y) } }
3. Ukuran dinamis dari elemen koleksi kecil
Jika Anda menggulir koleksi besar,
contentOffset
berubah untuk yang kecil
contentOffset
. Selain itu, sel pusat dari koleksi kecil tidak sebesar yang lain. Sel samping memiliki ukuran tetap, dan sel pusat bertepatan dengan rasio aspek dari gambar yang dikandungnya.

Anda dapat menggunakan teknik yang sama seperti dalam kasus paralaks. Mari kita membuat
UICollectionViewFlowLayout
khusus untuk koleksi kecil dan mendefinisikan ulang
prepareAttributes(attributes:
Mengingat bahwa lebih lanjut logika tata letak koleksi kecil akan rumit, kami akan membuat entitas terpisah untuk menyimpan dan menghitung geometri sel.
struct Cell { let indexPath: IndexPathβ let dims: Dimensions let state: Stateβ func updated(new state: State) -> Cell { return Cell(indexPath: indexPath, dims: dims, state: state) } }β extension Cell { struct Dimensions {β let defaultSize: CGSize let aspectRatio: CGFloat let inset: CGFloat let insetAsExpanded: CGFloat }β struct State {β let expanding: CGFloatβ static var `default`: State { State(expanding: .zero) } } }
UICollectionViewFlowLayout
memiliki properti
collectionViewContentSize
yang menentukan ukuran area yang dapat digulir. Agar tidak menyulitkan hidup kita, mari kita biarkan konstan, terlepas dari ukuran sel pusat. Untuk geometri yang benar untuk setiap sel, Anda perlu mengetahui
aspectRatio
gambar dan keterpencilan pusat sel dari
contentOffset
. Semakin dekat sel, semakin dekat ukurannya.
size.width / size.height
aspectRatio
.
aspectRatio
ke
aspectRatio
. Saat mengubah ukuran sel tertentu, pindahkan sel yang tersisa (ke kanan dan kiri) menggunakan
affineTransform
. Ternyata untuk menghitung geometri sel tertentu, Anda perlu mengetahui atribut tetangga (terlihat).
extension Cell {β func attributes(from layout: ThumbnailLayout, with sideCells: [Cell]) -> UICollectionViewLayoutAttributes? {β let attributes = layout.layoutAttributesForItem(at: indexPath)β attributes?.size = size attributes?.center = centerβ let translate = sideCells.reduce(0) { (current, cell) -> CGFloat in if indexPath < cell.indexPath { return current - cell.additionalWidth / 2 } if indexPath > cell.indexPath { return current + cell.additionalWidth / 2 } return current } attributes?.transform = CGAffineTransform(translationX: translate, y: .zero)β return attributes } var additionalWidth: CGFloat { (dims.defaultSize.height * dims.aspectRatio - dims.defaultSize.width) * state.expanding } var size: CGSize { CGSize(width: dims.defaultSize.width + additionalWidth, height: dims.defaultSize.height) } var center: CGPoint { CGPoint(x: CGFloat(indexPath.row) * (dims.defaultSize.width + dims.inset) + dims.defaultSize.width / 2, y: dims.defaultSize.height / 2) } }
state.expanding
dianggap hampir sama dengan
parallaxValue
.
func cell(for index: IndexPath, offsetX: CGFloat) -> Cell {β let cell = Cell( indexPath: index, dims: Cell.Dimensions( defaultSize: itemSize, aspectRatio: dataSource(index.row), inset: config.distanceBetween, insetAsExpanded: config.distanceBetweenFocused), state: .default)β guard let attribute = cell.attributes(from: self, with: []) else { return cell }β let cellOffset = attribute.center.x - itemSize.width / 2 let widthWithOffset = itemSize.width + config.distanceBetween if abs(cellOffset - offsetX) < widthWithOffset { let expanding = 1 - abs(cellOffset - offsetX) / widthWithOffset return cell.updated(by: .expand(expanding)) } return cell }β override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return (0 ..< itemsCount) .map { IndexPath(row: $0, section: 0) } .map { cell(for: $0, offsetX: offsetWithoutInsets.x) } .compactMap { $0.attributes(from: self, with: cells) } }
4. Logika penempatan unsur-unsur sel kecil tidak hanya bergantung pada contentOffset, tetapi juga pada interaksi pengguna
Saat pengguna menggulir koleksi kecil, semua sel memiliki ukuran yang sama. Saat menggulir koleksi besar, ini tidak benar. (
lihat gifs 3 dan 5 ). Mari kita menulis animator yang akan memperbarui properti tata letak
ThumbnailLayout
. Animator akan menyimpan
DisplayLink
dalam dirinya sendiri dan memanggil blok 60 kali per detik, memberikan akses ke kemajuan saat ini. Sangat mudah untuk
easing functions
berbagai
easing functions
ke animator. Implementasinya dapat dilihat di github pada tautan di akhir posting.
Mari kita masukkan properti
ThumbnailLayout
di
ThumbnailLayout
, dimana
expanding
semua
Cell
akan dikalikan. Ternyata
aspectRatio
mengatakan berapa banyak
aspectRatio
gambar tertentu akan mempengaruhi ukurannya jika menjadi terpusat. Dengan
expandingRate == 0
semua sel akan berukuran sama. Di awal gulir koleksi kecil, kami akan menjalankan animator yang menetapkan
expandingRate
ke 0, dan di akhir gulir, menjadi sebaliknya. Bahkan, saat memperbarui tata letak, ukuran sel pusat dan sel samping akan berubah. Tidak
contentOffset
-
contentOffset
dengan
contentOffset
dan menyentak!
class ScrollAnimation: NSObject {β enum `Type` { case begin case end }β let type: Typeβ func run(completion: @escaping () -> Void) { let toValue: CGFloat = self.type == .begin ? 0 : 1 let currentExpanding = thumbnails.config.expandingRate let duration = TimeInterval(0.15 * abs(currentExpanding - toValue))β let animator = Animator(onProgress: { current, _ in let rate = currentExpanding + (toValue - currentExpanding) * current self.thumbnails.config.expandingRate = rate self.thumbnails.invalidateLayout() }, easing: .easeInOut)β animator.animate(duration: duration) { _ in completion() } } }
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { if scrollView == thumbnails.collectionView { handle(event: .beginScrolling)
5. Animasi khusus untuk dipindahkan dan dihapus
Ada banyak artikel yang memberi tahu cara membuat animasi khusus untuk memperbarui sel, tetapi dalam kasus kami mereka tidak akan membantu kami. Artikel dan tutorial menjelaskan cara mengganti atribut sel yang diperbarui. Dalam kasus kami, mengubah tata letak sel yang dihapus menyebabkan efek samping -
expanding
sel tetangga, yang cenderung menggantikan yang dihapus selama animasi, berubah.
Memperbarui konten di
UICollectionViewFlowLayout
berfungsi sebagai berikut. Setelah menghapus / menambahkan sel, metode
prepare(forCollectionViewUpdates:)
dimulai, memberikan array
UICollectionViewUpdateItem
, yang memberi tahu kita sel mana di mana indeks diperbarui / dihapus / ditambahkan. Selanjutnya, tata letak akan memanggil sekelompok metode
finalLayoutAttributesForDisappearingItem(at:) initialLayoutAttributesForAppearingDecorationElement(ofKind:at:)
dan teman-teman mereka untuk dekorasi / pemandangan tambahan. Ketika atribut untuk data yang diperbarui diterima,
finalizeCollectionViewUpdates
dipanggil. Apple:
Tampilan koleksi memanggil metode ini sebagai langkah terakhir sebelum melanjutkan untuk menganimasikan perubahan apa pun yang terjadi. Metode ini disebut dalam blok animasi yang digunakan untuk melakukan semua penyisipan, penghapusan, dan memindahkan animasi sehingga Anda dapat membuat animasi tambahan menggunakan metode ini sesuai kebutuhan. Jika tidak, Anda dapat menggunakannya untuk melakukan tugas-tugas menit terakhir yang terkait dengan mengelola informasi status objek tata letak Anda.
Masalahnya adalah kita dapat mengkhususkan atribut hanya untuk sel yang
diperbarui , dan kita perlu mengubahnya untuk semua sel, dan dengan cara yang berbeda. Sel tengah baru harus mengubah
aspectRatio
, dan sel samping harus
transform
.

Setelah memeriksa bagaimana animasi default sel pengumpulan selama penghapusan / penyisipan bekerja, diketahui bahwa lapisan lapisan dalam
finalizeCollectionViewUpdates
berisi
CABasicAnimation
, yang dapat diubah di sana jika Anda ingin menyesuaikan animasi untuk sel yang tersisa. Keadaan menjadi lebih buruk ketika log menunjukkan bahwa antara
performBatchUpdates
dan
performBatchUpdates
prepare(forCollectionViewUpdates:)
prepareAttributes(attributes:)
prepare(forCollectionViewUpdates:)
dipanggil, dan mungkin sudah ada jumlah sel yang salah, meskipun
collectionViewUpdates
belum dimulai, sangat sulit untuk mempertahankan dan memahami hal ini. Apa yang bisa dilakukan tentang ini? Anda dapat menonaktifkan animasi bawaan ini!
final override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) { super.prepare(forCollectionViewUpdates: updateItems) CATransaction.begin() CATransaction.setDisableActions(true) }β final override func finalizeCollectionViewUpdates() { CATransaction.commit() }
Berbekal animator yang sudah ditulis, kami akan melakukan semua animasi yang diperlukan berdasarkan permintaan untuk dihapus, dan kami akan meluncurkan pembaruan
dataSource
di akhir animasi. Dengan demikian, kami akan menyederhanakan animasi koleksi saat memperbarui, karena kami sendiri mengontrol kapan jumlah sel akan berubah.
func delete( at indexPath: IndexPath, dataSourceUpdate: @escaping () -> Void, completion: (() -> Void)?) {β DeleteAnimation(thumbnails: thumbnails, preview: preview, index: indexPath).run { let previousCount = self.thumbnails.itemsCount if previousCount == indexPath.row + 1 { self.activeIndex = previousCount - 1 } dataSourceUpdate() self.thumbnails.collectionView?.deleteItems(at: [indexPath]) self.preview.collectionView?.deleteItems(at: [indexPath]) completion?() } }
Bagaimana cara kerja animasi seperti itu? Di
ThumbnailLayout
mari kita simpan brosur opsional yang memperbarui geometri sel tertentu.
class ThumbnailLayout {β typealias CellUpdate = (Cell) -> Cell var updates: [IndexPath: CellUpdate] = [:]
Dengan alat seperti itu, Anda dapat melakukan apa saja dengan geometri sel, melempar pembaruan selama karya animator dan menghapusnya sebagai pujian. Ada juga kemungkinan menggabungkan pembaruan.
updates[index] = newUpdate(updates[index])
Menghapus kode animasi agak rumit, terletak di file
DeleteAnimation.swift di repositori. Animasi perpindahan fokus antar sel diimplementasikan dengan cara yang sama.

6. Indeks sel "aktif" tidak hilang ketika mengubah orientasi
scrollViewDidScroll(_ scrollView:)
dipanggil bahkan jika Anda cukup memberi nilai pada
contentOffset
, dan juga saat mengubah orientasi. Ketika gulir dua koleksi disinkronkan, beberapa masalah mungkin timbul selama pembaruan tata letak. Trik berikut membantu: pada pembaruan tata letak, Anda dapat mengatur
scrollView.delegate
ke
nil
.
extension ScrollSynchronizer {β private func bind() { preview.collectionView?.delegate = self thumbnails.collectionView?.delegate = self }β private func unbind() { preview.collectionView?.delegate = nil thumbnails.collectionView?.delegate = nil } }
Saat memperbarui ukuran sel pada saat perubahan orientasi, akan terlihat seperti ini:
extension PhotosViewController {β override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator)β contentView.synchronizer.unbind() coordinator.animate(alongsideTransition: nil) { [weak self] _ in self?.contentView.synchronizer.bind() } } }
Agar tidak kehilangan
contentOffset
diinginkan saat mengubah orientasi, Anda dapat memperbarui
targetIndexPath
di
scrollView.delegate
. Ketika Anda mengubah orientasi, tata letak akan dinonaktifkan jika Anda menimpa
shouldInvalidateLayout(forBoundsChange:)
. Ketika mengubah
bounds
tata letak akan meminta untuk mengklarifikasi
contentOffset
, untuk memperjelasnya, Anda perlu mendefinisikan kembali
targetContentOffset(forProposedContentOffset:)
. Apple:
Selama pembaruan tata letak, atau saat beralih di antara tata letak, tampilan koleksi memanggil metode ini untuk memberi Anda kesempatan untuk mengubah offset konten yang diusulkan untuk digunakan pada akhir animasi. Anda dapat mengganti metode ini jika animasi atau transisi dapat menyebabkan item diposisikan dengan cara yang tidak optimal untuk desain Anda.
Tampilan pengumpulan memanggil metode ini setelah memanggil metode prepare()
dan collectionViewContentSize
.
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint { let targetOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset) guard let layoutHandler = layoutHandler else { return targetOffset } let offset = CGFloat(layoutHandler.targetIndex) / CGFloat(itemsCount) return CGPoint( x: collectionViewContentSize.width * offset - farInset, y: targetOffset.y) }
Terima kasih sudah membaca!Semua kode dapat ditemukan di github.com/YetAnotherRzmn/PhotosApp