Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — дополнительный пункт 6. UICollectionView и перемещение ячеек.

Содержание

Текст Домашнего Задания 4 на английском языке доступен на  iTunes в пункте “Programming: Project 4: Smashtag Mentions″На русском языке вы можете скачать здесь:

Задание 4 iOS 10.pdf

iOS 10 Задания


В Задании 4 вы должны усовершенствовать приложение Smashtag, которое мы создали на Лекции 9, чтобы обеспечить быстрый доступ к хэштэгам hashtags, URLs urls, изображениям images и пользователям users, упомянутым в твите. Основными идеями в этом Задании являются многопоточность, работа с таблицей Table View, глубокое знание Navigation Controller, множественные MVC типа Tab Bar Controller и работа с изображениями с помощью Scroll View.

Основой для решения Задания 4 является демонстрационный пример «Smashtag L9«, код которого доступен как на  iTunes название “Lecture 9 Demo Code: Smashtag«, так и на Github.

В данном посте представлено решение Дополнительного пункта 6 Задания 4. Код можно найти на Github.

Начало решения Задания 4 находится в постах:

Задание 4 CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — обязательные пункты 1- 7.
Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — обязательные пункты 8 — 10.

Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — дополнительные пункты 1-5.

Код для Обязательных пунктов 1- 7 находится на Github.
Код для Дополнительного пункта 6 находится на Github.

Пункт 6 дополнительный

Добавьте некоторый UI элемент, который показывает новый View Controller, отображающий UICollectionView всех первых изображений (image) (или, если хотите, всех images) во всех твитах, которые удовлетворяют условиям поиска.  Когда пользователь кликает на изображении (image) в этом UICollectionView, “переезжайте” (segue) на показ этого Tweet (даже если вы будете показывать один Tweet, вам все равно следует использовать ваш TweetTableViewController и тогда вам будет доступна функциональность “преследования mentions”).


Добавляем на storyboard  Collection View Controller. У него есть ячейка для размещения изображений ( cell, как и в случае с таблицей), но это двухмерная ячейка и прототип этой ячейки тоже будет двухмерным. Мы изменим некоторые настройки по сравнению с теми, которые пришли из шаблона:

В прототипе ячейки мы разместим изображение Image View и индикатор активности (activity indicator), задавая им необходимые ограничения (constraints) для системы Autolayout.  Изображение Image View «приклеиваем» к краям ячейки:

Индикатор активности привязываем к середине изображения Image View:

Для ячейки, также как и в случае с таблицей, для прототипа ячейки нужно задать идентификатор (reuse identifier) ячейки:

Давайте добавим два новых класса к нашему проекту: один — для Collection View Controller и назовем его ImageCollectionViewController (superclass — UICollectionViewController):

другой — для прототипа ячейки Collection View Cell и назовем его ImageCollectionViewCell (superclass — UICollectionViewCell):

Начнем размещать вначале код для ячейки в классе  ImageCollectionViewCell. Создим Outlets imageView  и spinner для  изображение Image View и индикатор активности (Activity indicator View):

Мы вернемся к конфигурации ячейки коллекции позже, а сейчас рассмотрим  ImageCollectionViewController, который приходит с готовым кодом шаблона.  В начала мы должны определиться с public API и внутренней структурой, удобной для функционирования нашей коллекции Collection View. Это будут разные структуры и нам понадобятся некоторые преобразования. Public API очень простой — это двухмерный массив всех выбранных по заданному критерию твитов в коренном фрагменте Tweet Table View Controller :

а вот удобной внутренней структурой images для коллекции Collection View  будет одномерный массив структур TweetMedia. Нам не понадобятся секции — все изображения будут размещаться в одной секции, поэтому массив images является одномерным:

Screen Shot 2015-07-13 at 8.45.18 AM

В структуру TweetMedia, конечно, должно войти само изображение вместе со своим соотношением сторон ( Aspect Ratio), а также твит, которому оно принадлежит, ведь после выбора изображения, мы должны показать соответствующий твит. Поэтому «приклеим» к каждому изображению твит:

Screen Shot 2016-07-21 at 7.40.09 AM

Как только public API нашего класса будет установлениями извне, мы должны преобразовать его двумерный массив [[Twitter.Tweet]] в одномерный  [TweetMedia]. Это выполняем это с помощью функций flatMap и map для массивов:

Массив tweets — двухмерный и поддерживает твиты из различных загрузок в отдельных подмассивах. Во-первых, мы должны превратить этот двухмерный массив в одномерный с помощью функции flatMap. Эта процедура называется «выпрямлением» (flatten) массива массивов. Отдельный твит может иметь множество изображений ( images). Для каждого твита создаем массив новых структур данных, содержащих индивидуальные изображения  (images) – или ничего. Преобразуем их с помощью функции map в новый двумерный массив, содержащий эти подмассивы и, наконец, «выпрямляем «, принимая во внимание, что есть твиты без изображений.

