Pushing, Popping, Presenting, & Dismissing ViewControllers
I recently found myself in a situation where I had presented a UINavigationController modally and then pushed UIViewControllers within the UINavigationController. On the last UIViewController, I had two scenarios that I wanted to handle: 1) navigate back to the first UIViewController within the UINavigationController’s stack and 2) dismiss the UINavigationController, including all the pushed UIViewControllers contained in the UINavigationController stack.
After some initial trial & error, I decided to spend some time understanding the different ways in which both UINavigationControllers and UIViewControllers handle navigation.
First lesson learned:
Any UIViewController (including UINavigationControllers as UINavigationControllers are a type of UIViewControllers) presented modally must be dismissed.
Second lesson learned:
Since UINavigationControllers hold UIViewControllers in their stack, they can present UIViewControllers both modally and via a push. When navigating away from any pushed UIViewControllers, UINavigationControllers must pop the UIViewController.
Third lesson learned:
Though it is possible to present UIViewControllers both modally and via a push, UIViewControllers can only present other UIViewControllers. Therefore, UIViewControllers can only be dismissed (unless they are inside a UINavigationController stack and the UINavigationController is handling the pop — see lesson #2). This is why it is not possible to call popViewController on a UIViewController.
Fourth lesson learned:
PresentingViewController is a property provided by Apple that is available on both UIViewControllers and UINavigationControllers. Calling presentingViewController.dismiss(animated:completion) will dismiss everything presented by the parentViewController. The parentViewController is the UIViewController that presented the current UIViewController. So, if you have a UINavigationController with 3 UIViewControllers within its stack, calling presentingViewController.dismiss(animated:completion) will dismiss both the UINavigationController and the 3 UIViewControllers held within its stack.
Fifth lesson learned:
Similarly, Apple also provides another property on UIViewControllers and UINavigationControllers — presentedViewController. Calling presentedViewController.dismiss(animated:completion) will dismiss the childViewController. The childViewController is the current UIViewController that is being presented.
Prior to this recent investigation, I always thought that navigation was relatively basic. However, when faced with a combination of pushed & presented UIViewControllers, I realized how important it is to fully understand the different navigation patterns.
Swift как с модального экрана делать pushviewcontroller
Pushing, Popping, Presenting, & Dismissing ViewControllers
I recently found myself in a situation where I had presented a UINavigationController modally and then pushed UIViewControllers within the UINavigationController. On the last UIViewController, I had two scenarios that I wanted to handle: 1) navigate back to the first UIViewController within the UINavigationController’s stack and 2) dismiss the UINavigationController, including all the pushed UIViewControllers contained in the UINavigationController stack.
After some initial trial & error, I decided to spend some time understanding the different ways in which both UINavigationControllers and UIViewControllers handle navigation.
First lesson learned:
Any UIViewController (including UINavigationControllers as UINavigationControllers are a type of UIViewControllers) presented modally must be dismissed.
Second lesson learned:
Since UINavigationControllers hold UIViewControllers in their stack, they can present UIViewControllers both modally and via a push. When navigating away from any pushed UIViewControllers, UINavigationControllers must pop the UIViewController.
Third lesson learned:
Though it is possible to present UIViewControllers both modally and via a push, UIViewControllers can only present other UIViewControllers. Therefore, UIViewControllers can only be dismissed (unless they are inside a UINavigationController stack and the UINavigationController is handling the pop — see lesson #2). This is why it is not possible to call popViewController on a UIViewController.
Fourth lesson learned:
PresentingViewController is a property provided by Apple that is available on both UIViewControllers and UINavigationControllers. Calling presentingViewController.dismiss(animated:completion) will dismiss everything presented by the parentViewController. The parentViewController is the UIViewController that presented the current UIViewController. So, if you have a UINavigationController with 3 UIViewControllers within its stack, calling presentingViewController.dismiss(animated:completion) will dismiss both the UINavigationController and the 3 UIViewControllers held within its stack.
Fifth lesson learned:
Similarly, Apple also provides another property on UIViewControllers and UINavigationControllers — presentedViewController. Calling presentedViewController.dismiss(animated:completion) will dismiss the childViewController. The childViewController is the current UIViewController that is being presented.
Prior to this recent investigation, I always thought that navigation was relatively basic. However, when faced with a combination of pushed & presented UIViewControllers, I realized how important it is to fully understand the different navigation patterns.
Swift как с модального экрана делать pushviewcontroller
За последнюю неделю я ломаю себе голову над тем, как решить проблему с отображением и отключением нескольких контроллеров представления. Я создал образец проекта и вставил код прямо из проекта. У меня есть 3 контроллера просмотра с соответствующими файлами .xib. MainViewController, VC1 и VC2. У меня есть две кнопки на главном контроллере вида.
Это открывает VC1 без проблем. В VC1 у меня есть еще одна кнопка, которая должна открывать VC2 и одновременно закрывать VC1.
Я хочу, чтобы он вернулся к контроллеру основного представления, в то время как VC1 должен был быть навсегда удален из памяти. VC1 должен отображаться только тогда, когда я нажимаю кнопку VC1 на главном контроллере.
Другая кнопка на контроллере главного представления также должна иметь возможность отображать VC2 напрямую, минуя VC1, и должна возвращаться на главный контроллер при нажатии кнопки на VC2. Нет длительного кода, циклов или таймеров. Просто вызовы костей для просмотра контроллеров.
Ответы
не отправляет сообщение самому себе, он фактически отправляет сообщение своему представляющему VC, прося его выполнить отклонение. Когда вы представляете VC, вы создаете отношения между представляющим VC и представленным. Таким образом, вы не должны уничтожать представляющий VC во время его представления (представленный VC не может отправить это сообщение о закрытии…). Поскольку вы на самом деле не учитываете это, вы выходите из приложения в запутанном состоянии. См. Мой ответ « Отклонение представленного контроллера представления», в котором я рекомендую этот метод, написан более четко:
В вашем случае вам необходимо убедиться, что все управление выполняется в mainVC . Вы должны использовать делегата для отправки правильного сообщения обратно в MainViewController из ViewController1, чтобы mainVC мог отклонить VC1, а затем представить VC2.
В VC2 VC1 добавьте протокол в файл .h над @interface:
и ниже в том же файле в разделе @interface объявите свойство для хранения указателя делегата:
В файле VC1 .m метод кнопки отклонения должен вызывать метод делегата
Теперь в mainVC установите его как делегата VC1 при создании VC1:
и реализуем метод делегата:
present2: может быть тем же методом, что и ваш метод IBAction VC2Pressed: button. Обратите внимание, что он вызывается из блока завершения, чтобы гарантировать, что VC2 не будет представлен, пока VC1 не будет полностью отклонен.
Теперь вы переходите от VC1-> VCMain-> VC2, поэтому вы, вероятно, захотите, чтобы был анимирован только один из переходов.
Обновить
В своих комментариях вы выражаете удивление по поводу сложности, необходимой для достижения, казалось бы, простой вещи. Уверяю вас, этот шаблон делегирования настолько важен для большей части Objective-C и Какао, и этот пример — самый простой из возможных, и вам действительно стоит приложить усилия, чтобы освоиться с ним.
В Руководстве по программированию контроллера View от Apple говорится следующее :
Отклонение представленного контроллера представления
Когда приходит время отклонить представленный контроллер представления, предпочтительный подход состоит в том, чтобы позволить контроллеру представления представления отклонить его. Другими словами, всякий раз, когда это возможно, тот же контроллер представления, который представил контроллер представления, также должен нести ответственность за его отклонение. Хотя существует несколько методов для уведомления контроллера представления представления о том, что его представленный контроллер представления должен быть отклонен, предпочтительным методом является делегирование. Для получения дополнительной информации см. «Использование делегирования для связи с другими контроллерами».
Если вы действительно продумаете, чего хотите достичь и как вы это делаете, вы поймете, что обмен сообщениями с вашим MainViewController для выполнения всей работы является единственным логическим выходом, учитывая, что вы не хотите использовать NavigationController. Если вы действительно используете NavController, вы фактически «делегируете», даже если не явно, navController для выполнения всей работы. Должен быть какой-то объект, который будет отслеживать все, что происходит с вашей навигацией по VC, и вам нужен какой-то способ связи с ним, что бы вы ни делали.
На практике совет Apple немного экстремален . в обычных случаях вам не нужно создавать выделенный делегат и метод, вы можете положиться на [self presentingViewController] dismissViewControllerAnimated: — это когда в таких случаях, как ваш, вы хотите, чтобы вы уволили чтобы иметь другие эффекты на удаленные объекты, о которых вам нужно позаботиться.
Вот что вы можете себе представить, работая без хлопот с делегатами .
После того, как представляющий контроллер попросил нас уволить, у нас есть блок завершения, который вызывает метод в PresentingViewController для вызова VC2. Делегат не требуется. (Большой плюс блоков в том, что они уменьшают потребность в делегатах в этих обстоятельствах). Однако в этом случае есть несколько вещей, которые мешают .
- в VC1 вы не знаете, что mainVC реализует метод present2 — вы можете столкнуться с трудными для отладки ошибками или сбоями. Делегаты помогут вам избежать этого.
- после того, как VC1 отклонен, на самом деле не нужно выполнять блок завершения . или нет? Означает ли self.presentingViewController что-нибудь еще? Вы не знаете (я тоже) . с делегатом у вас нет этой неопределенности.
- Когда я пытаюсь запустить этот метод, он просто зависает без предупреждений или ошибок.
Так что, пожалуйста . найдите время, чтобы изучить делегирование!
update2
В своем комментарии вам удалось заставить его работать, используя это в обработчике кнопки увольнения VC2:
Это, конечно, намного проще, но при этом возникает ряд проблем.
Тесная связь
Вы жестко связываете свою структуру viewController вместе. Например, если вы вставите новый viewController перед mainVC, ваше требуемое поведение будет нарушено (вы перейдете к предыдущему). В VC1 вам также нужно было #import VC2. Следовательно, у вас довольно много взаимозависимостей, что нарушает цели ООП / MVC.
При использовании делегатов ни VC1, ни VC2 не нужно ничего знать о mainVC или его предшественниках, поэтому мы сохраняем все слабо связанное и модульное.
объем памяти
VC1 никуда не делся, у вас все еще есть два указателя на него:
- presentedViewController свойство mainVC
- Свойство presentingViewController VC2
Вы можете проверить это, зарегистрировавшись, а также просто сделав это из VC2.
Он по-прежнему работает, по-прежнему возвращает вас к VC1.
Мне это кажется утечкой памяти.
Ключ к разгадке — в предупреждении, которое вы здесь получаете:
Логика нарушается, поскольку вы пытаетесь отклонить представляющий VC, для которого VC2 является представленным VC. Второе сообщение на самом деле не выполняется — возможно, что-то происходит, но у вас все еще остаются два указателя на объект, от которого, как вы думали, вы избавились. ( править — я проверил это, и это не так уж плохо, оба объекта исчезнут, когда вы вернетесь в mainVC )
Это довольно длинный способ сказать — пожалуйста, используйте делегатов. Если это поможет, я сделал еще одно краткое описание паттерна здесь:
Всегда ли передача контроллера в конструктор — плохая практика?
обновление 3
Если вы действительно хотите избегать делегатов, это может быть лучшим выходом:
Но ничего не сбрасывайте со счетов . как мы выяснили, на самом деле этого не происходит.
Как мы (знаем), мы не отклонили VC1, мы можем вернуться через VC1 к MainVC. MainVC отклоняет VC1. Поскольку VC1 ушел, предполагается, что VC2 идет с ним, так что вы вернулись на MainVC в чистом состоянии.
Он по-прежнему сильно связан, поскольку VC1 должен знать о VC2, а VC2 должен знать, что он был доставлен через MainVC-> VC1, но это лучшее, что вы получите без небольшого явного делегирования.
Push ViewController с модальной анимацией (горизонтальный флип)
Мне нужно нажать контроллер вида на другой контроллер представления.
переход от menuVC к VC1 не требует анимации, но переход от VC1 к VC2 и от VC2 к VC1 требует возникновения флип-анимации.
Однако при переходе с VC2 на menuVC анимация не требуется.
Я использую следующий код:
Экран полностью исчезает, когда я пытаюсь сделать это.
Теперь вы должны использовать анимацию на основе блоков. Кроме того, если вы создаете контролер раскадровки из контроллера, который также находится в одном и том же раскадровке, вы можете более легко получить доступ к этой раскадровке с помощью self.storyboard.
Однако это также создало пустой экран.
Вместо этого, если вы используете раскадровки, лучше всего создать экземпляр через раскадровку:
yourViewControllerID устанавливается в раскадровке под инспектором идентификации для класса yourviewcontroller.
Как поменять UIViewController внутри модального окна?
Потому-что в нормальном варианте это должно выглядеть следующим образом:
[MainScreenVC]
Button1
—> present [LogInVC]
——> present [NavigationController]
———> push [UserFeedVC]
Button2
—> present [SignInVC]
——>present [NavigationController]
———>push [UserFeedVC]
- Вконтакте
В нормальном варианте:
— модальное окно закрывается, пересылая МэйнСкрину инфу о результате логина
— МэйнСкрин пушит/презентит то что надо
Как поменять UIViewController внутри модального окна?
Потому-что в нормальном варианте это должно выглядеть следующим образом:
[MainScreenVC]
Button1
—> present [LogInVC]
——> present [NavigationController]
———> push [UserFeedVC]
Button2
—> present [SignInVC]
——>present [NavigationController]
———>push [UserFeedVC]
- Вконтакте
В нормальном варианте:
— модальное окно закрывается, пересылая МэйнСкрину инфу о результате логина
— МэйнСкрин пушит/презентит то что надо
iOS 5 Попытка pushViewController из модального представления
У меня есть приложение с вкладками iOS 5, использующее раскадровки.
Мой контроллер панели вкладок указывает на три контроллера навигации.
С одной из них поток выглядит так:
Начальный вид —> Просмотр фотографий (модальный) —> Просмотр каталога
На экране фото у меня есть кнопка со следующим кодом:
Я пробовал дурачиться с presentingViewController , parentViewController — даже тип привёл их к UINavigationController . Это приводит к сбою со следующим сообщением об ошибке:
Так что это говорит мне, что я не получил UINavigationController , но UITabBarController .
Есть ли способ обойти это?
2 ответы
Переход в стиле «push» может быть выполнен только из контроллера представления, которым управляет UINavigationController . Если вы попытаетесь это сделать, иначе ничего не произойдет.
Вместо того, чтобы отображать представление Photo модально, как вы описываете в своем вопросе, вы должны отобразить экземпляр UINavigationController в качестве модального представления и сделать представление Photo View контроллером корневого представления представления навигации. (Все это можно настроить с помощью раскадровки). Тогда ваш push segue будет работать.
Если вы не хотите, чтобы верхняя панель навигации появлялась на вашем первом контроллере представления (просмотр фотографий), вы можете использовать:
Это скроет верхнюю панель навигации. После того, как вы нажмете новый контроллер представления, если вы хотите, чтобы панель навигации снова появлялась на этом и любых последующих контроллерах представления, вам нужно будет установить для setNavigationBarHidden значение NO на новом контроллере представления.