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

Содержание

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

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

iOS 9 Задания


Для выполнения Задания 4 необходим материал  Лекции 8 и Лекции 9. Исходное приложение Smashtag L9 находится на сайте Стэнфорда для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

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

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

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

Код для Обязательных пунктов 1- 7 находится на Github для Xcode 7 и Swift 2.2.
Код для Обязательных пунктов 1- 10 находится на Github для Xcode 7 и Swift 2.2.
Код для Дополнительных пунктов 1- 5 находится на Github для Xcode 7 и Swift 2.2.
Код для Дополнительного пункта 6 можно найти на Github для Xcode 7 и Swift 2.2.

Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

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

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

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

Screen Shot 2015-07-12 at 4.52.33 PM

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

Screen Shot 2015-07-12 at 5.01.15 PM

Screen Shot 2015-07-12 at 5.08.15 PM

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

Screen Shot 2015-07-12 at 5.13.30 PM

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

Screen Shot 2015-07-12 at 5.38.30 PM

Screen Shot 2015-07-12 at 5.42.43 PM

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

Screen Shot 2015-07-12 at 5.56.44 PM

Мы уже имели дело с изображениями и их выборкой из «сети» в классах ImageViewController и ImageTableViewCell. Так что код очень похож на код этих классов. Public API этого класса будет imageURL: NSURL?  и кэш cache:NSCache? для кэширования изображений. Вычисляемая переменная image введена для удобства манипулирования свойством image в imageVIew и позволяет очень удобно остановить индикатор активности («колесико»).

В отношении кэширования мы следуем подсказки b) в Задании 4 для Дополнительных пунктов:

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

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

Screen Shot 2016-07-20 at 9.01.04 PM

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

Screen Shot 2016-07-22 at 8.10.08 AM

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

Screen Shot 2016-07-22 at 8.19.15 AM

Теперь рассмотрим класс  ImageCollectionViewController который приходит с готовым кодом шаблона.
Согласно подказке № 6a для дополнительных пунктов

Шаблон, который вы получите при создании subclass UICollectionViewController, вызывает registerClass в viewDidLoad. УДАЛИТЕ ЭТУ СТРОКУ КОДА. Вместо этого вы будете устанавливать класс UICollectionViewCells на storyboard. Если вы не уничтожите этот вызов registerClass, то он переопределит все, что вы задали на storyboard, так как  viewDidLoad вызывается после того, как выполнена загрузка со storyboard.

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

Screen Shot 2016-07-22 at 9.40.20 AM

Затем мы будем использовать его в реализации методов UICollectionViewDataSource (тот же самый механизм, что и UITableViewDataSource). Но сначала мы должны определиться с public API и Моделью для нашего Collection View. Это будут разные структуры и нам понадобятся некоторые преобразования. Public API  очень простой — это двухмерный массив всех выбранных по заданному критерию твитов в коренном фрагменте

Screen Shot 2015-07-13 at 8.43.10 AM

а вот Модель для Collection Views (внутренняя структура данных) будет представлять собой одномерный массив структур TweetMedia

Screen Shot 2015-07-13 at 8.45.18 AM

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

Screen Shot 2016-07-21 at 7.40.09 AM

Теперь мы можем представить методы Data Source для Collection View

Screen Shot 2016-07-22 at 8.51.41 AM

В последнем методе мы видим, что public API для прототипа ячейки ImageCollectionViewController лучше сделать не просто imagURL, необходимое для загрузки изображения, а целиком структуру TweetMedia. Поэтому вернемся к классу  ImageCollectionViewCell, обслуживающему наш прототип ячейки и изменим его public API.

Screen Shot 2015-07-13 at 9.02.20 AM

Конечно, при загрузке изображения нам не нужна вся структура TweetMedia, но при «переезде» на другой View Controller для отображения соответствующего твита она нам пригодится в методе prepareForSegue.

Screen Shot 2015-07-13 at 9.25.51 AM

До сих пор наша работа с 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

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

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

Screen Shot 2016-07-22 at 12.44.27 PM

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

Screen Shot 2016-07-22 at 2.17.47 PM

В ImageCollectionViewController осталось выполнить преобразование структуры public API в структуру Модели для Collection View, то есть нам нужно преобразовать var tweets: [[Tweet]] в var images = [TweetMedia](). Выполняем это с помощью функций flatMap и map для массивов.

Screen Shot 2015-07-13 at 10.00.06 AM

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

Screen Shot 2016-07-22 at 12.56.43 PM
. . . . . . . . . . . .
Screen Shot 2016-07-22 at 1.49.39 PM

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

Screen Shot 2015-07-13 at 11.08.45 AM

При нажатии на эту кнопку мы «переезжаем» на вновь созданный  ImageCollectionViewController, передавая ему все выбранные твиты в качестве public API.

Screen Shot 2015-07-13 at 11.11.23 AM

Обратите внимание, что segue идет НЕ ОТ ячейки таблицы, а ОТ полного Tweet Table View Controller.

Screen Shot 2016-07-21 at 8.00.48 AM

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

Screen Shot 2015-07-14 at 9.46.53 AM

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

Screen Shot 2016-07-21 at 7.52.36 AM

В классе ImageCollectionViewController в методе prepareForSegue передаем TweetTableViewController массив твитов tweets = [[Tweet]](), состоящий из одного твита, соответствующего изображению, на которое мы кликнули.

Screen Shot 2016-07-21 at 3.30.27 PM

Дело в том, что в public API класса TweetTableViewController  входит не только текстовое поле searchText для поиска твитов, но и двухмерный массив твитов tweets = [[Tweet]](), который мы можем устанавливать извне. В этом случае нам не нужно выполнять выборку данных и метод searchForTweets(), у нас уже есть наш единственный твит, уже размещенный в массиве  tweets, и работает перегрузка таблицы tableView.reloadData()  в didSet {} :

Screen Shot 2016-07-21 at 3.42.51 PM

Кстати, если вы не хотите видеть пустой экран при старте приложения, укажите в методе viewDidLoad(), что в случае отсутствия твитов, нужно запустить выборку с помощью функции searchForTweets(), а она уже разберется, откуда взять строку с текстом поиска твитов: то ли из хранилища NSUserDefaults, то ли дать значение по умолчанию «#stanford».

Screen Shot 2016-07-21 at 3.52.15 PM

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

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

Screen Shot 2016-07-21 at 3.55.33 PM

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

Screen Shot 2016-07-21 at 3.57.03 PM

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

Screen Shot 2016-07-21 at 4.12.56 PM

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

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

  1. Татьяна, подскажите, пожалуйста, почему мы устанавливаем «стоимость» объекта кэша именно в килобайтах, а не в байтах? Заранее благодарю.

    • Специальных мыслей по поводу килобайт не было, просто числа поменьше и проще следить за объектами в кэше. Я знаю, что стандартное решение — в байтах, но я бы предпочла кэш, ориентированный на время помещения объекта в кэш, но я с этим не экспериментировала.

Обсуждение закрыто.