Я не претендую на истину в последней инстанции, но и в разработке кое-чего все-таки смыслю. Посему решил поделиться с вами некоторыми результатами проделанной работы, поделиться некой компиляцией знания о навигационных контроллерах, так сказать. Может это и поможет какой-либо из бренных оболочек, способных именоваться далее моими читателями, создать более совершенный программный продукт.
Предметом исследования будет навигационный контроллер, а именно класс UINavigationController из стандартного фреймворка UIKit для работы с интерфейсом, который нам любезно предоставляет Apple.
«Контроллер» в данном случае — некий класс, инкапсулирующий логику, согласно концепции (еще называемой паттерном) MVC.


Навигационный контроллер (UINavigationController) — класс высокого уровня абстракции, содержит в себе иерархию других контроллеров представлений, между представлениями(вьюшками/UIView) которых способен осуществлять навигацию (в чем его, собственно, основная задача и состоит!), передавая в нужный момент управление соответствующему контроллеру. Кроме этого — композиционно содержит в себе навигационную панель (UINavigationBar), которую отображает на экране, и соответствующим образом меняет содержимое данной панели: в зависимости от активного контроллера.
В любой момент из активного контроллера можно получить, как текущий navigation Item, так и navigation Bar:
Иерархическая структура — всегда древовидная:



Мое знакомство с этим элементом управления поначалу было поверхностным, но после одного случая пришлось углубиться. Дело в том, что в одном моем приложении, в разных местах, в связи с большим количеством асинхронности, неслабой связностью — происходило куча всякого непотребства при переходах от одного экрана к другому, да и постоянно происходили двойные переходы, при быстрых касаниях (тачах). До этого мне удавалось успешно справляться различными обходными путями, но куда уж мы бы делись без стремления к совершенному…
В один прекрасный момент мне нужно было прекратить двойной переход (через 2 уровня иерархии, после срабатывания кнопки назад, и быстрого срабатывания). Собственно требовалось поставить блокировку в момент срабатывания перехода. После некоторого исследования выяснилось, что существует 2 способа это сделать:
1) Создать кнопку программно, повесить на навигейшен бар, прикрепить к ней соответствующий селектор (метод-обработчик), в котором явно осуществлять блокировку и вызывать один из методов, по типу popViewControllerAnimated:;
2) Использовать протокол, реализующий делегата для навигационной панели UINavigationBarDelegate.
К сожалению, у первого подхода был явный недостаток: программно создавая кнопку и вешая ее на навигейшен бар, я не смог бы добиться легко стандартной стрелочки и кнопки назад (у меня просто не было этой иконки, она походу берется из стандартных asset-ов (наборов)).
После некоторых проб выяснилось, что UINavigationBarDelegate позволяет, чтобы в качестве делегата был только UINavigationController, и я решился попробовать все-таки сделать подкласс для этого зверя.
Делегирование — один из фундаментальных паттернов проектирования, суть которого в том, что мы делегируем (переназначаем) ответствие за какие-либо действия на класс делегата. Конкретно для objective-c:
Класс делегирующий поведение -> класс-делегат
— назначаем соответствующий протокол классу-делегату, например — определяем все методы со спецификатором @required
и некоторые методы, помеченные ключевым словом @optional
— назначаем классу, который делегирует поведение, этот делегат через свойство делегата (у класса делегирующего должно быть свойство, что-то вроде @property (assign, nonatomic) id delegate;)
— после этого, если мы пишем первый класс, то в нужных местах тягаем методы, не забывая делать проверки по типу
В общем, чем это похоже на то, что один объект нанимает другой объект, чтобы этот объект объяснил ему, что делать и как поступать в определенных ситуациях. Так-то…

