Задание 5. Top Places (Objective-C + iOS 9). CS 193P iOS 7 2014. Обязательные пункты 1 -5.

Screen Shot 2016-02-08 at 1.26.59 PM

Содержание

Цель задания — создать приложение, которое показывает пользователю список наиболее популярных мест на Земле, в которых сделаны фотографии и размещены на сервисе Flickr, а также дает возможность просмотра этих фотографий. Это Задание преследует следующие методические задачи:  вы должны научиться работать с Table Views, Scroll Views, Image View и многопоточностью, узнать, как строить универсальные приложения, которые работают на любых как  iPhone, так и на iPad (с соответствующим UI на каждом).
Все данные, которые вам необходимы, будут загружаться с Flickr.com c использованием API Flickr. Вам будет предоставлен код для создания URLs запросов к Flickr, которые понадобятся в этом домашнем задании.

Текст Домашнего задания на английском языке доступен на  iTunes в пункте  “Developing iOS 7 app:Assignment 5″
На русском языке 

Задание 5 Top Places fall 2013.pdf

Задание выполнялось в Xcode 7 iOS 9. Режимы «Use Auto Layout» и «Use Size classes» в этом Домашнем задании включены.
Код Задания 5 находится на Github. Ниже представлен мой вариант решения Задания 5. У Вас может быть совсем другая логика построения этого приложения.
Это первая часть Задания 5.
Вторая часть Задания 5 находится здесь.

Пункт 1

Загрузите данные с URL, поставляемого методом URLForTopPlaces вспомогательного класса FlickrFetcher, для получения массива наиболее популярных мест, где были сделаны Flickr фотографии за последнее время. Смотри (Подсказки (Hints)) как интерпретировать данные, возвращаемые Flickr.

Создайте новый проект по шаблону Single View Application

Screen Shot 2016-01-28 at 6.29.39 PM

Сделайте это приложение универсальным  (чтобы оно запускалось на iPhone и iPad)

Screen Shot 2016-01-28 at 6.30.50 PM

Добавьте Flickr-API к проекту путем размещения папки Flickr Fetch (полученной с сайта Stanford, но соответствующим образом исправленной).

Screen Shot 2016-01-29 at 11.17.53 AM

Получите API key непосредственно на Flickr и установите его в FlickrAPIKey.h.

Screen Shot 2016-01-29 at 11.20.18 AM

В классе ViewController, импортируем файл FlickrFetcher.h и создаем свойство NSArray *places для накопления интересующих нас данных с сервиса Flickr.

Screen Shot 2016-01-29 at 11.25.26 AM
Массив NSArray *places представляет собой массив словарей, содержащих информацию о местах на Земле, где сделаны фотографии и размещены на Flickr. Будем считывать данные с помощью NSURLSession, поэтому определяем сессию NSURLSession *session, задание на загрузку данных NSURLSessionDownloadTask *task с определенного URL и получаем блок completionHandler: 
^(NSURL *location, NSURLResponse *response, NSError *error)
, в котором можем проверять ошибки и вести обработку полученной информации

Screen Shot 2016-01-29 at 11.59.59 AM

При обработке следуем подсказкам, находящимся в Задании 5:

Подсказка №3. Данные, возвращаемые с Flickr, представлены в JSON формате. iOS имеет встроенный JSON парсер. Просто создайте  NSData, содержащие информацию, возвращаемую с Flickr (используйте метод dataWithContentsOfURL: класса NSData); затем преобразуйте JSON в property list (то есть объекты NSArray и NSDictionary) используя метод класса в NSJSONSerialization с именем JSONObjectWithData:options:error:. Вы можете передать 0 в качестве аргумента для options.

Согласно этой подсказки № 3 получаем код

Screen Shot 2016-01-29 at 12.04.16 PM

Подсказка №5. Верхний уровень результатов запроса Flickr — это словарь. Внутри этого словаря находится массив с вашими результатами. Например, для получения массива мест из данных, возвращенных методом URLForTopPlaces (давайте предположим, что вы уже преобразовали Flickr результаты из формата JSON в словарь с именем results) , вы, во-первых, можете использовать NSDictionary *placesResults = results[@”places”] и тогда получаем массив NSArray *places = placesResults[@”place”]. Альтернативно, вы можете сделать это путем вовлечения одного метода:

NSArray *places = [results valueForKeyPath:@”places.place”];

Мы выбрали второй альтернативный вариант применения одного метода

Screen Shot 2016-01-29 at 12.10.46 PM
Для того, чтобы получать данные и их обрабатывать, нужно знать URL для запроса наиболее популярных мест на Flickr. Для этого используем подсказку № 4

