Я не претендую на істину в останній інстанції, але й у розробці дечого все-таки розумію. Тому вирішив поділитися з вами деякими результатами виконаної роботи, поділитися компіляцією знання про навігаційних контролерів, так би мовити. Може це і допоможе будь-якій з тлінних оболонок, здатних іменуватися далі моїми читачами, створити більш досконалий програмний продукт.

Предметом дослідження буде навігаційний контролер, а саме клас UINavigationController із стандартного фреймворку UIKit для роботи з інтерфейсом, який нам люб'язно надає Apple.

Коротко про…

"Контролер" в даному випадку – якийсь клас, що інкапсулює логіку, згідно з концепцією (ще званої патерном) MVC.

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

Ієрархічна структура завжди деревоподібна:

Передісторія

Моє знайомство з цим елементом управління спочатку було поверховим, але після одного випадку довелося заглибитись.Справа в тому, що в одному моєму додатку, в різних місцях, у зв'язку з великою кількістю асинхронності, неслабкою зв'язністю – відбувалося купа всякого непотребу при переходах від одного екрана до іншого, та й постійно відбувалися подвійні переходи, при швидких торканнях (тачах). До цього мені вдавалося успішно справлятися різними обхідними шляхами, але куди ми поділися б без прагнення до досконалого…

Одного разу мені потрібно було припинити подвійний перехід (через 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)])

Загалом, чим це схоже на те, що один об'єкт наймає інший об'єкт, щоб цей об'єкт пояснив йому, що робити і як чинити у певних ситуаціях. Так…

Створення нового підкласу на 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, ще й відповідає на запитання: «чи виконувати цю дію?» Таким чином, метод повинен запускатися перед анімацією заміни 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 рівні захисту від переходів:
    а) на рівні методів необхідноналагодженняБарDelegate;

в) відповідно блокуванням взаємодії користувача, якщо почалася хоча б одна відповідна анімація, і розблокуванням, якщо завершилися всі;
вшитий захист від обробки екшенів відразу 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

