Irbisa · Шпаргалка
UICollectionView и diffable data source
Compositional layout, diffable data source, регистрация ячеек, content configuration, префетч
Когда выбирать
UICollectionView, а неUITableView?Товары без скидки после небольшого скролла начинают показывать бейдж SALE: что не так с этим кодом?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCell", for: indexPath) as! ProductCell let product = products[indexPath.item] cell.titleLabel.text = product.title if product.isOnSale { cell.saleBadge.isHidden = false cell.priceLabel.textColor = .systemRed } return cell}Найдите баг: свайп для удаления заметки роняет приложение с
NSInternalInconsistencyException.override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { guard editingStyle == .delete else { return } tableView.deleteRows(at: [indexPath], with: .automatic) notes.remove(at: indexPath.row) storage.save(notes)}При быстром скролле сетки фотографий ячейки на мгновение показывают картинку предыдущего элемента: в чём причина и как это исправить?
final class PhotoCell: UICollectionViewCell { let imageView = UIImageView() private var loadTask: Task<Void, Never>? private var photoID: Photo.ID? override func prepareForReuse() {…Найдите баг: после прихода новых песен нажатие на звёздочку в строке иногда добавляет в избранное другую песню.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Song", for: indexPath) as! SongCell cell.titleLabel.text = songs[indexPath.row].title cell.starButton.tag = indexPath.row cell.starButton.addTarget(self, action: #selector(starTapped(_:)), for: .touchUpInside) return cell} @objc private func starTapped(_ sender: UIButton) { songs[sender.tag].isFavorite.toggle()} func songArrived(_ song: Song) { songs.insert(song, at: 0) tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)}Как сделать сетку в три колонки на
UICollectionViewFlowLayoutи когда переходить на compositional layout?final class GridViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { private static let spacing: CGFloat = 8 private let columns: CGFloat = 3 init() { let layout = UICollectionViewFlowLayout()…Зачем нужен diffable data source вместо
reloadDataилиperformBatchUpdates?enum Section { case main } final class ContactsViewController: UIViewController { private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource<Section, Contact.ID>! private var contacts: [Contact.ID: Contact] = [:]…Что класть в diffable-снапшот (структуры модели или только их id) и что ломается, если класть структуры?
struct Message: Identifiable, Hashable { let id: UUID var text: String var isRead: Bool}…Переключение звёздочки «избранное» через
reloadItemsзаставляет обложку мигать и убирает клавиатуру из текстового поля в той же строке: что нужно было вызвать?Что заменяют
CellRegistrationиcontentConfigurationв современном collection view и что они дают?private lazy var rowRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Todo.ID> { [weak self] cell, _, id in guard let todo = self?.store[id] else { return } var content = cell.defaultContentConfiguration() // a value: copy, change, assign content.text = todo.title content.secondaryText = todo.due.formatted(date: .abbreviated, time: .omitted)…Этот data source компилируется без предупреждений: что произойдёт при запуске и как это исправить?
dataSource = UICollectionViewDiffableDataSource<Section, Contact.ID>(collectionView: collectionView) { collectionView, indexPath, id in let registration = UICollectionView.CellRegistration<UICollectionViewListCell, Contact.ID> { cell, _, id in var content = cell.defaultContentConfiguration() content.text = ContactStore.shared.name(for: id) cell.contentConfiguration = content } return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: id)}Что дают item, group и section в compositional layout и относительно чего на самом деле считается
.fractionalWidth(1.0)?func makeLayout() -> UICollectionViewCompositionalLayout { UICollectionViewCompositionalLayout { _, environment in let columns = environment.container.effectiveContentSize.width > 700 ? 4 : 2 // 1.0 of its GROUP, not of the screen. let item = NSCollectionLayoutItem(layoutSize: .init(…Лента самоподстраивающихся ячеек дёргается при скролле, а индикатор прокрутки всё время меняет размер: в чём причина?
func makeLayout() -> UICollectionViewCompositionalLayout { // Both item and group estimated: this is what turns on measuring. let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(120)) // near the real average let item = NSCollectionLayoutItem(layoutSize: size) let group = NSCollectionLayoutGroup.vertical(layoutSize: size, subitems: [item])…Дизайн хочет вид inset-grouped таблицы с разделителями и swipe-to-delete, а экран уже сделан на collection view: что использовать?
private func makeLayout() -> UICollectionViewLayout { var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) // Stored on the layout for the screen's lifetime: capture self weakly. config.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in // Resolve the index path to an identifier now; it may be stale later.…Пользователь пролистывает сотню строк, а запросы картинок продолжают приходить для ячеек, давно ушедших с экрана: что пропустил код префетча?
@MainActorfinal class FeedViewController: UIViewController, UICollectionViewDataSourcePrefetching { private var dataSource: UICollectionViewDiffableDataSource<Int, Post.ID>! private let images = ImageCache() // Keyed by the stable id: index paths move with every snapshot. private var tasks: [Post.ID: Task<Void, Never>] = [:]…Этот код бесконечной ленты иногда загружает одну страницу дважды, а потом падает с ошибкой дублирующихся идентификаторов: что не так и как сделать правильно?
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard indexPath.item == posts.count - 1 else { return } Task { let page = try await api.fetchPosts(page: currentPage + 1) currentPage += 1 posts += page var snapshot = dataSource.snapshot() snapshot.appendItems(page.map(\.id)) await dataSource.apply(snapshot) }}Как вставить горизонтально прокручиваемый collection view в ячейку таблицы и что ломается при переиспользовании?
final class ShelfCell: UITableViewCell { let books = UICollectionView(frame: .zero, collectionViewLayout: ShelfCell.makeLayout()) // Created in init(style:reuseIdentifier:) with the cell's own registration. private var dataSource: UICollectionViewDiffableDataSource<Int, Book.ID>! private(set) var shelfID: Shelf.ID?…Перестановка перетаскиванием выглядит правильно, пока следующее обновление модели не вернёт строку на прежнее место: что упустил обработчик перестановки?
// The snapshot holds ids; the model is an order array plus a store.private var order: [Item.ID] = []private var store: [Item.ID: Item] = [:] func configureReordering() { dataSource.reorderingHandlers.canReorderItem = { [weak self] id in…Нужна скруглённая тонированная подложка под всей секцией и маленький бейдж, свисающий с угла ячейки: какие объекты layout это дают?
let badge = NSCollectionLayoutSupplementaryItem( layoutSize: .init(widthDimension: .absolute(20), heightDimension: .absolute(20)), elementKind: "badge", containerAnchor: NSCollectionLayoutAnchor(edges: [.top, .trailing], fractionalOffset: CGPoint(x: 0.3, y: -0.3)))let item = NSCollectionLayoutItem(…Горизонтальная карусель с постраничной прокруткой должна увеличивать центральную карточку во время свайпа: как это сделать в compositional layout?
func carouselSection() -> NSCollectionLayoutSection { let item = NSCollectionLayoutItem(layoutSize: .init( widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))) // 0.75 of the width, so the neighbouring cards peek in on both sides. let group = NSCollectionLayoutGroup.horizontal(…Вы написали свой
UICollectionViewLayoutдля «ступенчатой» сетки, и скролл ползёт: какой метод делает слишком много?final class StaggeredLayout: UICollectionViewLayout { var itemHeights: [CGFloat] = [] private let columns = 2 private let maxItemHeight: CGFloat = 400 // upper bound for the search private var cache: [UICollectionViewLayoutAttributes] = [] // sorted by frame.minY private var byItem: [UICollectionViewLayoutAttributes] = [] // in item order…Живая лента из десяти тысяч строк на мгновение замирает при каждом обновлении из сокета: на что тратит время
applyи что менять?@MainActorfinal class FeedController { private var dataSource: UICollectionViewDiffableDataSource<Int, Message.ID>! private let store = MessageStore() // an actor: merging and sorting run off main private var pending: [Message] = [] private var flushTask: Task<Void, Never>?…Вам достался
UITableViewDelegateна 600 строк, и его нужно перевести на compositional list: что переносить первым и как доказать, что ничего не сломалось?