Этот пост относится к ранним временам современной iOS. Он обновляется с учетом текущей информации и текущего синтаксиса Swift.
Сегодня в iOS все представляет собой контейнер. Это основной способ создания приложений сегодня.
Приложение может быть настолько простым, что у него будет только один экран. Но даже в этом случае каждая вещь на экране представляет собой контейнерное представление.
Это так просто ...
примечания к версии
2020. В наши дни вы обычно просто загружаете представление контейнера из отдельной раскадровки, что очень просто. Это объясняется в нижней части этого поста. Если вы новичок в представлениях контейнеров, возможно, сначала ознакомьтесь с руководством по контейнеру «классический стиль» («та же раскадровка»).
2021 г. Обновленный синтаксис. Использовал новые красивые заголовки SO "###". Подробнее о загрузке из кода.
(A) Перетащите контейнерный вид в свою сцену ...
Перетащите представление контейнера в представление сцены. (Так же, как вы перетаскиваете любой элемент, например UIButton.)
Вид контейнера на этом изображении выделен коричневым цветом. На самом деле это внутри вашего вида.
Когда вы перетаскиваете представление контейнера в представление сцены, Xcode автоматически дает вам две вещи:
Вы получаете представление контейнера внутри представления сцены, и,
вы получаете совершенно новый UIViewController
, который просто сидит где-то на белом фоне вашей раскадровки.
Эти две вещи связаны с Масонским Символом - объяснено ниже!
(B) Щелкните этот новый контроллер представления. (Итак, это новая вещь, которую Xcode сделал для вас где-то в белой области, не то, что внутри вашей сцены.) ... и измените класс!
Это действительно так просто.
Готово.
Вот то же самое, объясненное визуально.
Обратите внимание на представление контейнера на (A)
.
Обратите внимание на контроллер на (B)
.
Нажмите на B. (Это B, а не A!)
Подойдите к инспектору вверху справа. Обратите внимание, что там написано UIViewController
Измените его на свой собственный класс, которым является UIViewController.
Итак, у меня есть класс Swift Snap
, который является UIViewController
.
Итак, где написано UIViewController в инспекторе, я ввел в Snap.
(Как обычно, Xcode автоматически завершит Snap, когда вы начнете вводить Snap ....)
Вот и все - готово.
Как изменить вид контейнера - скажем, на вид таблицы.
Поэтому, когда вы нажимаете, чтобы добавить представление контейнера, Apple автоматически предоставляет вам связанный контроллер представления, расположенный на раскадровке.
В настоящее время (2019 г.) по умолчанию используется UIViewController
.
Это глупо: он должен спросить, какой тип вам нужен. Например, часто требуется табличный вид.
Вот как изменить его на другое:
На момент написания Xcode по умолчанию дает UIViewController
. Допустим, вы хотите вместо этого UICollectionViewController
:
(i) Перетащите контейнерный вид в свою сцену. Посмотрите на UIViewController на раскадровке, которую Xcode предоставляет вам по умолчанию.
(ii) Перетащите новый UICollectionViewController
в любое место в основной белой области раскадровки.
(iii) Щелкните вид контейнера внутри вашей сцены. Щелкните инспектор соединений. Обратите внимание, что есть один запускаемый переход. Наведите курсор на инициированный переход и обратите внимание, что Xcode выделяет все нежелательные UIViewController.
(iv) Щелкните x, чтобы на самом деле удалить вызвавший переход.
(v) DRAG из этого инициированного перехода (единственный вариант - viewDidLoad). Перетащите через раскадровку в свой новый UICollectionViewController. Отпустите, и появится всплывающее окно. Вы должны выбрать встроить.
(vi) Просто удалите все ненужные UIViewController. Готово.
Укороченная версия:
удалите ненужный UIViewController.
Поместите новый UICollectionViewController
в любом месте раскадровки.
Удерживая нажатой клавишу Control, перетащите из окна контейнера Connections - Trigger Segue - viewDidLoad на ваш новый контроллер.
Обязательно выберите "Вставить" во всплывающем окне.
Это так просто.
Ввод текстового идентификатора ...
У вас будет один из этих квадратов в квадрате масонских символов: он находится на изогнутой линии, соединяющей ваше контейнерное представление с контроллером представления.
Масонский символ - это segue.
Выберите переход, нажав на масонский символ.
Посмотрите направо.
Вы ДОЛЖНЫ ввести текстовый идентификатор перехода.
Вы выбираете имя. Это может быть любая текстовая строка. Часто хорошим выбором является segueClassName.
Если вы будете следовать этому шаблону, все ваши сегменты будут называться segueClockView, seguePersonSelector, segueSnap, segueCards и так далее.
Далее, где вы используете этот текстовый идентификатор?
Как подключиться к дочернему контроллеру ...
Затем сделайте следующее в коде во ViewController всей сцены.
Допустим, у вас есть три представления контейнера в сцене. Каждое представление контейнера содержит отдельный контроллер, например Snap, Clock и прочее.
Последний синтаксис
var snap:Snap?
var clock:Clock?
var other:Other?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueSnap")
{ snap = (segue.destination as! Snap) }
if (segue.identifier == "segueClock")
{ clock = (segue.destination as! Clock) }
if (segue.identifier == "segueOther")
{ other = (segue.destination as! Other) }
}
Это так просто. Вы подключаете переменную для ссылки на контроллеры, используя вызов prepareForSegue
.
Как подключиться в "обратном направлении", вплоть до родителя ...
Скажем, вы находитесь в контроллере, который вы поместили в представление контейнера (в примере - Snap).
Добраться до контроллера вида босса над вами (Dash в примере) может быть непросто. К счастью, это просто:
// Dash is the overall scene.
// Here we are in Snap. Snap is one of the container views inside Dash.
class Snap {
var myBoss:Dash?
override func viewDidAppear(_ animated: Bool) { // MUST be viewDidAppear
super.viewDidAppear(animated)
myBoss = parent as? Dash
}
Критично: работает только с viewDidAppear
и новее. Не будет работать в viewDidLoad
.
Готово.
Важно: это только работает для контейнерных представлений.
Совет, не забывайте, что это работает только для контейнерных представлений.
В наши дни с идентификаторами раскадровки обычным делом просто выводить новые представления на экране (скорее, как при разработке Android). Итак, допустим, пользователь хочет что-то отредактировать ...
// let's just pop a view on the screen.
// this has nothing to do with container views
//
let e = ...instantiateViewController(withIdentifier: "Edit") as! Edit
e.modalPresentationStyle = .overCurrentContext
self.present(e, animated: false, completion: nil)
При использовании представления контейнера ГАРАНТИРУЕТСЯ, что Dash будет родительским контроллером представления Snap.
Однако это НЕ ОБЯЗАТЕЛЬНО при использовании instantiateViewController.
Очень сбивает с толку то, что в iOS родительский контроллер представления не связан с классом, который его создал. (Он может быть таким же, но обычно это не то же самое.) Шаблон self.parent
предназначен только для представлений контейнера.
(Для аналогичного результата в шаблоне instantiateViewController вы должны использовать протокол и делегат, помня, что делегат будет слабым звеном.)
Обратите внимание, что в наши дни довольно легко динамически загружать представление контейнера из другой раскадровки - см. Последний раздел ниже. Часто это лучший способ.
prepareForSegue плохо назван ...
Стоит отметить, что prepareForSegue - это действительно плохая репутация!
prepareForSegue используется для двух целей: загрузки представлений контейнера и перехода между сценами.
Но на практике вы очень редко переключаетесь между сценами! В то время как почти каждое приложение, само собой разумеется, имеет много-много контейнерных представлений.
Было бы лучше, если бы prepareForSegue назывался чем-то вроде loadContainerView.
Больше, чем один...
Типичная ситуация: у вас есть небольшая область на экране, где вы хотите отобразить один из нескольких различных контроллеров представления. Например, один из четырех виджетов.
Самый простой способ сделать это: просто разместите четыре разных представления контейнера внутри одной и той же области. В своем коде просто скройте все четыре и включите тот, который вы хотите видеть.
Легкий.
Представления контейнера из кода ...
... динамически загружать раскадровку в представление контейнера.
2019+ Синтаксис
Допустим, у вас есть файл раскадровки Map.storyboard, идентификатор раскадровки - MapID, а раскадровка - это контроллер представления для вашего класса Map
.
let map = UIStoryboard(name: "Map", bundle: nil)
.instantiateViewController(withIdentifier: "MapID")
as! Map
Сделайте обычный UIView в своей основной сцене:
@IBOutlet var dynamicContainerView: UIView!
Apple объясняет здесь четыре вещи, которые необходимо сделать, чтобы добавить динамическое представление контейнера
addChild(map)
map.view.frame = dynamicContainerView.bounds
dynamicContainerView.addSubview(map.view)
map.didMove(toParent: self)
(В этой последовательности.)
И чтобы удалить это представление контейнера:
map.willMove(toParent: nil)
map.view.removeFromSuperview()
map.removeFromParent()
(Также в таком порядке.) Вот и все.
Однако обратите внимание, что в этом примере dynamicContainerView
- это просто фиксированный вид. Он не меняется и не масштабируется. Это будет работать только в том случае, если ваше приложение никогда не вращается или что-то еще. Обычно вам нужно добавить четыре обычных ограничения, чтобы просто сохранить map.view внутри dynamicContainerView при изменении его размера. Фактически, это самое удобное в мире расширение, которое нужно в любом приложении для iOS.
extension UIView {
// it's basically impossible to make an iOS app without this!
func bindEdgesToSuperview() {
guard let s = superview else {
preconditionFailure("`superview` nil in bindEdgesToSuperview")
}
translatesAutoresizingMaskIntoConstraints = false
leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
topAnchor.constraint(equalTo: s.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: s.bottomAnchor).isActive = true
}
}
Таким образом, в любом реальном приложении приведенный выше код будет выглядеть следующим образом:
addChild(map)
dynamicContainerView.addSubview(map.view)
map.view.bindEdgesToSuperview()
map.didMove(toParent: self)
(Некоторые даже делают расширение .addSubviewAndBindEdgesToSuperview()
, чтобы избежать там строчки кода!)
Напоминание о том, что заказ должен быть
- добавить ребенка
- добавить реальный вид
- назовите didMove
Удаление одного из них?
Вы добавили map
в держатель динамически, теперь вы хотите его удалить. Правильный и единственный порядок:
map.willMove(toParent: nil)
map.view.removeFromSuperview()
map.removeFromParent()
Часто у вас будет представление о держателе, и вы захотите поменять местами разные контроллеры. Так:
var current: UIViewController? = nil
private func _install(_ newOne: UIViewController) {
if let c = current {
c.willMove(toParent: nil)
c.view.removeFromSuperview()
c.removeFromParent()
}
current = newOne
addChild(current!)
holder.addSubview(current!.view)
current!.view.bindEdgesToSuperview()
current!.didMove(toParent: self)
}
person
Fattie
schedule
01.05.2014
nil
. В частности, self.aboutVC, self.utilityView и self.aboutVC.aboutView. - person Abhi Beckert   schedule 01.05.2014