Создание нового подкласса на objective-c любят обзывать «субклассированием», поэтому не буду сильно отходить от этих канонов.
В чем преимущество создания подкласса? Сначала я думал обработать в навигейшене только одну ситуацию, но после пришел к выводу, что значительно лучше централизованно обрабатывать все схожие ситуации, внедрить определенные куски кода напрямую в навигейшен, чтобы избавиться от некоторых проблем на корню и для всех других ситуаций. Еще одно преимущество в том, что можно централизованно (в одном месте кода) писать конфигурационный код, который будет общим для каждого контроллера (например, в моем случае — отключать мультитач)
Почти все методы навигации в данном случае начинаются с приставок push/pop, что-то вроде протолкнуть/вытолкнуть (не как в Git-e антонимы push/pull), но такова была принятая не мной конвенция именования целевых методов. Пару слов про UINavigationBar. Он содержит в себе схожую иерархию, но NavigationItem-ов. Эти Item-ы представляют из себя элементы UINavigationBar-a (к сабвьюшкам этого бара, напрямую, доступа нет. Да и в документации явно не рекомендуется каким-либо образом их доставать/менять
Только из названия уже должно быть предельно ясно, что это методы по типу will/did. Первый вызывается перед соответствующим действием, второй — после. Только в данном случае первый метод по типу should, еще и являет ответ на вопрос: «выполнять ли это действие?» Таким образом, метод should запускается перед анимацией замены item-a navigationBar-a, а метод did — после. Исходя из задачи, первой моей идеей было блокировать пользовательское взаимодействие в методе should, и возвращать в методе did. Методы push означают движение вниз по иерархии (к более частному), а методы pop — в направлении к корневому.
Одна из ключевых концепций защитного программирования при асинхронности — «обрабатываем соответствующим образом, или блокируем промежуточные состояния». Промежуточные состояния (intermediate states) всегда являются одним из главных источников багов в программах. Так как анимация по своей сути — действие асинхронное (то есть неизвестен точный момент времени, когда вызовется кусок кода, означающий окончание действия, вследствие чего его невозможно синхронизировать с другими кусками кода. Асинхронный код всегда выполняется в отдельном потоке), то его следует экранировать!
По защитному программированию теоретическая часть вполне себе неплохо описана в известном чтиве «Совершенный код»

Кроме того, анимация перехода (segue) с одного корневого представления к другому тоже занимает определенное время, как выяснилось, оно отлично от времени анимации навигационной панели. Длительность анимации UINavigationBar-a статична и определяется константой
А длительность анимации перехода может быть различна. Основная причина этого — методы viewDidLoad/viewWillAppear:/методы построения макета (layout-a) по правилам построения (ограничениям/constraint-ам). Соответственно, анимацию перехода — тоже нужно экранировать.
У UINavigationController-a есть протокол делегата UINavigationControllerDelegate. Он определяет 6 методов, 4 связанных с transition-ами, позволяющими обрабатывать непосредственно текущую анимацию (но Available ios 7.0 + соответственно говорит, что они еще недостаточно актуальны), а вот остальные 2 — просто кладезь).
Соответственно обработчики начала и окончания анимации появления контроллера представления.
Хотелось бы еще пару слов о переходах (segue), в последнее время они стали удобной и модной технологией, так как позволяют на сторибоарде творить чудеса. Ранее для выполнения перехода требовалось инстанцировать экземпляр контроллера, передать нужные данные в объект, и запустить метод pushViewController:animated:, теперь достаточно создать «сегу» на сторибоарде, на экшен, если требуется — повесить идентификатор, конфигурировать. В нашем случае segue navigation controller-a всегда запускаются как push (не как modal или что-то другое).
После этого с любым переходом можно работать в коде, существует 3 метода UIViewController-a:
Первый метод позволяет перед переходом выполнять какие-либо действия с контроллером назначения перед его появлением, обрабатывать различные переходы (
Второй метод позволяет, кроме прочего, позволить или прервать выполнение перехода.
Третий метод позволяет программно вызвать переход в коде, собственно он содержит в себе код перехода с
Самое главное здесь то, что переходы push с помощью segue вызывают одни и те же методы из navigationController-a (если он есть):

Что еще может быть интересно здесь? Существуют так называемые обратные переходы (unwind segue), которые выполняют переходы обратно по контроллерам (они также содержат в себе методы pop). И у каждого из UIStoryboardSegue есть метод perform, в котором можно переопределять анимацию перехода с помощью субклассирования UIStoryboardSegue.
Использование переходов (segue) является наиболее современной практикой выполнения перемещения с одного контроллера представления к другому.


И еще для того, чтобы грамотно выполнить поставленную задачу — пару слов о пользовательском взаимодействии с интерфейсом. Когда пользователь касается экрана, генерится и вбрасывается touch event, к сожалению UIEvent не имеет открытого конструктора, так что мы не имеем возможности легко создавать наши события касания к экрану устройства, таким образом эмулируя данную ситуацию. Контролы во всем приложении реагируют на соответствующие события (event-ы), им предназначенные, в результате чего интерфейс становится интерактивным и реагирующим на действия пользователя.
Некоторые действия на события уже предопределены (например, когда мы делаем touch down по кнопке — кнопка переходит в состояние highlighted (подсвечена), и меняет внешний вид). Мы можем перехватывать события, и обрабатывать их, как нам вздумается, назначая обработчики, через селекторы. Селектор хранит в себе хэш-значение, позволяющее быстро выбрать связанный с ним метод из хэш-таблицы селекторов класса. Все Event-ы назначаются и направляются (если не ошибаюсь) в недрах класса UIApplication, который имеет 2 важных метода
В общем, это реализация target-action паттерна:

Существует 2 способа блокировать пользовательское взаимодействие: первый — блокирование получения событий конкретным элементом управления (контролом); второй — блокирование отправки событий непосредственно из объекта-экземпляра приложения.
1й способ (у каждого View есть свойство userInteractionEnabled):
2й способ (объект приложения является синглтоном):
Так как имеется нужда блокировать любое взаимодействие (неизвестно при нажатии конкретно по какой кнопке будет выполняться опасный код (со следующим далее переходом)), то нам подходит второй способ.
Как вы, может, знаете, наилучшая практика — это задавать внешний вид с помощью UIAppearance, но благодаря подобному подклассу, можно и отказаться от нее, если использовать везде этот подкласс. К тому-же инкапсулировать эту логику (сокрыть) внутри навигейшен контроллера является весьма грамотным решением. Для этого подходит метод
Если кому-то интересно про мультитач (чтобы не было возможности нажать подряд 2 кнопки):
PS. если вы хотите воспользоваться сиим чудом, пользуйтесь на свой страх и риск, я далеко не все опробовал из того, что имелось, так что для некоторых ситуаций вам придется, возможно, дописывать самим. Классы Utility/GAIClient не поставляются (из первого берется метод на отключение мультитача, с помощью второго — отсылается non-crash репорт на GoogleAnalytics).
Было реализовано:
в) соответственно блокированием пользовательского взаимодействия, если началась хотя-бы одна соответствующая анимация, и разблокированием, если завершились все;
вшитая защита от обработки экшенов сразу 2х кнопок (посредством отключения мультитача);
механизм деблокирования, в случае если что-то пошло не так;
создание репорта, если что-то пошло не так.
1-я проблема была связана с тем, что при использовании явного и неявного переходов (во втором случае через navigation bar-кнопку «Back») во втором случае не запускается метод
2-я проблема — поведение navigation-bar-a на iOS 7.0. На этой прошивке для стандартного навигейшен контроллера делегат назначается автоматически (и если мы еще раз пытаемся это сделать вручную — генерит исключение (exception)).
3-я проблема — на 7й прошивке имеется правый свайп interactivePopGestureRecognizer, который позволяет делать переходы назад (он вызывал только метод navigation controller delegate will, из-за чего намертво блочил пользовательское взаимодействие).
4-я проблема — в крайне редких ситуациях могла возникнуть опасность, что не всегда запускался противоположный метод (система должна была быть в случае чего самовосстанавливающейся). Было реализовано подобие таймера, с обработчиком-деблокиратором.
Git Repo на GitHub-e
Предметом исследования будет навигационный контроллер, а именно класс UINavigationController из стандартного фреймворка UIKit для работы с интерфейсом, который нам любезно предоставляет Apple.
Вкратце о...
«Контроллер» в данном случае — некий класс, инкапсулирующий логику, согласно концепции (еще называемой паттерном) MVC.


