Проводя адаптацию предыдущего стэнфордского курса «Developing iOS 7 Apps for iPhone and iPad» 2014 г. к iOS 9, я обнаружила, что в курсе «Developing iOS 8 Apps with Swift» 2015 г. практически отсутствует настройка адаптивного Split View Controller, так как там он использовался только для Графического Калькулятора на Лекции 6 и 7 и в Домашнем Задании 3. В этом варианте Master не представлял собой традиционную таблицу или каскад таблиц, выбирая из которых соответствующий элемент, мы получали в качестве Detail изображение, связанное с выбранным элементом.
В курсе по iOS 8 нам не требовалось более скрупулезной настройки адаптивного UI, состоящего из SplitViewController и Popover.
Мне представляется целесообразным восполнить этот пробел и реализовать универсальное приложение Photomania Universal URL, которое представлено в стэнфордском курсе «Developing iOS 7 Apps for iPhone and iPad» 2014 г, на Swift в iOS 9. Приложение Photomania на Swift и iOS 9 находится на Github. Давайте подробно рассмотрим как оно было построено.
При проектировании этого приложения на iOS 9 и Swift мы должны решить три вопроса:
- создать адаптивный интерфейс в iOS 9 на основе концепции Size Classes
- обеспечить сохранение данных в Core Data и работу с ними UI приложения
- осуществлять подачку данных с сервера Flickr в фоновом режиме
В этом посте решается первая задача, а именно, — создание адаптивного интерфейса со SplitViewController и Popover.
Что представляет собой приложение Photomania Universal URL? Приложение Photomania Universal URL запрашивает сервер Flickr о наиболее “свежих” по времени фотографиях, составляет список фотографов, сделавших эти фотографии, и показывает его вам. Eсли вы кликните на фотографе, то в таблице появляется список сделанных им фотографий. Затем вы можете кликнуть на любой фотографии в списке, и получить как полномасштабное изображение вашей фотографии, так и URL этого изображения.
Вот как работает это приложение с iPhone:
А вот как оно работает с iPad:
В фоновом режиме (in the background) приложение постоянно запрашивать Flickr о все большем и большем количестве последних фотографий и забрасывать их в базу данных Core Data. Список фотографов на экране отслеживает появление этих данных, которые постоянно автоматически обновляются.
Однако спроектированное таким образом для iOS 7 приложение Photomania Universal URL не будет правильно работать на всех приборах в iOS 9: на iPhone 5s и более старших моделях iPhones снизу и сверху на экране будут появляться черные полосы.
Поэтому была поставлена задача создать современный адаптивный UI этого приложения для iOS 9 на Swift. Вот с каким приложением мы собираемся иметь дело. Приложение было спроектировано для iOS 7 и написано на Objective-C. Реализация его включала в себя две storyboards: одна — для iPhone, а другая — для iPad (приведены ниже). Это было в порядке вещей, и ни о каком адаптивном интерфейсе, кроме как использования механизма Autolayout, речь не шла.
С незапамятных времен Split View Controller и Popover в iOS были доступны только на iPad. Начиная с iOS 8, они теперь работают и на iPad, и на iPhone, благодаря концепции Size Classes и их адаптивному поведению. Однако автоматическая адаптация, предложенная Apple «из коробки», чаще всего нас не устраивает и приходится писать небольшой дополнительный код, используя методы делегатов UISplitViewControllerDelegate
и UIPopoverPresentationControllerDelegate
. В данной статье мы будем исследовать адаптивные способности Split View Controller и Popover на примере очень простых практических приложений, работающих с сервером Flickr.com, представляющим собой облачный сервис для хранения фотографий. Сама по себе эта задача имеет большой практический смысл, так как является часто встречающимся случаем, когда данные считываются с некоторого сервера и представляются затем ввиде связанных таблиц и изображений. Попутно мы будем демонстрировать “вживую” такие синтаксические конструкции Swift, как вычисляемые свойства c {get}
и {set}
, наблюдатели свойств didSet{}
, функции высшего порядка map, flatMap, filter
, вывод типа из контекста и перегрузку (overload) функций, совместное использование Swift и Objective-C кода, работу со структурами struct
, использование хранилища NSUserDefaults
и т.д. Но все же в этой статье акцент делается на более сложных конфигурациях адаптивных Split View Controller и Popover.
Впоследствие все приведенные в этой статье приложения вы сможете использовать в качестве шаблонов для разработки ваших приложений с похожими задачами.
Итак, согласно новой философии iOS, пользовательский интерфейс (UI) может достаточно быстро настраиваться на любой тип прибора в зависимости от того, какой Size Class имеет его экран.
Для классификации различных приборов в iOS 8 было введено понятие Size Class. Всего четыре Size Classes:
- Horizontal Regular
- Horizontal Compact
- Vertical Regular
- Vertical Compact
Ваш View Controller
( Split View Controller или Popover ) всегда существует в среде Size Сlass с определенной шириной (width) и высотой (height). В настоящий момент Size Class может быть либо Compact, либо Regular:
Полноразмерные View Controllers
на iPad всегда являются Regular по обоим направлениям (горизонтальному и вертикальному). На всех iPhones перед появлением iPhone 6+ и iPhone 6s+, горизонтальный размер всегда был Compact (и в портретном, и в ландшафтном режимах), а вертикальный размер был Regular в портретном режиме и Compact в ландшафтном режиме. С появлением больших iPhones 6+ и iPhones 6s+ у них, в отличие от других iPhones, ширина стала Regular в ландшафтном режиме, что и дало основание распространить применение полноценного Split View Controller на iPhones 6+ и iPhones 6s+ в ландшафтном режиме.
Будет не очень удобно, если я для своих приложений буду создавать различные интерфейсы для всех 4-х ситуаций. Поэтому в Xcode 7 у нас есть еще один Size Class, называемый Any (wAny hAny), который мы используем при проектировании универсальных приложений:
В iOS 7 нам приходилось при создании универсального приложения использовать два совершенно разных пользовательских интерфейса, построенных на разных типах Controllers (ниже представлены storyboards приложения Photomania Universal URL из предыдущего стэнфордского курса CS193P Осень 2013 — Зима 2014г. “Developing iOS 7 Apps for iPhone and iPad”):
Split View Controller на iPad
Main_iPad.storyboard
Navigation Controller на iPhone
Main_iPhone.storyboard
В iOS 8 Split View Controller становится адаптивным. Это означает, что единственный Split View Controller может управлять двумя этими архитектурами для двух типов приборов.
Для получения адаптивного интерфейса нужно выбрать файл storyboard с Split View Controller, указать в Инспекторе Файла режим Size Class и Auto Layout
и добавить новый адаптивный segue типа Show Detail:
Адаптивная storyboard очень похожа на storyboard для iPad в iOS 7, за исключением двух вещей:
- размеры всех экранных фрагментов одинаковые и соответствуют универсальному в Xcode классу wAny hAny,
- появился новый адаптивный segue типа Show Detail Segue, который помогает Split View Controller осуществлять сразу две функции: “скольжение”, как в Navigation Controller для iPhones, и «переезд» к Detail, как это принято в классическом Split View Controller для iPads.
Далее нам предстоит исследовать работу адаптивного Split View Controller на различных приборах, и в случае отклонения его работы от привычного нам функционирования вносить определенные дополнения в код. Количество этих дополнений зависит от того, какие элементы входят в Master и Detail. Мы перечислим пять интересных с точки зрения разработчика случаев, которые отличаются сложностью Master, а Detail везде один и тот же — единственный ImageViewController
, вставленный в Navigation Controller и призванный показывать изображение фотографии:
1. Классический вариант: один элемент в Master, вставленный в Navigation Controller, (часто это Table View Controller) — приложение AdaptiveSplitViewController1Swift на Github.
2. Множество Table View Controller элементов, вставленных в Navigation Controller — приложение AdaptiveSplitViewController2Swift на Github.
3. Tab Bar Controller в качестве Master — приложение AdaptiveSplitViewController3Swift на Github.
4. Случай разных UI и разных пользовательских классов для приборов с разными Size Classes здесь не рассматривается, но идею можно посмотреть в “Адаптивный интерфейс с двумя storyboards для iOS 9”.
5. Адаптивный Popover — приложение AdaptiveSplitViewController4Swift на Github.
Проще всего понять необходимость дополнительных поправок, если экспериментировать не с громоздким приложения Photomania Universal URL c “подкачкой” Flickr фотографий в фоновом режиме в Core Data, а с очень простым приложением, также работающим с фотографиями, находящимися на сервере Flickr. Информация об этих фотографиях асинхронно считывается в приложении и размещается в таблице с тем, чтобы после выбора пользователем определенной фотографии, показать ее полномасштабное изображение. Проектирование такого приложения с “чистого листа” позволит нам “убить двух зайцев”. С одной стороны, продемонстрировать синтаксические конструкции Swift. А, с другой стороны, использовать это простое экспериментальное приложение в качестве базового для более сложных конфигураций адаптивных Split View Controller и Popover.
Экспериментальное приложение
Открываем новый проект для универсального приложения на Swift, называем его AdaptiveSplitViewController1Swift и используем шаблон Single View Application (именно этот шаблон, а не Master-Detail Application, который мы будем исследовать позже).
Будем проектировать наше приложение с «чистого листа». Поэтому уберем со storyboard единственный экранный фрагмент View Controller и оставим storyboard совершенно пустой. Удалим также файл ViewController.swift.
Установим на storyboard адаптивный режим, то есть включим опции Size Class и Auto Layout:
Перетянем из палитры объектов на storyboard Table View Controller для списка Flickr фотографий и обычный View Controller для полномасштабного изображения фотографии. Последний экранный фрагмент получит URL фотографии в качестве Модели и покажет ее изображение. Поэтому он будет называться Image View Controller.
и соединим между собой Table View Controller и Image View Controller обычным Show segue c идентификатором Show Photo:
Устанавливаем Navigation Controller в качестве стартового экранного фрагмента:
В Table View Controller устанавливаем стиль прототипа динамической ячейки как Subtitle и задаем идентификатор photoCell
для повторно используемой ячейки
Для таблицы с фотографиями создаем очень простой класс FlickrPhotosTVC
, который наследует от UITableViewController
и задача которого состоит в показе списка фотографий, представленных массивом структур Photo
:
FlickrPhotosTVC.swif
Структура Photo
, которую мы разместим в файле DataModel.swift, содержит заголовок фотографии title
, более детальную информацию о фотографии subtitle
, уникальный идентификатор фотографии unique
, URL изображения imageURL
, сделавшего фотографию фотографа photographer
:
DataModel.swift
Структура struct Photo
имеет convenience
инициализатор init?(json:[String:AnyObject])
, у которого на входе JSON данныe, полученные с сервера Flickr в виде словаря [String:AnyObject]
. Это Optional
инициализатор, и именно поэтому у него стоит знак ?
вопроса в имени init?
:
DataModel.swift
Есть специальные требования формирования атрибутов Photo
. Если у фотографии нет заголовка title
, то нужно использовать детальное описание subtitle
фотографии как title
. Если у фотографии нет ни заголовка title
, ни детального описания subtitle
, нужно использовать “Unknown” как title
. Ключи FLICKR_PHOTO_TITLE, FLICKR_PHOTO_ID, FLICKR_PHOTO_DESCRIPTION
и FLICKR_PHOTO_OWNER
словаря с информацией о Flickr фотографии определены в файле FlickrFetcher.h, содержащем public API для Flickr (об этом немного позже).
Массив структур var photos = [Photo]
является Моделью для MVC FlickrPhotosTVC
и используется в методах UITableViewDataSource
:
FlickrPhotosTVC.swift
А также при “переезде” на Image View Controller в предположении, что Моделью обслуживающего его пользовательского класса ImageViewController
является свойство var imageURL: NSURL?
, задающее URL изображения фотографии. Делаем подготовку нового segue типа Show Detail в коде класса FlickrPhotosTVC
таким образом, чтобы учесть присутствие Navigation Controller, предшествующего Image View Controller, и добавить имеющуюся у Split View Controller кнопку displayModeButtonItem
на навигационную панель (но об этом позже):
FlickrPhotosTVC.swift
Константы для storyboard собраны в privater
структуре Storyboard
:
FlickrPhotosTVC.swift
Cогласно концепции объектно-ориентированного программирования сделаем класс FlickrPhotosTVC
более обощенным (generic), то есть он будет формировать таблицу фотографий, представленных массивом [Photo]
и при выборе определенной строки в таблице показывать изображение соответствующей фотографии с помощью MVC ImageViewController
. Но он не будет заботится о том, как получен массив [Photo]
: c сервера Flickr или из хранилища NSUserDefaults
.
Поэтому для показа фотографий c сервера Flickr мы создаем новый класс JustPostedFlickrPhotosTVC
, который является subclass класса FlickrPhotosTVC
:
JustPostedFlickrPhotosTVC.swift
Данные c сервера Flickr будут загружаться в классе JustPostedFlickrPhotosTVC
c использованием public API для Flickr, который был предоставлен стэнфордским сайтом в виде папки Flickr Fetcher. Он позволяет получить URLs для различных запросов к Flickr, код его написан на Objective-C. Наиболее простой способ включить Objective-C код в Swift проект это копировать по одному файлу из папки Flickr Fetcher (их там всего три) в ваш Swift проект, тогда перед тем, как скопировать файл FlickrFetcher.m вас спросят, хотите ли вы добавить Objective-C файл заголовка для связи со Swift?:
Вы отвечаете “Создать связующий файл заголовка”. В результате сформируется пустой файл заголовка AdaptiveSplitViewController1Swift-Bridging-Header.h для вашего проекта, в который вы добавляете необходимые файлы заголовков public API для Flickr:
Все. Вам больше ничего не надо делать, теперь вы можете обращаться к классам и константам public API для Flickr напрямую. Вам останется только получить ключ Flickr API. Бесплатный Flickr.com account вполне подойдет, так как вы не будете размещать на Flickr фотографии, а только запрашивать их.
Вновь полученный файл заголовка автоматически пропишется в настройках проекта:
Вернемся к классу JustPostedFlickrPhotosTVC
, который специально предназначен для считывания наиболее “свежих” фотографии с Flickr, и используем public API для формированию URL запроса о “недавних” фотографиях. Считывание данных по этому URL происходит асинхронно и полученная информация преобразуется в массив [Photo]
:
JustPostedFlickrPhotosTVC.swift
Преобразование JSON данных в массив структур [Photo]
осуществляется одной строкой благодаря функции flatMap
, выводу типа из контекста и Optional
инициализатору Photo
.
Полученный в результате парсинга массив self.photos
“подхватывается” superclass FlickrPhotosTVC
и мы видим таблицу фотографий при условии, что выставили на storyboard пользовательский класс для таблицы Flickr Photos как JustPostedFlickrPhotosTVC
:
Для того, чтобы показать пользователю, что идет загрузка данных из сети, которая требует некоторого времени, включим на storyboard Refresh Control для экранного фрагмента Flickr Photos:
Перед методом func fetchPhotos ()
загрузки данных с сервера Flickr поставим @IBAction
— то есть покажем, что это Action:
JustPostedFlickrPhotosTVC.swift
C помощью CTRL-перетягивания привяжем Refresh Control к @IBAction
func fetchPhotos ()
:
Для получения полномасштабного изображения фотографии используется класс ImageViewController
, Моделью которого является var imageURL: NSURL?
:
ImageViewController.swift
Подробно проектирование этого класса описано в Лекции 9 CS193P Winter 2015 — Scroll View и Многопоточность (Multithreading) курса «Developing iOS 8 Apps with Swift» стэнфордского университета CS193P зима 2015 года.
Устанавливаем этот класс оставшемуся на storyboard Image View Controller:
Запускаем приложение, ждем, когда закончится загрузка c Flickr, выбираем фотографию, получаем ее изображение.
Все функционирует правильно и хорошо выглядит на Compact-width приборах (все iPhones, кроме iPhone 6+ и iPhone 6s+, для которых этот режим справедлив только в портретном виде), но на Regular-width приборах (все iPad и iPhone 6+ и iPhone 6s+ в ландшафтном режиме) следует использовать Split View Controller.
Код находится на Github — приложение AdaptiveSplitViewController1Swift.
На этом мы закончим формирование нашего экспериментальное приложения и приступим к реализации адаптивного Split View Controller в классическом варианте.
1. Классический вариант адаптивного Split View Controller
Для этого создадим еще одну storyboard в этом приложении с помощью меню File -> New -> File -> User Interface:
Назовем ее условно iPad.storyboard (на самом деле имя не имеет никакого значения, так как впоследствии мы переименуем iPad.storyboard в Main.storyboard):
В результате получим пустую iPad.storyboard. Для того, чтобы приложение работало с iPad.storyboard добавим код в AppDelegate.swift:
Вытяним из Палитры Объектов и разместим на storyboard Split View Controller, который появляется вместе с двумя сопровождающими View Controllers, которые мы тут же удалим, а вместо Master копируем с Main.storyboard Flickr Photos
, вставленный в Navigation Controller, и вместо Detail — Image View Controller
, также вставленный в Navigation Controller.
Используем новый адаптивный segue типа Show Detail с идентификатором Show Photo.
В скобках на картинке в Инспекторе Атрибутов дается уточнение, что segue типа Show Detail — это Replace segue, а это означает, что Detail этого Split View Controller будет замещаться новым экземпляром MVC. Это очень важно для дальнейшего понимания. Как и всякий другой segue, segue типа Show Detail нуждается в подготовке, особенно в случае, если destination
( тот View Controller, куда мы “переезжаем” благодаря этому segue) — это UINavigationController
:
FlickrPhotosTVC.swift
Следует обратить внимание, что при использовании segue происходит полная замена Detail новым MVC, а prepareForSegue
работает еще до загрузки ImageViewController
и установка нового значения Модели (то есть imageURL
) может не обновить до конца пользовательский интерфейс Image View Controller, так как некоторые outlets еще не выставлены и мы еще не находимся полностью на экране. Поэтому класс ImageViewController
спроектирован таким образом, что при установке Модели — imageURL
, выборка данных об изображении фотографии с сервера Flickr и обновление пользовательского интерфейса происходит только, если ImageViewController
уже находится на экране:
ImageViewController.swift
В нашем случае, когда Detail будет полностью замещается новым экземпляром MVC, выборку данных из сети и обновление UI реализуем в методе “жизненного цикла” viewWillAppear
, то есть непосредственно перед появлением изображения image
на экране:
ImageViewController.swift
Запускаем приложение на iPhone 6+ и на iPad в портретном режиме:
На iPhone 6+ и iPad 2 появляется пустой экран — это Detail (в нашем случае ImageViewController
) для SplitViewController, и это было обычное поведение SplitViewController ранее, когда он проектировался только для iPad. Но если на iPhone 6+ есть возвратная кнопка к Master (в нашем случае это таблица со списком Flickr фотографий), то на iPad даже непонятно, что делать. Пользователь должен каким-то магическим способом догадаться, что работает жест swipe
, который покажет нам Master, то есть список Flickr фотографий.
Дальше можно выбирать фотографию и экран с фотографией будет автоматически обновляться. Все работает, но нет возвратной кнопки в Master.
Запускаем приложение на iPad в портретном режиме.
Запускаем приложение на iPad в ландшафтном режиме.
Все работает.
Запускаем приложение на iPhone 6+ в портретном режиме
При запуске в портретном режиме есть возвратная кнопка, призывающая нас выбрать Flickr фотографию. Нажимаем на эту кнопку, и действительно попадаем в Master, то есть экранный фрагмент со списком фотографий. Дальше можно выбирать фотографию и получать ее изображение. Этот режим работает.
Переходим в ландшафтный режим на iPhone 6+
Все работает. Фотографии обновляются в Detail.
Какой можно сделать вывод из этих экспериментов?
Если мы имеем дело с Regular-width приборами (iPad в портретном и ландшафтном режимах и iPhone 6+, iPhone 6s+ в ландшафтном режиме), то на экране одновременно находятся и Master, и Detail. Этот режим для адаптивного Split View Controller назван expanded
. Это известный нам ранее привычный режим обычного Split View Controller.
Если мы имеем дело с Compact-width приборами ( iPhone 6+, iPhone 6s+ в портретном режиме, все другие iPhones в портретном и ландшафтном режимах), то на экране находится только один MVC: либо Master, либо Detail, поэтому этот режим назван collapsed
для адаптивного Split View Controller. Это режим, когда адаптивного Split View Controller работает как Navigation Controller, в стэке которого находятся и Master, и Detail.
У нас одна storyboard, которая работает на обоих платформах (iPhone и iPad) и автоматически адаптируется.
Но нас не устраивают две вещи:
- Мы хотим, чтобы при старте на любом iPad в портретном режиме появлялась такая же возвратная кнопка, как и при старте на любом iPhone в портретном режиме;
- При старте на любом iPhone в портретном режиме появлялся бы не Detail с пустым экраном (как это было принято для iPad), а Master (как это было принято для iPhone).
Для того, чтобы при старте на любом iPad в портретном режиме появлялась возвратная кнопка необходимо разместить ее явно на навигационной панели в AppDelegate.swift:
и в методе prepareForSegue
в классе FlickrPhotos
:
FlickrPhotos.swift
Кроме этого дать заголовок Navigation Controller для Master, который в нашем случае будет “Flickr Photos”:
В результате мы имеем необходимую возвратную кнопку для iPad в портретном режиме и кнопку переключения режимов для iPhone 6+, iPhone 6s+ в ландшафтном режиме.
Вот как работает кнопка переключения режимов на iPhone 6+, iPhone 6s+ в ландшафтном режиме:
Теперь работу адаптивного Split View Controller для Regular-width приборов можно считать удовлетворительной.
Что нас не устраивает в работе адаптивного Split View Controller для Compact-width приборов?
На iPhones работа должна сразу начинаться с показа Master и дальше двигаться последовательно в сторону Detail c помощью стэкового механизма Navigation Controller. Это обеспечивается методами делегата UISplitViewControllerDelegate
, один из которых мы сейчас реализуем в AppDelegate
.
Вначале мы подтверждаем протокол UISplitViewControllerDelegate
:
AppDelegate.swift
И, наконец, реализуем метод collapseSecondaryViewController, ontoPrimaryViewController
делегата UISplitViewControllerDelegate
, который срабатывает при переходе в collapsed
режим, когда на экране должен остаться только один View Controller. Он спрашивает нас, нужно ли отбросить Detail? Если мы отвечаем true
, то на экране в collapsed
режиме остается только Master, если false
— то Detail. Мы хотим иметь Master только при старте, то есть когда Detail — это Navigation Controller, его топовым View Controller в стэке является ImageViewController , Модель которого imageURL имеет значение равное nil. Именно это условие мы выделяем прежде, чем возвратить true:
AppDelegate.swift
Стартуем iPhone 6+ в портретном режиме:
Теперь наш адаптивный интерфейс работает как нужно, то есть для Compact-width приборов мы стартуем со списка Flickr фотографий.
Итак, настройка адаптивного Split View Controller для классического случая завершена.
Переименуем storyboard Main.storyboard в iPhone.storyboard и оставим ее для истории в этом приложении, а iPad.storyboard в Main.storyboard и сделаем ее основной для универсального приложения:
, а также избавимся от лишнего кода в AppDelegate
:
Окончательный вариант простейшего приложения AdaptiveSplitViewController1Swift находится на Github.
Подведем итог нашим действиям по созданию адаптивного Split View Controller:
Шаг 1. Перетаскивая Split View Controller из Палитры объектов.
Шаг 2. Вставляем Master и Detail в Navigation Controller и подсоединяем к Split View Controller.
Шаг 3. Добавляем segue типа Show Detail
Шаг 4. Настраиваем для segue метод prepareForSegue
с учетом того, что destinationViewController
для этого segue может быть как ImageViewController
, так и UINavigationController
, в стэке которого на самом верху находится ImageViewController
.
Шаг 5. Добавляем кнопки на навигационную панель в методе prepareForSegue
подготовки segue и в AppDelegate
.
Шаг 6. Реализуем в AppDelegate
метод collapseSecondaryViewController:ontoPrimaryViewController:
делегата UISplitViewControllerDelegate
, который срабатывает при переходе в collapsed
режим, когда на экране должен остаться только один View Controller; в этом случае мы отбрасываем Detail при старте.
Можно обойтись без этих 6 шагов и получить точно такой же код и UI сразу же, если воспользоваться прекрасным Master-Detail шаблоном приложения, в котором уже есть и Show Detail segue, и Navigation Controller, и весь необходимый код.
Мы можем еще немного улучшить работу Split View Controller для iPad, задав преимущественный режим показа .AllVisible
:
AppDelegate.swift
Что означает preferredDisplayMode?
Это свойство определяет предпочтительный режим показа. Split View Controller делает все возможное, чтобы настроить интерфейс соответствующим образом, но может использовать и другой режим показа, если, например, будет недостаточно места для показа в заданном вами предпочтительном режиме. По умолчанию значение свойства preferredDisplayMode
равно .Automatic
. На iPad это приводит к использованию режима .PrimaryOverlay
в портретной ориентации и .AllVisible
в ландшафтной ориентации. Задавая предпочтительный режим показа .AllVisible
, мы, фактически, влияем только на показ Split View Controller на iPad в портретном режиме
В AppDelegate.swift приведены еще две закомментированные строки кода
// splitViewController.preferredPrimaryColumnWidthFraction = 0.5
// splitViewController.maximumPrimaryColumnWidth = 512
Если вы снимите комментарии с этих строк, то сможете регулировать ширину столбцов для Master и Detail. Вы можете использовать свойство preferredPrimaryColumnWidthFraction
, которое принимает значение от 0.0 до 1.0, чтобы представить долю от общей ширины экрана, которую занимает Master. По умолчанию это свойство принимает значение UISplitViewControllerAutomaticDimension
, которое приводит к подходящей ширине Master, выбираемой Split View Controller.
Действительная ширина Master ограничивается значениями в диапазоне minimumPrimaryColumnWidth
и maximumPrimaryColumnWidth
. Split View Controller будет делает все возможное, чтобы настроить интерфейс соответственно заданным вами значениям свойств, но может поменять их на другие в зависимости от располагаемого пространства. Вы можете получить действительную ширину Master с помощью свойства primaryColumnWidth
.
Код находится на Github — приложение AdaptiveSplitViewController1Swift.
2. Множество Table View Controllers в Master
Но пойдем дальше и добавим еще один экранный фрагмент Table View Controller для фотографов, которые сделали Flickr фотографии.
Для этого на основе предыдущего приложения AdaptiveSplitViewController1Swift создадим новое приложение AdaptiveSplitViewController2Swift и включим в Master еще одну Table View Controller. Теперь наш пользовательский интерфейс выглядит следующим образом:
Вначале мы выбираем фотографа, потом любую фотографию этого фотографа из списка его фотографий, а затем показываем выбранную фотографию:
Экранный фрагмент Table View Controller для фотографов обслуживает достаточно простой класс FlickrPhotographersTVC
. Это практически копия класса FlickrPhotoTVС
, там тоже есть свойство var photos = [Photo]()
, представляющее собой список фотографий, но его установка приводит к вычислению другого свойства var photographers = [Photographer]()
, которое представляет собой список фотографов, сделавших эти фотографии, и является Моделью для класса FlickrPhotographersTVC
:
FlickrPhotographersTVС.swift
Фотография Photo
и фотограф Photographer
задаются структурами и находятся в файле DataModel.swift:
На основе Модели var photographers = [Photographer]()
реализованы методы UITableViewDataSource
:
FlickrPhotographersTVС.swift
Для подготовки “переезда” на таблицу со списком фотографий Flickr Photos используется метод prepareForSegue
, который также как и в классе FlickrPhotos
учитывает, что destinationViewController
, на который мы “переезжаем”, может быть вставлен в Navigation Controller:
Выбрав фотографа photographer
, мы задаем для Модели destinationViewController
, только фотографии photos
, сделанные этим фотографом.
Константы для storyboard собраны в private
структуре Storyboard
:
FlickrPhotographersTVС.swift
Точно также, как и в “классическом варианте” будем считать, что класс FlickrPhotographersTVC
согласно концепции объектно-ориентированного программирования имеет более общий характер. Он не заботится о том, как получен массив [Photo]
: c сервера Flickr или из NSUserDefaults
. Для получения массива [Photo]
c сервера Flickr у нас уже есть класс JustPostedFlickrPhotosTVC
, который сделаем теперь subclass не FlickrPhotosTVC
как в “классическом” варианте”, а subclass FlickrPhotographersTVC
:
JustPostedFlickrPhotosTVС.swift
Именно класс JustPostedFlickrPhotosTVC
будет пользовательским классом для экранного фрагмента Flickr Photographers:
а для экранного фрагмента Flickr Photos пользовательским классом будет FlickrPhotosTVC
:
Запускаем приложение, смотрим как оно работает. Все работает прекрасно, за исключением одной ситуации, когда iPhone 6+ (или iPhone 6s+) переходит из портретного режима в ландшафтный (то есть из collapsed
режима в expanded
согласно терминологии адаптивного Split View Controller). При этом его экран в портретном режиме показывает Список Фотографий на месте Detail, а вовсе не изображение выбранной фотографии:
При переходе из collapsed
режима, когда на экране находится только один View Controller, в режим expanded
(Master и Detail одновременно на экране), адаптивный Split View Controller берет текущий экран в качестве Detail по умолчанию. А это совсем не то, что нам нужно: на месте Detail может быть только ImageViewController
. С помощью другого метода separateSecondaryViewControllerFromPrimaryViewController
делегата UISplitViewControllerDelegate
нам самим придется произвести нужную настройку:
AppDelegate.swift
Метод separateSecondaryViewControllerFromPrimaryViewController
срабатывает при переходе из collapsed
режима в expanded
и он нас спрашивает, какой View Controller следует взять в качестве Detail. По умолчанию в качестве Detail берется текущий View Controller, если это не Master. В нашем особом случае, когда primaryViewController
— таблица со списком фотографий FlickrPhotosTVC
, вставленная в Navigation Controller, мы должны сгенерировать Detail в условиях скудной информации. Нам неоткуда взять ImageViewController
(изображение фотографии) как только со storyboard и нам необходим идентификатор экранного фрагмента Detail на storyboard (в нашем случае это Navigation Controller для Detail). Пусть этим идентификатором будет “detailNavigation”:
После восстановления Detail со storyboard, мы можем получить наш ImageViewController
(в коде это controller
) и сделать необходимые настройки: добавить кнопки на навигационную панель, выставить заголовок ImageViewController
и настроить его Модель ( в нашем случае это imageURL
), которая будет соответствовать первой строке в списке фотографий:
if let photo = photosView.photos.first {
controller.imageURL = NSURL(string: photo.imageURL)
controller.title = photo.title
}
Запускаем приложение, теперь все работает правильно. Кроме того, выделяется первая строка в списке фотографий, для которой показывается фотография:
Вывод: для того, чтобы обеспечить адаптивную работу Split View Controller c многочисленные (>1) Table View Controllers в качестве Master, нужно использовать метод separateSecondaryViewControllerFromPrimaryViewController
делегата UISplitViewControllerDelegate
.
Код находится на Github — приложение AdaptiveSplitViewController2Swift.
Во второй части этого поста мы будем дальше усложнять наше экспериментальное приложение и распространим его на случай 3 — Tab Bar Controller в качестве Master для Split View Controller, и 5 — адаптивный Popover.
Код для всех вариантов можно найти на Github.
Можно скачать текст поста в PDF формате