Halo, Habr! Saya mempersembahkan kepada Anda terjemahan artikel "
Tutorial UICollectionView: Mengubah presentasi dengan cepat ".
Dalam artikel ini, kami akan mempertimbangkan penggunaan berbagai cara menampilkan elemen, serta penggunaan kembali dan perubahan dinamisnya. Di sini kita tidak akan membahas dasar-dasar bekerja dengan koleksi dan pembayaran otomatis.
Hasilnya, kami mendapat contoh:
Saat mengembangkan aplikasi seluler, sering ada situasi ketika tampilan tabel tidak cukup dan Anda perlu menunjukkan daftar elemen yang lebih menarik dan unik. Selain itu, kemampuan untuk mengubah cara elemen ditampilkan dapat menjadi "chip" dalam aplikasi Anda.
Semua fitur di atas cukup sederhana untuk diimplementasikan menggunakan UICollectionView dan berbagai implementasi protokol UICollectionViewDelegateFlowLayout.
Kode proyek lengkap.Yang kita butuhkan pertama-tama untuk implementasi:
Sel dengan UIImageView dan UILabel untuk menampilkan buah
Kami akan membuat sel dalam file terpisah dengan xib untuk digunakan kembali.
Secara desain, kita melihat bahwa ada 2 opsi sel yang mungkin - dengan teks di bawah ini dan teks di sebelah kanan gambar.