Навигационный контроллер (UINavigationController) — класс высокого уровня абстракции, содержит в себе иерархию других контроллеров представлений, между представлениями(вьюшками/UIView) которых способен осуществлять навигацию (в чем его, собственно, основная задача и состоит!), передавая в нужный момент управление соответствующему контроллеру. Кроме этого — композиционно содержит в себе навигационную панель (UINavigationBar), которую отображает на экране, и соответствующим образом меняет содержимое данной панели: в зависимости от активного контроллера.
В любой момент из активного контроллера можно получить, как текущий navigation Item, так и navigation Bar:
self.navigationItem
self.navigationController.navigationBar
Иерархическая структура — всегда древовидная:



Предыстория
Мое знакомство с этим элементом управления поначалу было поверхностным, но после одного случая пришлось углубиться. Дело в том, что в одном моем приложении, в разных местах, в связи с большим количеством асинхронности, неслабой связностью — происходило куча всякого непотребства при переходах от одного экрана к другому, да и постоянно происходили двойные переходы, при быстрых касаниях (тачах). До этого мне удавалось успешно справляться различными обходными путями, но куда уж мы бы делись без стремления к совершенному…
В один прекрасный момент мне нужно было прекратить двойной переход (через 2 уровня иерархии, после срабатывания кнопки назад, и быстрого срабатывания). Собственно требовалось поставить блокировку в момент срабатывания перехода. После некоторого исследования выяснилось, что существует 2 способа это сделать:
1) Создать кнопку программно, повесить на навигейшен бар, прикрепить к ней соответствующий селектор (метод-обработчик), в котором явно осуществлять блокировку и вызывать один из методов, по типу popViewControllerAnimated:;
2) Использовать протокол, реализующий делегата для навигационной панели UINavigationBarDelegate.
К сожалению, у первого подхода был явный недостаток: программно создавая кнопку и вешая ее на навигейшен бар, я не смог бы добиться легко стандартной стрелочки и кнопки назад (у меня просто не было этой иконки, она походу берется из стандартных asset-ов (наборов)).
После некоторых проб выяснилось, что UINavigationBarDelegate позволяет, чтобы в качестве делегата был только UINavigationController, и я решился попробовать все-таки сделать подкласс для этого зверя.
О делегировании, навигации и защитном программировании, UINavigationControllerDelegate/UInavigationBarDelegate
Делегирование — один из фундаментальных паттернов проектирования, суть которого в том, что мы делегируем (переназначаем) ответствие за какие-либо действия на класс делегата. Конкретно для objective-c:
Класс делегирующий поведение -> класс-делегат
— назначаем соответствующий протокол классу-делегату, например — определяем все методы со спецификатором @required
и некоторые методы, помеченные ключевым словом @optional
— назначаем классу, который делегирует поведение, этот делегат через свойство делегата (у класса делегирующего должно быть свойство, что-то вроде @property (assign, nonatomic) id delegate;)
— после этого, если мы пишем первый класс, то в нужных местах тягаем методы, не забывая делать проверки по типу
if(self.delegate && [self.delegate conformsToProtocol:@protocol(MyProtocol)] && [self.delegate respondsToSelector: @selector(aMethod)]){
[delegate aMethod];
}
В общем, чем это похоже на то, что один объект нанимает другой объект, чтобы этот объект объяснил ему, что делать и как поступать в определенных ситуациях. Так-то…