// // HUNavigationController.m // // Created by HuktoDev на 03.07.15. // #import /* Підклас NavigationController-a, надає механізм централізованого захисту всіх контролерів від подвійних переходів і від мультитача. Механізм захисту від переходів реалізований, як повне блокування користувальницької взаємодії (event-ів у додатку) під час перехідних станів, таких як а) анімації navigation-бара б) анімованих переходів між уявленнями На випадок не спрацьовування деблокування - акуратно вшитий механізм розблокування екрану за таймером після блокування якщо відбудеться тривале блокування - відсилає репорт в Google аналітікс * / # Warning Зробити свій особливий тип логів для навігейшена @interface HUNavigationController : UINavigationController @property (assign, nonatomic) BOOL isBarPopProcessing; @property (assign, nonatomic) BOOL isBarPushProcessing; @property (assign, nonatomic) BOOL єTransitionControllerProcessing; @property (assign, nonatomic) BOOL isBlockedInteraction; -(void)blockAllInteraction; -(BOOL)restoreAllInteraction; -(void)makeExclusiveTouchToViewController:(UIViewController*)viewController; /* метод для перевірки, чи можливо обробити кастомний пуш/поп*/ -(BOOL)isNeedNavigationBarActionBlocking; -(BOOL)isInteractionDisabled; @end 
// // HUNavigationController.m // // Created by HuktoDev на 03.07.15.// #import "HUNavigationController.h" @implementation HUNavigationController < NSTimer *timerCheckBlocking; NSDate *dateStartBlocking; >#pragma mark - UIViewController cycle - (void)viewDidLoad <[super viewDidLoad]; //ініціалізація булевих прапорів self.isBarPopProcessing = NO; self.isBarPushProcessing = NO; self.isTransitionControllerProcessing = NO; self.isBlockedInteraction = NO; //Призначення делегатів self.delegate = self; //на 7-й прошивці - делегат призначається автоматично, інакше ексепшен if(!self.navigationBar.delegate)< self.navigationBar.delegate = self; >//розлочити все, що потрібно self.navigationBar.userInteractionEnabled = YES; [self endIgnoringIf:[self isInteractionDisabled]]; //запустити таймер перевірки блокування timerCheckBlocking = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(checkBlocking) userInfo:nil repeats:YES]; > /* в iOS 7 - використовується правий свайп для повернення до попереднього контролера (блокування подібної поведінки */ -(void)viewWillAppear:(BOOL)animated < [super viewWillAppear:animated]; if ([self respondsToSelector:@selector(interactivePopGesture) ])< self.interactivePopGestureRecognizer.enabled = NO; self.interactivePopGestureRecognizer.delegate = self; >> -(void)viewWillDisappear:(BOOL)animed self.interactivePopGestureRecognizer.enabled = YES; self.interactivePopGestureRecognizer.delegate = nil; таймер [self endIgnoringIf:[self isInteractionDisabled] ] >>#pragma mark - UIGestureRecognizerDelegate - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer < return NO-> #pragma mark -UINavigationController segues methods wrappers - -(UIViewController *)popViewControllerAnimated:(BOOL)animated< if([self isNeedNavigationBarActionBlocking])< return nil; >else < return [super popViewControllerAnimated:animated]; >> - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated< if([self isNeedNavigationBarActionBlocking])< return [NSArray array]; >else < return [super popToViewController:viewController animated:animated]; >> -(NSArray *)popToRootViewControllerAnimated:(BOOL)animated< if([self isNeedNavigationBarActionBlocking])< return [NSArray array]; >else < return [super popToRootViewControllerAnimated:animated]; >> -(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated < if(![self isNeedNavigationBarActionBlocking])< [super pushViewController:viewController animated:animated]; >> #pragma mark - UINavigationBarDelegate -(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item < //тільки для кнопок back bar button (автоматичний перехід) //якщо кастомний баттон - то перехід вже починає і тоді потрібно перевірити, і повернути YES //Блокуємо множинні виклики методів делегата navigation bar-a if(self.isBarPopProcessing || self.isBarPushProcessing)< return NO; >self.isBarPopProcessing = YES; //для переходів за замовчуванням (наприклад, за допомогою back) (у тих, у кого самостійно не запускається popViewControllerAnimated, відповідно ще не заблоковані інтерекшени if(! self.isTransitionControllerProcessing && ! self.isBlockedInteraction) < [super popViewControllerAnimated:YES] >; [self blockAllInteraction], return YES; restoreAllInteraction]; >- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item <//якщо анімація бару ще йде - не робити дію (для кастомних кнопок, що виконують segue/push - попередньо завжди в коді контролера повинна стояти перевірка //захист від множинних викликів методу if(self.isBarPopProcessing || self.isBarPushProcessing)< return NO ; >self.isBarPushProcessing = YES; [self blockAllInteraction]; self.isBarPushProcessing = NO; [self restoreAllInteraction];Аналогічно блокування при початку анімації, розблокування при закінченні*/ - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated < NSLog(@"WILL SHOW"); self.isTransitionControllerProcessing = YES; //місце, де можна централізовано викликати загальний для кожного контролера код ініціалізації (загальний viewWillAppear:) [self makeExclusiveTouchToViewController:viewController]; [self blockAllInteraction]; >- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated < NSLog(@"DID SHOW"); //Місце, де можна централізовано викликати загальний для кожного контролера код ініціалізації (загальний viewDidAppear:) self.isTransitionControllerProcessing = NO; [self restoreAllInteraction]; >#pragma mark - User Interaction manage /* блокатор/деблокатор */ -(void)blockAllInteraction < NSLog(@"TRY TO BLOCK ALL INTERACTION"); //якщо ще не заблоковано взаємодію - скасувати попередній таймер розблокування, заблокувати, і запустити новий 2-секундний таймер на деблокування if(! [self isInteractionDisabled])< NSLog(@"ATTEMPT SUCCESS BLOCK INTERACTION"); [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(restoreAllInteraction) object:nil]; [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; self.isBlockedInteraction = YES; [self checkBlocking]; [self performSelector:@selector(restoreAllInteraction) withObject:nil afterDelay:2.f]; >> -(BOOL)restoreAllInteraction< NSLog(@"TRY TO RESTORE ALL INTERACTION"); //скасувати попередній реквест на відновлення, спробувати відновити взаємодію, якщо жодна анімація більше не йде і хоч що-небудь є заблокованим //якщо не вдається - запустити таймер на майбутнє на відновлення (рекурсивно запускатиметься, поки не виконаються умови [NSObject cancelPreviousPerformRequestsWithTarget: selfselector:@selector(restoreAllInteraction) object:nil]; BOOL isRestoreSuccess = [self endIgnoringIf:(! self.isBarPopProcessing && ! self.isBarPushProcessing && [self isInteractionDisabled] && ! self.isTransitionControllerProcessing)]; if(isRestoreSuccess)< NSLog(@"ATTEMPT SUCCESS RESTORE INTERACTION"); [self checkBlocking]; return YES; >else < if([self isInteractionDisabled] )< [self performSelector:@selector(restoreAllInteraction) withObject:nil afterDelay:2.f]; >return NO; > > /* кондишени, 1) публічний, щоб перевірити, чи можна виконувати перехід у коді контролера*/ -(BOOL)isNeedNavigationBarActionBlocking < return (self.isBarPopProcessing || self.isBarPushProcessing || self.isTransitionControllerProcessing) >/* метод, основна точка доступу до інформації про поточний стан користувальницької взаємодії*/ -(BOOL)isInteractionDisabled < return (self.isBlockedInteraction ||[UIApplication sharedApplication].isIgnoringInteractionEvents); >/* перестаємо блокувати user interaction, якщо умова виконується*/ -(BOOL)endIgnoringIf:(BOOL)condition< if(condition)< [[[UIApplication sharedApplication] endIgnoringInteractionEvents]; self.isBlockedInteraction = NO; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(restoreAllInteraction) object:nil]; return YES; >else < return NO; >> #pragma mark - Multitouch block /* метод відключення мультитача*/ -(void)makeExclusiveTouchToViewController:(UIViewController*)viewController <[Utility makeExclusiveTouchToSubviews:viewController.view]; >#pragma mark - Google analytics reports /* постійно перевіряти, якщо інтервал блокування триває більше 10 секунд - надіслати репорт в гугл аналітікс*/ -(void)checkBlocking< if(!dateStartBlocking && [self isBlockedInteraction])< dateStartBlocking = [ ]; >else if(dateStartBlocking && ! [self isBlockedInteraction])< NSLog(@"interface was blocked on %.1f seconds", -([dateStartBlocking timeIntervalSinceNow])); dateStartBlocking = nil; >elseif(dateStartBlocking && [self isBlockedInteraction]) < NSTimeInterval intervalBlocking = [dateStartBlocking timeIntervalSinceNow]; isValid])< [timerCheckBlocking invalidate]; >dateStartBlocking = nil; > > -void)sendInteractionBlockingReport < NSLog(@"ERROR INTERFACE LOCK . "); nvars : \nisBarPopProcessing %i \nisBarPushProcessing %i \nisTransitionControllerProcessing %i \nisBlockedInteraction %i", self.visibleViewController, self.isBarPopProcessing, self.isBarPushProcessing, self.isTransitionControllerProcessing, self.isBlocked sendReportNonFailExceptionWithDescription:descriptionLockReport]; [timerCheckBlocking invalidate]; 

[З пісочниці] UINavigationController і з чим його їдять: базові принципи, субкласування, захист від подвійних переходів та багато іншого 20.07.2015 14:48 - Kozak

UINavigationController 自带了一个工具栏,通过 [self.navigationController setToolbarHidden:NO]; 来显示工具栏,工具栏中的内容可以通过viewController来设置,显示的顺序和设置的NSArray中存放的顺序一致,每一个UIBarButtonItem对象都可以设定点击事件,可以使用系统提供的很多常用风格的对象,也可以根据需求进行自定义,下面举例使用系统提供的样式.

// 1 显示工具条 [self.navigationController setToolbarHidden:NO]; // 2 创建四个UIBarButtonItem UIBarButtonItem *itemOne = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:nil action:nil]; UIBarButtonItem *itemTwo = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:nil action:nil]; UIBarButtonItem *itemThree = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:nil action:nil]; UIBarButtonItem *itemFour = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:nil action:nil]; // 间隙 UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; // 3 添加到toolbarItems vc.toolbarItems = @[itemOne,flexibleItem,itemTwo,flexibleItem,itemThree,flexibleItem,itemFour];