Подсказка №4. Первая вещь, которую вы, возможно, захотите сделать как только скопируете код FlickrFletcher к себе в приложение (и добавите свой API ключ) — это выберите данные с помощью URLForTopPlaces , затем сделаете парсинг JSON и наконец NSLog() результатов парсинга. Таким образом вы сможете увидеть формат выбранных данных Flickr. Тоже самое можно повторить, когда вы запрашиваете список фотографий для заданного места.

Это соответствует строке кода

[objc]
NSURL *url = [FlickrFetcher URLforTopPlaces];
[/objc]

Печать JSON данных нужно организовывать в main queue, поэтому возвращаемся в блоке в  main queue и печатаем содержимое полученных данных на консоли:

Screen Shot 2016-01-29 at 12.27.42 PM
Вот результаты печати, и мы можем сделать вывод, что self.places — это массив словарей. Каждый словарь содержит все необходимые данные для определенного местоположения — это описание места (ключ «_content»), это количество фотографий, сделанных в этом месте (ключ «photo_count»), это географические координаты (ключи «latitude» и «longitude»).

[js]
(
{
"_content" = "Bordeaux, Aquitaine, France";
latitude = "44.849";
longitude = "-0.576";
"photo_count" = 92;
"place_id" = VITSM0tUWrqXwo4;
"place_type" = locality;
"place_type_id" = 7;
"place_url" = "/France/Aquitaine/Bordeaux";
timezone = "Europe/Paris";
"woe_name" = Bordeaux;
woeid = 580778;
},
{
"_content" = "Helsinki, Uusimaa, Finland";
latitude = "60.171";
longitude = "24.932";
"photo_count" = 29;
"place_id" = JOvLad9UVL9At9A;
"place_type" = locality;
"place_type_id" = 7;
"place_url" = "/Finland/Uusimaa/Helsinki";
timezone = "Europe/Helsinki";
"woe_name" = Helsinki;
woeid = 565346;
},
{. . . . . . . . . .
[/js]

Пункты 2, 3

2. Создайте UITabBarController пользовательский интерфейс с двумя закладками. Первая закладка показывает UITableView список мест, полученных в обязательном пункте 1, разделенных на секции по странам, а внутри секции по алфавиту. Вторая закладка показывает UITableView список из 20 недавно просмотренных (в вашем приложении) фотографий (в хронологическом порядке с поздним в качестве первого фото и без дупликатов).

3. При появлении популярного места в UITableView в вашем приложении, наиболее детальная часть местоположения (например, имя города) должна быть title ячейки (cell)  UITableView, а остальная часть местоположения (например, штат, провинция и т.д.) должна появляться как subtitle ячейки UITableView. Название страны должно быть в заголовке секции.

Для этого на storyboard убираем существующий обычный View Controller и добавляем Tab Bar Controller, убираем два Сontrollers, которые прибыли вместе с Tab Bar Controller. Добавляем два новых Table View Controllers, вставляем их в   Navigation Controllers и подсоединяем к Tab Bar Controller. Даем имя закладкам и добавляем иконки для списка мест Top Places и для списка «недавних» фотографий  Resents:

Screen Shot 2016-01-29 at 7.01.44 PM
Начнем с закладки Top Places. Создадим с помощью меню File -> New -> File -> Cocoa Class пользовательский класс TopPlacesTVC, который наследует от UITableViewController

Screen Shot 2016-01-29 at 7.13.42 PM
И назначим этот класс на storyboard Table View Controller, соответствующему закладке Top Places

Screen Shot 2016-01-29 at 7.18.54 PM
Кроме того, устанавливаем тип прототипа для ячейки таблицы Subtitle и идентификатор прототипа «Top places«.

Screen Shot 2016-01-29 at 8.08.02 PM

Перенесем загрузку данных с Flickr из предыдущего View Controller и добавим методы делегата для Table View. Но сначала разберемся с Моделью. Пусть Модель будет свойство:

@property (nonatomic,strong) NSArray *places;

которое представляет собой массив словарей NSDictionary , отображающих места, где сделаны Flickr фотографии.  Этот массив мы получили выше с помощью  NSURLSession. Так как наши данные в таблице будут группироваться по странам, то нам понадобится больше, чем просто массив мест places в качестве внутренней структуры данных для UITableViewController. Создадим внешний словарь NSDictionary по странам placesByCountry, в котором ключом будет страна country, а значением — массив мест — словарей, соответствующих этой стране

@property (nonatomic, strong) NSDictionary *placesByCountry;

Мы будем заполнять этот словарь с помощью метода createPlacesByCountryForPlaces всякий раз, как кто-то будет устанавливать нашу Модель, то есть свойство  places 

Screen Shot 2016-01-29 at 7.53.44 PM
Screen Shot 2016-01-30 at 10.26.43 AM

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

Screen Shot 2016-01-30 at 10.20.50 AM

Теперь у нас есть словарь мест по странам self.placesByCountry и страны self.countries, отсортированные по имени в алфавитном порядке, и мы можем приступить к реализации методов делегата UITableViewController

Screen Shot 2016-01-30 at 11.37.25 AM
. . . . . . . . . . . . . . . . .

Screen Shot 2016-01-30 at 11.40.08 AM
Для конфигурации ячейки таблицы мы должны получить title и subtitle, которые представляют собой название места и все остальное, связанное с его описанием. Для этого получаем из словаря topPlaceDictionary топового места, соответствующего этой строке таблицы, описание места topPlaceDiscription, которое может выглядеть, например, так

«Mur Mawr, Wales, United Kingdom» или так

«Bollene, Provence-Alpes-Cote d’Azur, France»

Для извлечения элементов названия топового места используем метод componentsJoinedByString для NSString и получаем массив с этими элементами, причем название страны (United Kingdom и France) находится на последнем месте.

Screen Shot 2016-01-30 at 12.09.53 PM

Пункт 4

Если пользователь выберет место в UITableView, вы должны опять запросить у Flickr 50 фотографий с этого места и показать их в списке. URL поставляется вспомогательным классом FlickrFetcher и методом класса URLForPhotosInPlace: maxResult: для получения этих фотографий из Flickr.  

Перетягиваем на storyboard новый Table View Controllor, не забываем установить идентификатор для прототипа ячейки таблицы «Flickr Photo Cell»

Screen Shot 2016-01-30 at 1.39.19 PM

Создаем Show Segue от ячейки первой таблицы (Table View Controller) к этому новому Controller с помощью CTRL-перетягивания.

Screen Shot 2016-01-30 at 1.46.16 PM
Screen Shot 2016-01-30 at 1.51.27 PM

И не забудем дать идентификатор этому segue:
Screen Shot 2016-01-30 at 1.56.32 PM
Создадим новый FlickrPhotos, который наследует от UITableViewControllerCreate и используется для показа списка фотографий с public свойством NSArray *photos, которое представляет собой массив фотографий:

Screen Shot 2016-01-30 at 2.21.42 PM
Не забудьте установить пользовательский класс для нашего нового Controller

Screen Shot 2016-01-30 at 3.50.12 PM

Каждый раз, когда мы будем устанавливать это свойство, мы будем перегружать данные в таблице:

Screen Shot 2016-01-30 at 2.40.00 PM
Реализуем методы DataSource для таблицы со списком фотографий:
Screen Shot 2016-01-30 at 4.56.11 PM
Для получения заголовка и подзаголовка в ячейке таблицы фотографий используем дополнительные методы, так как в Задании 5 специально оговаривается как они должны выглядеть. Поэтому временно выполним пункт 5, а затем опять вернемся к пункту 4 и продолжим работу на таблицей со списком фотографий и реализацией перехода на нее из списка популярных мест.

Пункт 5

Любой список фотографий должен показывать заголовок фотографии как title ячейки UITableView, а описание (description) фотографии как subtitle ячейки UITableView. Если у фотографии нет заголовка, используйте описание (description) фотографии как title ячейки. Если у фотографии нет ни заголовка, ни описания, используйте “Unknown” как title ячейки UITableView. Ключи словаря с информацией о Flickr фотографии определены в FlickrFetcher.h.

Создаем два дополнительных метода : один — для title

Screen Shot 2016-01-30 at 5.08.54 PM

, другой — для subtitle:

Screen Shot 2016-01-30 at 5.10.32 PM
В FlickrFletcher.h имеются #defines для всех интересующих вас ключей к данным из Flickr. У некоторых из них есть точки ”.” и их соответственно можно использовать только с valueForKeyPath:, например, FLICKR_PHOTO_DESCRIPTION.

Полученный класс FlickrPhotos является обобщенным (generic) классом  для создания списка Flickr фотографий, причем не имеет значения, откуда взялся этот список фотографий. Для фотографий, полученный для одного из популярных мест мы создадим новый класс PlaceFlickrPhotos, который наследует от FlickrPhotos, и создает список фотографий для топового места. Моделью этого класса, public API, является топовое место place:

Screen Shot 2016-01-30 at 7.33.01 PM
При установке этого топового места place мы запрашиваем у Flickr список 50 фотографий с этого места и устанавливаем public API, нашего superclass  FlickrPhotos, которым является self.photos:

Screen Shot 2016-01-30 at 7.42.22 PM
Нам осталось установить place в методе prepareForSegue для таблицы топовых мест, то есть в классе TopPlacesTVC:

Screen Shot 2016-01-30 at 8.11.47 PM
В результате мы получим нужный нам интерфейс

Screen Shot 2016-01-30 at 8.24.56 PM

Продолжение выполнения Задания 5  TopPlaces находится здесь. Код Задания 5 находится на Github.