Создание нового подкласса на objective-c любят обзывать «субклассированием», поэтому не буду сильно отходить от этих канонов.
В чем преимущество создания подкласса? Сначала я думал обработать в навигейшене только одну ситуацию, но после пришел к выводу, что значительно лучше централизованно обрабатывать все схожие ситуации, внедрить определенные куски кода напрямую в навигейшен, чтобы избавиться от некоторых проблем на корню и для всех других ситуаций. Еще одно преимущество в том, что можно централизованно (в одном месте кода) писать конфигурационный код, который будет общим для каждого контроллера (например, в моем случае — отключать мультитач)
Почти все методы навигации в данном случае начинаются с приставок push/pop, что-то вроде протолкнуть/вытолкнуть (не как в Git-e антонимы push/pull), но такова была принятая не мной конвенция именования целевых методов. Пару слов про UINavigationBar. Он содержит в себе схожую иерархию, но NavigationItem-ов. Эти Item-ы представляют из себя элементы UINavigationBar-a (к сабвьюшкам этого бара, напрямую, доступа нет. Да и в документации явно не рекомендуется каким-либо образом их доставать/менять
frame/bounds/alpha
UINavigationBar-a (он все-таки наследуется от UIView)). То есть конфигурировать навигейшен бар все-таки следует напрямую созданными и инициализированными navigationItem-ами, а все остальное — от лукавого. К чему все это? А к тому, что UINavigationBarDelegate предоставляет доступ к 4-м методам: - (BOOL)navigationBar:(UINavigationBar *)navigationBar
shouldPushItem:(UINavigationItem *)item;
- (void)navigationBar:(UINavigationBar *)navigationBar
didPushItem:(UINavigationItem *)item;
- (BOOL)navigationBar:(UINavigationBar *)navigationBar
shouldPopItem:(UINavigationItem *)item;
- (void)navigationBar:(UINavigationBar *)navigationBar
didPopItem:(UINavigationItem *)item;
Только из названия уже должно быть предельно ясно, что это методы по типу will/did. Первый вызывается перед соответствующим действием, второй — после. Только в данном случае первый метод по типу should, еще и являет ответ на вопрос: «выполнять ли это действие?» Таким образом, метод should запускается перед анимацией замены item-a navigationBar-a, а метод did — после. Исходя из задачи, первой моей идеей было блокировать пользовательское взаимодействие в методе should, и возвращать в методе did. Методы push означают движение вниз по иерархии (к более частному), а методы pop — в направлении к корневому.
Одна из ключевых концепций защитного программирования при асинхронности — «обрабатываем соответствующим образом, или блокируем промежуточные состояния». Промежуточные состояния (intermediate states) всегда являются одним из главных источников багов в программах. Так как анимация по своей сути — действие асинхронное (то есть неизвестен точный момент времени, когда вызовется кусок кода, означающий окончание действия, вследствие чего его невозможно синхронизировать с другими кусками кода. Асинхронный код всегда выполняется в отдельном потоке), то его следует экранировать!
По защитному программированию теоретическая часть вполне себе неплохо описана в известном чтиве «Совершенный код»