Теперь мы можем использовать массив images в реализации методов UICollectionViewDataSource (тот же самый механизм, что и UITableViewDataSource) для коллекции Collection View:

В последнем методе мы видим, что public API для прототипа ячейки ImageCell является целиком структура TweetMedia и общий кэш cache, предназначенный для кэширования изображений:

Мы используем общий для всех ячеек кэш cache:Cache?, следуя подсказке a) в Задании 4 для Дополнительных пунктов:

a) Так как, очевидно, все ваши изображения будут загружаться за пределами main thread, то прокручивание (scrolling around) будет происходить быстро, но, если вы будете повторно загружать их снова и снова по мере того, как пользователь осуществляет прокрутку, то вы будете получать много пустого пространства, которое заполняется со временем, но это не будет выглядеть так уж здорово. Так что нужно кэшировать изображения (images). Познакомьтесь с классом NSCache. Это как NSDictionary (objectForKey и  setObject:forKey), но добавляется концепция “стоимости” того, что будет в кэше, с помощью setObject:forKey:cost:. “Стоимостью” изображения мог бы быть размер в kb, например. NSCache будет выбрасывать из кэша элементы в любое время, когда ему захочется, так что вы всегда будете искать нужный URL (чтобы найти соответствующее изображение UIImage), использовать его, если вы нашли, или опять загрузить его, если вы не нашли. Вы скорее всего будете  ассоциировать кэш с вашим subclass UICollectionViewController (таким образом, чтобы он разделялся (shared) всеми ячейками и удалялся бы при исчезновении Controller). Вы должны правильно выполнить конфигурацию кэша, чтобы он был доступен для всех ячеек.

Для простоты использования кэша NSCache разработан специальный класс  Cache, который помогает работать с данными Swift: URL и Data вместо данных NSURL и NSData, кроме того subscript делает работу с NSCache более удобной:

“Стоимостью” изображения cost является размер данных изображения data в kb.

Выборка изображения происходит либо из кэша cache, куда оно попадает при загрузке изображения из Интернета и выбрасывается по показателю “стоимости” изображения cost, либо заново загружается из Интернета с использованием параллельной очереди:

Кэш cache задается в классе ImageCollectionViewController

и передается ячейке imageCell в методе cellForItemAt протокола dataSource:

Теперь рассмотрим класс ImageCollectionViewController, который приходит с готовым кодом шаблона.

Мы уже провели необходимую работу  c экранным фрагментом  Image Collection View Controller на storyboard, то нам нужно только зафиксировать идентификатор для ячейки Collection View в структуре Storyboard:

До сих пор наша работа с Collection View ничем не отличалась от работы с Table View. Но у Collection View есть мощное свойство  сollectionViewLayout, обслуживаемое классом UICollectionViewLayout, которое отвечает за расположение ячеек на экране. Apple «в коробке» поставляет один из таких вариантов расположения ячеек — потоковое расположение ячеек Flow Layout (класс UICollectionViewFlowLayout), которое соответствует некоторой «динамической» сетке, выстраиваемой по вертикале или по горизонтали. Логика работы  Flow Layout похожа на логику расположения символов в “выравненном тексте” :

Screen Shot 2016-07-22 at 10.40.19 AM

На рисунке представлена вертикальная потоковая сетка. Вот ее правила работы:

  • Элемент с наибольшей высотой height будет определять высоту строки
  • Все элементы в строке выравниваются по вертикали по центру
  • Минимальный зазор — это minimum расстояния между двумя элементами, но реальные зазоры зависят от ширины width Collection View
  • Flow Layout добавляет отбъекты с минимальным зазором до тех пор, пока это возможно, но затем реальный зазор увеличивается
  • Каждая секция имеет свои собственные зазоры для строки / элемента
  • В секции зазоры для строки / элемента фиксированы; у вас не может быть различных зазоров для для строки / элемента в секции
  • Каждая секция имеет свои собственные отспупы( inset)

Установки Flow Layout можно сделать в специальной структуре констант FlowLayout:

Screen Shot 2016-07-22 at 12.51.00 PM

Опираясь на эти установки, Collection View  выполняет настройку Flow Layout …

Screen Shot 2016-07-22 at 1.55.54 PM

… и  показывает на экране наши изображения.

Screen Shot 2016-07-22 at 1.58.44 PM

Мы видим, что все ячейки одинакового размера и изображение обрезается по длинной стороне. Это не очень хорошо — хотелось бы увидеть изображения, так сказать, «во весь рост».

Для этого нам дается подсказка b) к дополнительным пунктам:

