Статьи

UICollectionView DataSourcePrefetching Пример и объяснение

UICollectionView DataSourcePrefetching

Недавно Apple объявила о новом UICollectionViewDataSource протокола UICollectionViewDataSource , которое называется UICollectionViewDataSourcePrefetching . Это расширение позволяет реализовать две новые функции, обеспечивающие абсолютно плавную прокрутку. Этот урок покажет вам, как его использовать.

Вы можете найти исходный код на GitHub .

Настройка проекта Xcode

Откройте Xcode и создайте новый проект на основе шаблона приложения Single View . Прежде чем делать что-либо еще, чтобы улучшить внешний вид нашего приложения, выберите представление и выберите « Редактор» -> « Встроить» -> « Контроллер навигации». Теперь перетащите представление Collection View из библиотеки объектов на холст и отрегулируйте его размер.

Обратите внимание, что в представлении « Collection View есть одна повторно используемая cell , поэтому щелкните под Инспектором атрибутов и foodCell этой ячейке идентификатор , назовем его foodCell . Перетащите UIImageView на него и отрегулируйте его размер в соответствии с содержимым.

Cell Identifier

Создайте новый файл типа Cocoa Touch Class , подкласс UICollectionViewCell , назовите его collectionViewCell и назначьте его cell .

collectionViewCell Class

Подключите UIImageView к коду collectionViewCell.swift и назовите его « foodImage ». Кроме того, подключите collectionView к ViewController.swift .

ImageView connection

Соответствие протоколам и написание некоторых строк

Теперь пришло время написать код в viewController.swift .
Первое, что нужно сделать, это согласовать класс с UICollectionViewDelegate , UICollectionViewDataSource и UICollectionViewDataSourcePrefetching .

Declaring protocols

Вероятно, вы должны увидеть некоторые ошибки на боковой панели, потому что необходимые методы еще не реализованы. Мы рассмотрим это в ближайшее время, но давайте сначала закончим небольшую настройку.

Нам понадобится источник данных для этого UICollectionView , поэтому давайте просто создадим массив с именем imageArray , который в иллюстративных целях будет хранить 30 изображений. Вне любого метода создайте массив следующим образом:

 var imageArray = [ UIImage? ]( repeating: nil, count: 30 ) 

и переменная для хранения основного URL-адреса картинки, например:

 var baseUrl = URL( string : "https://placehold.it" )! 

То же, что и выше, создайте еще один массив типа URLSessionDataTask :

 var tasks = [ URLSessionDataTask? ]( repeating: nil, count: 30 ) 

Следующим шагом является создание двух отдельных функций для генерации изображений из динамически генерируемых URL. Первая функция требует параметр, который в нашем случае будет индексом каждой cell и будет возвращать URL.

  func urlComponents (index: Int) -> URL { var baseUrlComponents = URLComponents( url : baseUrl, resolvingAgainstBaseURL : true ) baseUrlComponents?.path = "/\(screenSize.width)x\(screenSize.height * 0.3)" baseUrlComponents?.query = "text=food \(index)" return (baseUrlComponents?.url)! } 

Второй, где процесс загрузки будет выполняться, и он требует indexPath каждой cell в качестве параметра. Тип возврата этой функции будет URLSessionDataTask .

  func getTask(forIndex: IndexPath) -> URLSessionDataTask { let imgURL = urlComponents(index: forIndex . row) return URLSession . shared . dataTask( with : imgURL) { data , response, error in guard let data = data , error == nil else { return } DispatchQueue . main . async() { let image = UIImage( data : data ) ! self . imageArray [ forIndex . row ]  = image self.collectionView.reloadItems(at: [ forIndex ] ) } } } 

Убедитесь, что вы добавили эти строки в viewDidLoad() :

 collectionView.dataSource = self collectionView. delegate = self collectionView.prefetchDataSource = self 

Реализация необходимых методов

Теперь мы должны реализовать 3 обязательных метода:

  • collectionView(_:numberOfItemsInSection:)
  • collectionView(_:cellForItemAt:)
  • collectionView(_:prefetchItemsAt:)

