UICollectionView DataSourcePrefetching
Недавно Apple объявила о новом UICollectionViewDataSource
протокола UICollectionViewDataSource
, которое называется UICollectionViewDataSourcePrefetching
. Это расширение позволяет реализовать две новые функции, обеспечивающие абсолютно плавную прокрутку. Этот урок покажет вам, как его использовать.
Вы можете найти исходный код на GitHub .
Настройка проекта Xcode
Откройте Xcode и создайте новый проект на основе шаблона приложения Single View . Прежде чем делать что-либо еще, чтобы улучшить внешний вид нашего приложения, выберите представление и выберите « Редактор» -> « Встроить» -> « Контроллер навигации». Теперь перетащите представление Collection View
из библиотеки объектов на холст и отрегулируйте его размер.
Обратите внимание, что в представлении « Collection View
есть одна повторно используемая cell
, поэтому щелкните под Инспектором атрибутов и foodCell
этой ячейке идентификатор , назовем его foodCell
. Перетащите UIImageView
на него и отрегулируйте его размер в соответствии с содержимым.
Создайте новый файл типа Cocoa Touch Class , подкласс UICollectionViewCell
, назовите его collectionViewCell
и назначьте его cell
.
Подключите UIImageView
к коду collectionViewCell.swift и назовите его « foodImage ». Кроме того, подключите collectionView
к ViewController.swift .
Соответствие протоколам и написание некоторых строк
Теперь пришло время написать код в viewController.swift .
Первое, что нужно сделать, это согласовать класс с UICollectionViewDelegate
, UICollectionViewDataSource
и UICollectionViewDataSourcePrefetching
.
Вероятно, вы должны увидеть некоторые ошибки на боковой панели, потому что необходимые методы еще не реализованы. Мы рассмотрим это в ближайшее время, но давайте сначала закончим небольшую настройку.
Нам понадобится источник данных для этого 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
.
Вывод
Использование этого протокола и его функций является хорошим выбором. Больше не нужно беспокоиться о производительности ячеек, поскольку они готовы выйти на экран до того, как их увидят, и с плавной прокруткой наше приложение чувствует себя лучше.