b) Большая разница между UITableView и UICollectionView состоит в том, что таблица (Table View) всегда имеет одно и тоже расположение элементов (то есть строки в единственном столбце). А коллекция изображений (Сollection View) имеет свойство UICollectionViewLayout,  которое определяет как располагаются ячейки (и поэтому является чрезвычайно гибким инструментом). UICollectionView по умолчанию имеет тип расположения ячеек UICollectionViewFlowLayout, который похож на расположение символов в “выровненном тексте”. Это прекрасно подходит для наших целей! Такие вещи, как размер ячейки, определяются делегатом как в таблице Table View, так и в коллекции Сollection View. Но в СollectionView делегат предоставляет протокол, который специально разработан для “движка” расположения ячеек. Для расположения ячеек типа  FlowLayout, протокол называется UICollectionViewDelegateFlowLayout. Поэтому, если вы хотите управлять, например,  размером ячеек, то вам надо реализовать метод  collectionView:layout:sizeForItemAt: протокола в вашем subclass UICollectionViewController.

Если мы хотите управлять размером size ячейки в соответствии с различными соотношениями сторон ratio (Aspect Ratio), то нам необходимо  реализовать метод  collectionView:layout:sizeForItemAt: протокола UICollectionViewDelegateFlowLayout в нашем классе ImageCollectionViewController:

Для этого нам необходимо подтвердить протокол UICollectionViewDelegateFlowLayout в нашем классе ImageCollectionViewController:

В подсказке  с) к дополнительным пунктам нам предлагают пойти более легким путем:

Вы можете пойти более легким путем и выбрать предопределенный размер ячеек (predetermined size) в UICollectionView или, возможно лучше, выбрать предопределенную “область” (то есть  width x height) для каждой ячейки (но выполнять настройку aspect ratio каждого изображения).

Для этого мы рассчитаем предпочтительный размер ячейки sizePredefined: на основе информации FlowLayout о параметрах расположения ячеек типа  FlowLayout:

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

Screen Shot 2016-07-22 at 2.17.47 PM
Осталось добавить распознаватель жестов pinch для изменения масштаба представления изображений. Это уже знакомая нам процедура: добавляем переменную scale, представляющую масштаб, добавляем распознаватель жестов в методе «жизненного» цикла viewDidLoad c указанием обработчика жеста zoom: и учитываем масштаб при задании размера изображения:


. . . . . . . . . . . .

Запускаем приложение и используем жест  pinch для  изменения масштаба  изображений:

На этом мы закончили с ImageCollectionViewController, осталось определить его взаимодействие с другими экранными фрагментами и мы возвращаемся на storyboard.
Добавим на экранный фрагмент Tweet Table View Controlle кнопку с изображением «камеры»:

При нажатии на эту кнопку мы «переезжаем» на вновь созданный  ImageCollectionViewController, с помощью Show segue, которому присвоен идентификатор «Show Images«:

передавая ему все выбранные твиты в качестве public API:

Запускаем приложение и получаем результат:

Screen Shot 2015-07-14 at 9.46.53 AM

Далее от нас требуют, чтобы при выборе какого-то изображения мы «переезжали на View Controller, показывающий соответствующий твит.  Создаем  segue ОТ ячейки  Collection View к Tweet from Сollection View:

Для того, чтобы принимать твиты извне, добавим в public API класса TweetTableViewController помимо текстового поля searchText для поиска твитов, одномерный массив твитов newTweets = Array<Twitter.Tweet>. В этом случае нам не нужно выполнять выборку данных и метод searchForTweets(), у нас уже есть массив новых твитов, который мы вставим в первую секцию массива твитов tweets = [Array<Twitter.Tweet>]():

В классе ImageCollectionViewController в методе prepare(for segue:) установил наш новый API newTweets в твит, соответствующий изображению, на которое мы кликнули:

Запускаем приложение — получаем нужный результат.

Screen Shot 2016-07-21 at 8.13.09 AM

Screen Shot 2016-07-21 at 8.16.07 AM

Screen Shot 2016-07-21 at 8.19.54 AM

Код находится на Github.

Перемещение ячеек в UICollectioView с помощью пары строк кода

У класса UICollectionViewController в iOS 9 появилось новое свойство installsStandardGestureForInteractiveMovement, которое добавляет стандартные жесты для перегруппировки ячеек. Если его установить в true, то будет доступно перетягивание ячеек на новые места с помощью pan и «бросание» их на новом месте после того, как вы с помощью жеста long press выберите передвигаемую ячейку. Дополнительно вы должны реализовать два метода UICollectionViewDataSource.

Screen Shot 2015-07-20 at 12.00.29 PM

Первый метод позволяет вам передвигать элементы UICollectionView c определенным индексом indexPath, а второй — ответственен за перегруппировку DataSource (Модели данных для UICollectionView).
И это все! Мы получим коллекцию передвигаемых View ячеек всего за пару строк!
Давайте применим эту «пару строк» к нашему приложению Smashtag.
Для этого добавим строку в viewDidLoad:

Далее реализует два метода UICollectionViewDataSource:

И это действительно все. Запускаем приложение. Выбирать ячейку для передвижения нужно с помощью жеста long press.

Screen Shot 2016-07-21 at 4.12.56 PM

Код можно найти на Github.