Mungkin ada jenis sel yang sama sekali berbeda, dalam hal ini Anda perlu membuat 2 kelas terpisah dan menggunakan yang diinginkan. Dalam kasus kami, tidak ada kebutuhan seperti itu dan 1 sel dengan UIStackView sudah cukup.
Langkah-langkah untuk membuat antarmuka untuk sel:- Tambahkan UIView
- Di dalamnya tambahkan UIStackView (horizontal)
- Selanjutnya, tambahkan UIImageView dan UILabel ke UIStackView.
- Untuk UILabel, tetapkan nilai Prioritas Ketahanan Kompresi Konten = 1000 untuk horizontal dan vertikal.
- Tambahkan 1: 1 untuk Rasio Aspek UIImageView dan ubah prioritas menjadi 750.
Ini diperlukan untuk tampilan yang benar dalam mode horizontal.
Selanjutnya, kami menulis logika untuk menampilkan sel kami dalam mode horizontal dan vertikal.
Kriteria utama untuk tampilan horizontal adalah ukuran sel itu sendiri. Yaitu jika ada cukup ruang - tampilkan mode horizontal. Jika tidak, vertikal. Kami berasumsi bahwa ruang yang cukup adalah ketika lebarnya 2 kali lebih besar dari ketinggian, karena gambar harus persegi.
Kode Sel: class FruitCollectionViewCell: UICollectionViewCell { static let reuseID = String(describing: FruitCollectionViewCell.self) static let nib = UINib(nibName: String(describing: FruitCollectionViewCell.self), bundle: nil) @IBOutlet private weak var stackView: UIStackView! @IBOutlet private weak var ibImageView: UIImageView! @IBOutlet private weak var ibLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() backgroundColor = .white clipsToBounds = true layer.cornerRadius = 4 ibLabel.font = UIFont.systemFont(ofSize: 18) } override func layoutSubviews() { super.layoutSubviews() updateContentStyle() } func update(title: String, image: UIImage) { ibImageView.image = image ibLabel.text = title } private func updateContentStyle() { let isHorizontalStyle = bounds.width > 2 * bounds.height let oldAxis = stackView.axis let newAxis: NSLayoutConstraint.Axis = isHorizontalStyle ? .horizontal : .vertical guard oldAxis != newAxis else { return } stackView.axis = newAxis stackView.spacing = isHorizontalStyle ? 16 : 4 ibLabel.textAlignment = isHorizontalStyle ? .left : .center let fontTransform: CGAffineTransform = isHorizontalStyle ? .identity : CGAffineTransform(scaleX: 0.8, y: 0.8) UIView.animate(withDuration: 0.3) { self.ibLabel.transform = fontTransform self.layoutIfNeeded() } } }
Mari kita beralih ke bagian utama - ke controller dan logika untuk menampilkan dan mengganti tipe sel.
Untuk semua status tampilan yang memungkinkan, buat enum PresentationStyle.
Kami juga menambahkan tombol untuk beralih antar status di bilah navigasi.
class FruitsViewController: UICollectionViewController { private enum PresentationStyle: String, CaseIterable { case table case defaultGrid case customGrid var buttonImage: UIImage { switch self { case .table: return imageLiteral(resourceName: "table") case .defaultGrid: return imageLiteral(resourceName: "default_grid") case .customGrid: return imageLiteral(resourceName: "custom_grid") } } } private var selectedStyle: PresentationStyle = .table { didSet { updatePresentationStyle() } } private var datasource: [Fruit] = FruitsProvider.get() override func viewDidLoad() { super.viewDidLoad() self.collectionView.register(FruitCollectionViewCell.nib, forCellWithReuseIdentifier: FruitCollectionViewCell.reuseID) collectionView.contentInset = .zero updatePresentationStyle() navigationItem.rightBarButtonItem = UIBarButtonItem(image: selectedStyle.buttonImage, style: .plain, target: self, action: #selector(changeContentLayout)) } private func updatePresentationStyle() { navigationItem.rightBarButtonItem?.image = selectedStyle.buttonImage } @objc private func changeContentLayout() { let allCases = PresentationStyle.allCases guard let index = allCases.firstIndex(of: selectedStyle) else { return } let nextIndex = (index + 1) % allCases.count selectedStyle = allCases[nextIndex] } }
Segala sesuatu tentang metode menampilkan elemen dalam koleksi dijelaskan dalam protokol UICollectionViewDelegateFlowLayout. Oleh karena itu, untuk menghapus implementasi apa pun dari pengontrol dan membuat elemen yang dapat digunakan kembali secara independen, kami akan membuat implementasi terpisah dari protokol ini untuk setiap jenis tampilan.
Namun, ada 2 nuansa:
- Protokol ini juga menjelaskan metode pemilihan sel (didSelectItemAt :)
- Beberapa metode dan logika adalah sama untuk semua metode pemetaan N (dalam kasus kami, N = 3).
Oleh karena itu, kami akan membuat protokol
CollectionViewSelectableItemDelegate , memperluas protokol
UICollectionViewDelegateFlowLayout standar, di mana kami menentukan penutupan pemilihan sel dan, jika perlu, setiap properti dan metode tambahan (misalnya, mengembalikan tipe sel jika jenis yang berbeda digunakan untuk representasi). Ini akan menyelesaikan masalah pertama.
protocol CollectionViewSelectableItemDelegate: class, UICollectionViewDelegateFlowLayout { var didSelectItem: ((_ indexPath: IndexPath) -> Void)? { get set } }
Untuk mengatasi masalah kedua - dengan duplikasi logika, kita akan membuat kelas dasar dengan semua logika umum:
class DefaultCollectionViewDelegate: NSObject, CollectionViewSelectableItemDelegate { var didSelectItem: ((_ indexPath: IndexPath) -> Void)? let sectionInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 20.0, right: 16.0) func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { didSelectItem?(indexPath) } func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { let cell = collectionView.cellForItem(at: indexPath) cell?.backgroundColor = UIColor.clear } func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { let cell = collectionView.cellForItem(at: indexPath) cell?.backgroundColor = UIColor.white } }
Dalam kasus kami, logika umum adalah memanggil penutupan saat memilih sel, serta mengubah latar belakang sel saat beralih ke keadaan yang
disorot .
Selanjutnya, kami menjelaskan 3 implementasi dari representasi: tabular, 3 elemen di setiap baris dan kombinasi dari dua metode pertama.
Tabel :
class TabledContentCollectionViewDelegate: DefaultCollectionViewDelegate {
3 elemen di setiap baris: class DefaultGriddedContentCollectionViewDelegate: DefaultCollectionViewDelegate { private let itemsPerRow: CGFloat = 3 private let minimumItemSpacing: CGFloat = 8
Kombinasi tabel dan 3x berturut-turut. class CustomGriddedContentCollectionViewDelegate: DefaultCollectionViewDelegate { private let itemsPerRow: CGFloat = 3 private let minimumItemSpacing: CGFloat = 8
Langkah terakhir adalah menambahkan data tampilan ke controller dan mengatur koleksi delegasi yang diinginkan.
Poin penting: karena delegasi koleksi
lemah , Anda harus memiliki tautan yang
kuat di controller ke objek tampilan.
Di controller, buat kamus dari semua tampilan yang tersedia mengenai jenis:
private var styleDelegates: [PresentationStyle: CollectionViewSelectableItemDelegate] = { let result: [PresentationStyle: CollectionViewSelectableItemDelegate] = [ .table: TabledContentCollectionViewDelegate(), .defaultGrid: DefaultGriddedContentCollectionViewDelegate(), .customGrid: CustomGriddedContentCollectionViewDelegate(), ] result.values.forEach { $0.didSelectItem = { _ in print("Item selected") } } return result }()
Dan dalam metode
updatePresentationStyle () , tambahkan perubahan animasi ke delegasi koleksi:
collectionView.delegate = styleDelegates[selectedStyle] collectionView.performBatchUpdates({ collectionView.reloadData() }, completion: nil)
Hanya itu yang diperlukan agar elemen kita dapat bergerak dengan animasi dari satu tampilan ke tampilan lain :)
Dengan demikian, kita sekarang dapat menampilkan elemen pada layar apa pun dengan cara apa pun yang kita suka, secara dinamis beralih di antara layar, dan yang paling penting, kode ini independen, dapat digunakan kembali, dan dapat diskalakan.
Kode proyek lengkap.