Кроме того, анимация перехода (segue) с одного корневого представления к другому тоже занимает определенное время, как выяснилось, оно отлично от времени анимации навигационной панели. Длительность анимации UINavigationBar-a статична и определяется константой
extern const CGFloat UINavigationControllerHideShowBarDuration;
А длительность анимации перехода может быть различна. Основная причина этого — методы viewDidLoad/viewWillAppear:/методы построения макета (layout-a) по правилам построения (ограничениям/constraint-ам). Соответственно, анимацию перехода — тоже нужно экранировать.
У UINavigationController-a есть протокол делегата UINavigationControllerDelegate. Он определяет 6 методов, 4 связанных с transition-ами, позволяющими обрабатывать непосредственно текущую анимацию (но Available ios 7.0 + соответственно говорит, что они еще недостаточно актуальны), а вот остальные 2 — просто кладезь).
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated;
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated;
Соответственно обработчики начала и окончания анимации появления контроллера представления.
О переходах (Segues)
Хотелось бы еще пару слов о переходах (segue), в последнее время они стали удобной и модной технологией, так как позволяют на сторибоарде творить чудеса. Ранее для выполнения перехода требовалось инстанцировать экземпляр контроллера, передать нужные данные в объект, и запустить метод pushViewController:animated:, теперь достаточно создать «сегу» на сторибоарде, на экшен, если требуется — повесить идентификатор, конфигурировать. В нашем случае segue navigation controller-a всегда запускаются как push (не как modal или что-то другое).
После этого с любым переходом можно работать в коде, существует 3 метода UIViewController-a:
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender;
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier
sender:(id)sender;
- (void)performSegueWithIdentifier:(NSString *)identifier
sender:(id)sender;
Первый метод позволяет перед переходом выполнять какие-либо действия с контроллером назначения перед его появлением, обрабатывать различные переходы (
identifier
перехода и destinationViewController
).Второй метод позволяет, кроме прочего, позволить или прервать выполнение перехода.
Третий метод позволяет программно вызвать переход в коде, собственно он содержит в себе код перехода с
pushViewController:animated:
.Самое главное здесь то, что переходы push с помощью segue вызывают одни и те же методы из navigationController-a (если он есть):

Что еще может быть интересно здесь? Существуют так называемые обратные переходы (unwind segue), которые выполняют переходы обратно по контроллерам (они также содержат в себе методы pop). И у каждого из UIStoryboardSegue есть метод perform, в котором можно переопределять анимацию перехода с помощью субклассирования UIStoryboardSegue.
Использование переходов (segue) является наиболее современной практикой выполнения перемещения с одного контроллера представления к другому.
О target-action модели, о взаимодействии пользователя (User Interaction)