UICollectionView должен знать, сколько строк будет внутри раздела, поэтому мы здесь вернем количество элементов в нашем массиве:

 func collectionView (_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return imageArray.count } 

Прежде чем перейти к наиболее важным методам, остается сделать только одну вещь. Давайте создадим метод, в котором мы можем контролировать процесс загрузки.
Еще раз требуемым параметром является indexPath cell .

  func requestImage( for Index: IndexPath) { var task: URLSessionDataTask if imageArray[ for Index.row] != nil { // Image is already loaded return } if tasks[ for Index.row] != nil && tasks[ for Index.row]!.state == URLSessionTask.State.running { // Wait for task to finish return } task = getTask( for Index: for Index) tasks[ for Index.row] = task task.resume() } } 

Наконец, пришло время построить и вернуть каждую cell . Итак, в этом методе:

  func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let foodCell = collectionView.dequeueReusableCell( withReuseIdentifier : "foodCell" , for : indexPath) as! collectionViewCell if let img = imageArray[indexPath.row] { foodCell.foodImage.image = img } else { requestImage( forIndex : indexPath) } return foodCell } 

Как мы видим, метод requestImage(forIndex: IndexPath) помогает нам наблюдать, загружено ли уже изображение, обрабатывается или не загружается вообще.

Предзагрузка

Чтобы ваше приложение имело «плавную плавную» производительность, вы должны стремиться к анимации приложения, которая работает со скоростью 60 кадров в секунду. Это означает, что данный кадр пользовательского интерфейса должен отображаться менее чем за 16,67 мс, чтобы анимация выглядела «плавной». В противном случае, когда частота кадров падает ниже, это становится очевидным для пользователя в виде изменчивая анимация.

Предварительная выборка — это механизм, с помощью которого вы получаете уведомление заранее, прежде чем представлению сбора понадобится отобразить ячейки, что дает возможность подготовить источник данных ячейки до фактического создания ячейки. Предварительная выборка обеспечивается протоколом UICollectionViewDataSourcePrefetching и содержит две функции экземпляра:

 public protocol UICollectionViewDataSourcePrefetching : NSObjectProtocol { // indexPaths are ordered ascending by geometric distance from the collection view @available(iOS 10.0, *) public func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) // indexPaths that previously were considered as candidates for pre-fetching, but were not actually used; may be a subset of the previous call to -collectionView:prefetchItemsAtIndexPaths: @available(iOS 10.0, *) optional public func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) } 

Реализация предварительной выборки источника данных

Первый и обязательный метод вызывается, когда представление Collection View готово начать создавать ячейки еще до того, как они появятся на экране. Массив объектов NSIndexPath передается в функцию и используется для подготовки источника данных. В нашем коде мы напишем что-то вроде этого:

  func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { for indexPath in indexPaths{ requestImage( for Index: indexPath) } } 

Реализация отмены предварительной выборки

Как упоминалось выше, второй метод collectionView(_:cancelPrefetchingForItemsAt:) — это дополнительная функция, которая позволяет вам возвращать или очищать источник данных, когда предварительная выборка для массива ячеек была отменена представлением Collection View . Это может произойти, когда прокрутка меняет направление или становится слишком быстрой, чтобы предварительная выборка могла быть реализована эффективно. Итак, давайте реализуем последнюю функцию в нашем коде:

 func collectionView( _ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { for indexPath in indexPaths{ if let task = tasks[indexPath.row] { if task .state != URLSessionTask.State.canceling { task .cancel() } } } } 

В некоторых случаях вы можете отключить предварительную выборку представления коллекции. Это можно сделать, установив для UICollectionView isPrefetchingEnabled false .

Эти новые функции также можно использовать в UITableView просто реализуя протокол UITableViewDataSourcePrefetching .

Вывод

Использование этого протокола и его функций является хорошим выбором. Больше не нужно беспокоиться о производительности ячеек, поскольку они готовы выйти на экран до того, как их увидят, и с плавной прокруткой наше приложение чувствует себя лучше.