- PVSM.RU - https://www.pvsm.ru -
Раньше для поддержки iPad делали отдельный xib. Чтобы унифицировать лейаут, в 2014 году Apple представила Auto Layout и Size Classes, а для адаптивной навигации UISplitViewController [1].

Split-контроллер — это контейнер, который разместит два контроллера рядом. Слева будет навигационный контроллер (речь не про Navigation Controller), справа соответсвующий выбору в навигационном. Короче, как в Настройках.
Разберем как настроить UISplitViewController и его поведение на экранах.
Обозначим левый (навигационный) и правый контроллеры. Для обоих установим заголовок:
class MasterController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Master"
self.navigationController?.navigationBar.prefersLargeTitles = true
}
}
class DetailController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Detail"
self.navigationController?.navigationBar.prefersLargeTitles = true
}
}
Split-контроллер это контейнер для двух контроллеров. Как я писал до ката, слева навигационный-главный контроллер, справа соответствующий выбору в навигационном, или детальный (Detail Controller). Master и Detail запоминайте, слова будут встречаться в документации и протоколах.
Перейдем в AppDelegate, в методе didFinishLaunchingWithOptions создаем новое окно, инициализируем Split-контроллер и два других, устанавливаем в Split:
let masterController = MasterController()
let masterNavigationController = UINavigationController(rootViewController: masterController)
let detailController = DetailController()
let detailNavigationController = UINavigationController(rootViewController: detailController)
let splitViewController = UISplitViewController()
splitViewController.viewControllers = [masterNavigationController, detailNavigationController]
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window!.rootViewController = splitViewController
self.window!.makeKeyAndVisible()
Запустим симулятор. Портретная и альбомная ориентации соответсвенно:

В портертной ориентации не видно Master-контроллера. Смахните у левого края, чтобы он появился. Настраивается с помощью режимов, их разберем дальше.
Добавим в Master-контроллер ячейки. Напомню, Master-контроллер это таблица, обернутая в UINavigationController.
class MasterController: UITableViewController {
override func viewDidLoad() {
self.tableView = UITableView(frame: .zero, style: .insetGrouped)
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "id")
self.navigationItem.title = "Master"
self.navigationController?.navigationBar.prefersLargeTitles = true
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id")!
cell.textLabel?.text = "(indexPath)"
return cell
}
}
В iOS 13 появился новый стиль таблицы .insetGrouped, я установил его. Стиль доступен начиная с Xcode 11.
Запустим проект:

По нажатию на ячейку покажем детальный контроллер. Как же режут слух «детальные контроллеры», надеюсь в коментах подскажите перевод лучше. Код выглядит так:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let controller = DetailController()
controller.navigationTitle = "(indexPath)"
self.showDetailViewController(UINavigationController(rootViewController: controller), sender: nil)
}
Помните про нейминг мастер / детальный контроллеры? Вот пример использования Detail в методе showDetailViewController.
Внимательные заметят, что проперти navigationTitle у контроллера нет. Обновим класс Detail-контроллера:
class DetailController: UIViewController {
var navigationTitle: String = "Detail"
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = navigationTitle
self.view.backgroundColor = .white
}
}
Запустите проект и нажмите на ячейку:

Навигация адаптивная, а значит всё готово. Ну почти. Выберите айфон и запустите:

Работает как обычный Navigation-контроллер. Это универсальность адаптивность — в зависимости от свободного пространства Split-контроллер размещает главный и детальный контроллеры.
На айфоне первым открылся не Master-контроллер, а Detail. Переход от отображения двух контроллеров к одному похожая ситуация, настраивается делегатом UISplitViewControllerDelegate. Возвращаемое значение определяет показывать Master-контроллер, или Detail:
extension AppDelegate: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
return true
}
}
Этот метод не всегда должен возвращать true. Пример: при смене ориентации в компактную и уже открытом Detail контроллере, может потребоваться оставить Detail на экране. Планируйте это поведение.

Альбомная ориентация для айфонов работает без Split по умолчанию. Это исправляется режимом отображения для Split-контроллера:
splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
Задаем предпочтительный режим отображения, а именно показывать пару Master-Detail всегда, где это возможно. Возможность определяет API, настроить нельзя.
В портретной ориентации останется так же. А вот в альбомной (только для Xs Max и 8+):

Если в SDK изменятся условия для Split-контроллера, ваш проект по умолчанию реализует их.
Можно настроить. Делается это отношением сторон:
splitViewController.preferredPrimaryColumnWidthFraction = 0.5
splitViewController.maximumPrimaryColumnWidth = 2000
Master и Detail будут одинаковых размеров. Обязательно установите maximumPrimaryColumnWidth. Опционально можно установить минимальную ширину. Скриншот добавлять не буду, и так много айпадов на туториал)
Добавляем кнопку, которая открывает Detail на весь экран. Split-контроллер должен быть в режиме .allVisible. Вставьте код для Detail-контроллера в viewDidLoad:
if let splitController = self.splitViewController{
if let navController = splitController.viewControllers.last as? UINavigationController {
navController.topViewController?.navigationItem.leftBarButtonItem = splitController.displayModeButtonItem
}
}
Описать поведение сложно, а гифка получается размером с бюджет Москвы. Поэтому скриншот:

Или у меня в твиттере [2] видео.
AutoLayout размещает элементы, Split-контроллер определяет навигацию. Эпл настоятельно рекомендует использовать статический Master-контроллер. Я ради эксперимента сделал слева навигационный контроллер, по нажатию на ячейку пушил контроллеры (вместо обновления Detail-контроллера). Выглядит странно.
In general, restrict navigation to one side of a split view. Placing navigation in both panes of a split view makes it hard for people to stay oriented and discern the relationship between the two panes.
Выделяйте активный выбор в Master-контроллере. Хотя содержимое Detail-контроллера может изменяться, оно всегда должно соответствовать выделению на Master. Это поможет людям контролировать отношения между контроллерами. Контролировать контроллеры — каламбур какой-то.
Не нужно использовать Split везде. Но если в вашем приложении сильная навигация, а root-контроллер это Tab или Navigation, скорее всего Split полезен. Если у вас одноэкранное приложение-переводчик, Split вам не нужен.
Ссылка на документацию [1], ссылка на гайдлайны [3].
Если вам удобнее смотреть видео, гляньте туториал:
Автор: IvanVorobei
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios/325074
Ссылки в тексте:
[1] UISplitViewController: https://developer.apple.com/documentation/uikit/uisplitviewcontroller
[2] в твиттере: https://twitter.com/varabeis/status/1154380460342075392
[3] на гайдлайны: https://developer.apple.com/design/human-interface-guidelines/ios/views/split-views/
[4] Источник: https://habr.com/ru/post/460965/?utm_campaign=460965&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.