И еще для того, чтобы грамотно выполнить поставленную задачу — пару слов о пользовательском взаимодействии с интерфейсом. Когда пользователь касается экрана, генерится и вбрасывается touch event, к сожалению UIEvent не имеет открытого конструктора, так что мы не имеем возможности легко создавать наши события касания к экрану устройства, таким образом эмулируя данную ситуацию. Контролы во всем приложении реагируют на соответствующие события (event-ы), им предназначенные, в результате чего интерфейс становится интерактивным и реагирующим на действия пользователя.
Некоторые действия на события уже предопределены (например, когда мы делаем touch down по кнопке — кнопка переходит в состояние highlighted (подсвечена), и меняет внешний вид). Мы можем перехватывать события, и обрабатывать их, как нам вздумается, назначая обработчики, через селекторы. Селектор хранит в себе хэш-значение, позволяющее быстро выбрать связанный с ним метод из хэш-таблицы селекторов класса. Все Event-ы назначаются и направляются (если не ошибаюсь) в недрах класса UIApplication, который имеет 2 важных метода
- (void)sendEvent:(UIEvent *)event;
- (BOOL)sendAction:(SEL)action
to:(id)target
from:(id)sender
forEvent:(UIEvent *)event;
В общем, это реализация target-action паттерна:

Существует 2 способа блокировать пользовательское взаимодействие: первый — блокирование получения событий конкретным элементом управления (контролом); второй — блокирование отправки событий непосредственно из объекта-экземпляра приложения.
1й способ (у каждого View есть свойство userInteractionEnabled):
self.navigationController.navigationBar.userInteractionEnabled = NO;
self.someButton.userInteractionEnabled = YES;
2й способ (объект приложения является синглтоном):
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
Так как имеется нужда блокировать любое взаимодействие (неизвестно при нажатии конкретно по какой кнопке будет выполняться опасный код (со следующим далее переходом)), то нам подходит второй способ.
Внешний вид navigation-bar-a
Как вы, может, знаете, наилучшая практика — это задавать внешний вид с помощью UIAppearance, но благодаря подобному подклассу, можно и отказаться от нее, если использовать везде этот подкласс. К тому-же инкапсулировать эту логику (сокрыть) внутри навигейшен контроллера является весьма грамотным решением. Для этого подходит метод
awakeFromNib
. Самолично я не пытался делать такое, но подсмотрел у других. Это был небольшой совет. Мультитач
Если кому-то интересно про мультитач (чтобы не было возможности нажать подряд 2 кнопки):
- (void) makeExclusiveTouchToSubviews:(UIView*)view {
for (UIView * currentSubtView in [view subviews]) {
currentSubView.multipleTouchEnabled = NO;
currentSubView.exclusiveTouch = YES;
[self makeExclusiveTouchToSubviews:currentSubView];
}
}
PS. если вы хотите воспользоваться сиим чудом, пользуйтесь на свой страх и риск, я далеко не все опробовал из того, что имелось, так что для некоторых ситуаций вам придется, возможно, дописывать самим. Классы Utility/GAIClient не поставляются (из первого берется метод на отключение мультитача, с помощью второго — отсылается non-crash репорт на GoogleAnalytics).
Реализованный функционал
Было реализовано:
- Способ блокировать переходы быстро вручную (в случае надобности);
- 3 уровня защиты от переходов:
а) на уровне методов should navigationBarDelegate;
в) соответственно блокированием пользовательского взаимодействия, если началась хотя-бы одна соответствующая анимация, и разблокированием, если завершились все;
вшитая защита от обработки экшенов сразу 2х кнопок (посредством отключения мультитача);
механизм деблокирования, в случае если что-то пошло не так;
создание репорта, если что-то пошло не так.
Возникшие нюансы и проблемы
1-я проблема была связана с тем, что при использовании явного и неявного переходов (во втором случае через navigation bar-кнопку «Back») во втором случае не запускается метод
popToViewController:animated:
, пришлось явно проверять, осуществляется ли уже переход с одного контроллера на другой;2-я проблема — поведение navigation-bar-a на iOS 7.0. На этой прошивке для стандартного навигейшен контроллера делегат назначается автоматически (и если мы еще раз пытаемся это сделать вручную — генерит исключение (exception)).
3-я проблема — на 7й прошивке имеется правый свайп interactivePopGestureRecognizer, который позволяет делать переходы назад (он вызывал только метод navigation controller delegate will, из-за чего намертво блочил пользовательское взаимодействие).
4-я проблема — в крайне редких ситуациях могла возникнуть опасность, что не всегда запускался противоположный метод (система должна была быть в случае чего самовосстанавливающейся). Было реализовано подобие таймера, с обработчиком-деблокиратором.
Скачать/посмотреть
Git Repo на GitHub-e