ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C часть 2

Top Bar

Было бы грубым нарушением рассказать о работе UINavigationController и забыть его двух очень важных элементах (Top Bar и Bottom Bar). Эти элементы используются для вывода информации. Чаще всего информации о том контроллере на котором находится пользователь или (и) логотипов программы или компании, которая разработала эту программу. На них можно добавлять кнопки навигации или (и) любые другие элементы интерфейса. Поскольку тема эта очень большая, сегодня я расскажу только о Top Bar.
В предыдущих примерах мы уже использовали такое полезное свойство Top Bar как title. Давайте посмотрим как мы это делали. Чтобы не создавать новый проект я предлагаю использовать код с нашего первого знакомства с UINavigationController.

Если вы загляните в метод viewDidLoad класса RootViewController то увидите следующую строчку: self.title = @»Students»;.  Эта часть кода отвечает за установку надписи на Top Bar. Важным моментом является тот факт, что если надпись установлена — на контроллер, который переходит пользователь Xcode сам добавляет кнопку «назад» и устанавливает ей надпись которую вы прописали в родительском контроллере.

 

В случае, если родительский контроллер не имеет надписи — кнопка добавлена не будет. Вам прийдется самостоятельно ее добавить. Как это сделать будет описано ниже.

Надпись кнопки «назад» возможно изменить, но для этого прийдется проделать некоторую «магию». В первую очередь в классеRootViewController следует перенести с метода viewDidLoad в viewWillAppear ту часть кода, которая отвечает за надпись.

 

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    self.students = [NSArray arrayWithObjects:@"Tom", @"Bill", @"Tom", @"Joe", @"Tom", nil];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.title = @"Students";
}</code>

 

Затем следует изменить метод viewDidLoad класса MyClassViewController:

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    NSArray* viewCtrlers = self.navigationController.viewControllers;
    UIViewController* prevCtrler = [viewCtrlers objectAtIndex:[viewCtrlers count]-2];
    prevCtrler.title = @"Back";

    name.text = selectedStudent;
}</code>

 

Теперь можно полюбоваться результатом проделанной работы:

 

Что же происходит в этом коде? В классе RootViewController мы устанавливаем надпись контроллеру навигации при каждом переходе на этот контроллер, а не в момент его первого отображения. Затем, в классе MyClassViewController мы получаем массив всех контроллеров представления нашего контроллера навигации, обращаемся к тому с которого вызван текущий контроллер и изменяем ему надпись. Фактически, мы не меняем надпись кнопки, у нас нету к ней доступа, но есть доступ к тому котроллеру представления с которого мы вызвали текущий. Этим доступом мы и воспользовались.

Есть еще один способ изменить кнопку «назад». Мы может ее просто подменить. Но перед тем как сделать это, я предлагаю изменить источник данных для таблицы. Вскоре вы поймете зачем я это предложил. После изменений метод viewDidLoad класаRootViewController будет выглядеть так:

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    self.students = [NSArray arrayWithObjects:
                     @"BackText", 
                     @"CustomBack", 
                     @"HideBack", 
                     @"Magic", 
                     @"Segment", 
                     @"CustomTitleBar", 
                     @"CustomTitleBar&amp;Promt",
                     @"HideTopBar",
                     nil];
}</code>

 

Теперь наша таблица содержит не просто имена, а обозначения того, что будет происходить при нажатии на конкретную ячейку. Со студентами наша программа никакой связи не имеет, поэтому, предлагаю изменить заголовок со Students на TopBar (я надеюсь, вы не забыли, где мы устанавливали заголовок).

Чтобы не применять все методы с которыми мы решили экспериментировать я предлагаю их выполнять отборочно. Поможет нам в этом условный оператор. В классе MyClassViewController измените метод viewDidLoad как показано ниже.

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    name.text = selectedStudent;

    if ([selectedStudent isEqualToString:@"BackText"]) {
        NSArray* viewCtrlers = self.navigationController.viewControllers;
        UIViewController* prevCtrler = [viewCtrlers objectAtIndex:[viewCtrlers count]-2];
        prevCtrler.title = @"Back";
    }
}</code>

 

Теперь надпись кнопки «назад» будет изменятся только в том случае, если мы нажали на ячейку таблицы со значением BackText.

Для продолжения экспериментов с кнопкой добавим в проект картинку, которая будет отображена на этой кнопке. Эту и другие картинки, который нам пригодятся в дальнейшем можно взять здесь. Скачайте и добавьте их в проект.

Продолжим добавлять свою кнопку «назад». Для этого в условный оператор метода viewDidLoad классаMyClassViewController добавим еще одну ветку условия:

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    name.text = selectedStudent;

    if ([selectedStudent isEqualToString:@"BackText"]) {
        NSArray* viewCtrlers = self.navigationController.viewControllers;
        UIViewController* prevCtrler = [viewCtrlers objectAtIndex:[viewCtrlers count]-2];
        prevCtrler.title = @"Back";
    } else if ([selectedStudent isEqualToString:@"CustomBack"]) {
        UIBarButtonItem *backButton = [[[UIBarButtonItem alloc]  
                                        initWithImage:[UIImage imageNamed:@"Back.png"]  
                                        style:UIBarButtonItemStylePlain  
                                        target:self  
                                        action:@selector(pressBack)] autorelease];  
        self.navigationItem.leftBarButtonItem = backButton;
    }
}</code>

 

Если вы запустите программу на выполнение и выберите в таблице ячейку со значением CustomBack то заметите, что привычная кнопка «назад» приобрела совершенно другой вид. Но при ее нажатии программа будет «падать». Связано это с тем, что при ее нажатии мы пытаемся вызвать метод pressBack, который в код не добавили. Предлагаю добавить его после метода viewDidLoad.

<code data-result="[object Object]">- (IBAction)pressBack {
	[self.navigationController popViewControllerAnimated:YES];
}</code>

 

Этот метод содержит всего одну строчку, которая выполняет переход на контроллер с которого был вызван текущий контроллер. В качестве параметра этот метод принимает логическое значение, таким образом вы указываете будет совершен анимированный переход или нет. Я предлагаю объявить этот метод в интерфейсе класса MyClassViewController поскольку он нам пригодиться для еще одной кнопки.

Как и большинство элементов интерфейса, кнопку «назад» можно прятать. В следующей ветке условного оператора демонстрируется эта возможность.

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    name.text = selectedStudent;

    if ([selectedStudent isEqualToString:@"BackText"]) {
        NSArray* viewCtrlers = self.navigationController.viewControllers;
        UIViewController* prevCtrler = [viewCtrlers objectAtIndex:[viewCtrlers count]-2];
        prevCtrler.title = @"Back";
    } else if ([selectedStudent isEqualToString:@"CustomBack"]) {
        UIBarButtonItem *backButton = [[[UIBarButtonItem alloc]  
                                        initWithImage:[UIImage imageNamed:@"Back.png"]  
                                        style:UIBarButtonItemStylePlain  
                                        target:self  
                                        action:@selector(pressBack)] autorelease];  
        self.navigationItem.leftBarButtonItem = backButton;
    } else if ([selectedStudent isEqualToString:@"HideBack"]) {
        self.navigationItem.hidesBackButton = YES;
    }
}</code>

Все хорошо, но теперь у нас нет возможности вернуться к предыдущему контроллеру представления. Для исправления этой ситуации я и предложил вам добавить метод pressBack в интерфейс класса MyClassViewController. Теперь просто добавьте в xib-файл кнопку и свяжите ее с этим методом. Как это делается я уже рассказывал в уроке Знакомство с GUI.

В правой части Top Bar Xcode не размещает автоматически никаких кнопок, но мы это можем сделать сами. Выполняется это так же как и с кастомной (нашей) кнопкой «назад». Разница лишь в последней строчке, где мы указываем, что это будет правая кнопка, а не кнопка «назад».

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    name.text = selectedStudent;

    if ([selectedStudent isEqualToString:@"BackText"]) {
        NSArray* viewCtrlers = self.navigationController.viewControllers;
        UIViewController* prevCtrler = [viewCtrlers objectAtIndex:[viewCtrlers count]-2];
        prevCtrler.title = @"Back";
    } else if ([selectedStudent isEqualToString:@"CustomBack"]) {
        UIBarButtonItem *backButton = [[[UIBarButtonItem alloc]  
                                        initWithImage:[UIImage imageNamed:@"Back.png"]  
                                        style:UIBarButtonItemStylePlain  
                                        target:self  
                                        action:@selector(pressBack)] autorelease];  
        self.navigationItem.leftBarButtonItem = backButton;
    } else if ([selectedStudent isEqualToString:@"HideBack"]) {
        self.navigationItem.hidesBackButton = YES;
    } else if ([selectedStudent isEqualToString:@"Magic"]) {
        UIBarButtonItem *addButtonMagic = [[[UIBarButtonItem alloc] initWithTitle:@"Magic"
                                                                           style:UIBarButtonItemStyleBordered
                                                                          target:self
                                                                          action:@selector(doMagic)] autorelease];
        self.navigationItem.rightBarButtonItem = addButtonMagic;
    }
}</code>

 

Как и в случае с кастомной кнопкой «назад» — приложение будет «падать» при нажатии на эту кнопку. Это произойдет потому, что мы не добавили метод, который вызывается при нажатии на нее. Добавьте после метода pressBack.

<code data-result="[object Object]">- (void)doMagic {
    name.text = @"This is Magic!!!";
}</code>

 

В этом примере я добавил обычную кнопку, но мог бы сделать и вот так:

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    name.text = selectedStudent;

    if ([selectedStudent isEqualToString:@"BackText"]) {
        NSArray* viewCtrlers = self.navigationController.viewControllers;
        UIViewController* prevCtrler = [viewCtrlers objectAtIndex:[viewCtrlers count]-2];
        prevCtrler.title = @"Back";
    } else if ([selectedStudent isEqualToString:@"CustomBack"]) {
        UIBarButtonItem *backButton = [[[UIBarButtonItem alloc]  
                                        initWithImage:[UIImage imageNamed:@"Back.png"]  
                                        style:UIBarButtonItemStylePlain  
                                        target:self  
                                        action:@selector(pressBack)] autorelease];  
        self.navigationItem.leftBarButtonItem = backButton;
    } else if ([selectedStudent isEqualToString:@"HideBack"]) {
        self.navigationItem.hidesBackButton = YES;
    } else if ([selectedStudent isEqualToString:@"Magic"]) {
        UIBarButtonItem *addButtonMagic = [[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Back.png"] 
                                                                            style:UIBarButtonItemStylePlain 
                                                                           target:self
                                                                           action:@selector(doMagic)] autorelease];
        self.navigationItem.rightBarButtonItem = addButtonMagic;
    }
}</code>

 

Как видите, мы можем добавить практически любую кнопку в правую часть Top Bar. Более того, мы можем добавить группу кнопок (сегмент):

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    name.text = selectedStudent;

    if ([selectedStudent isEqualToString:@"BackText"]) {
        NSArray* viewCtrlers = self.navigationController.viewControllers;
        UIViewController* prevCtrler = [viewCtrlers objectAtIndex:[viewCtrlers count]-2];
        prevCtrler.title = @"Back";
    } else if ([selectedStudent isEqualToString:@"CustomBack"]) {
        UIBarButtonItem *backButton = [[[UIBarButtonItem alloc]  
                                        initWithImage:[UIImage imageNamed:@"Back.png"]  
                                        style:UIBarButtonItemStylePlain  
                                        target:self  
                                        action:@selector(pressBack)] autorelease];  
        self.navigationItem.leftBarButtonItem = backButton;
    } else if ([selectedStudent isEqualToString:@"HideBack"]) {
        self.navigationItem.hidesBackButton = YES;
    } else if ([selectedStudent isEqualToString:@"Magic"]) {
        UIBarButtonItem *addButtonMagic = [[[UIBarButtonItem alloc] initWithTitle:@"Magic"
                                                                           style:UIBarButtonItemStyleBordered
                                                                           target:self
                                                                           action:@selector(doMagic)] autorelease];
        self.navigationItem.rightBarButtonItem = addButtonMagic;
    } else if ([selectedStudent isEqualToString:@"Segment"]) {
        UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:
                                                [NSArray arrayWithObjects:
                                                [UIImage imageNamed:@"up.png"],
                                                [UIImage imageNamed:@"down.png"],
                                                nil]];
        [segmentedControl addTarget:self action:@selector(segmentAction:) forControlEvents:UIControlEventValueChanged];
        segmentedControl.frame = CGRectMake(0.0f, 0.0f, 90.0f, 30.0f);
        segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
        segmentedControl.momentary = YES;

        UIBarButtonItem *segmentBarItem = [[UIBarButtonItem alloc] initWithCustomView:segmentedControl];
        [segmentedControl release];

        self.navigationItem.rightBarButtonItem = segmentBarItem;
        [segmentBarItem release];
    }
}</code>

 

Для сегмента мы создали отдельный метод, добавьте этот метод после doMagic.

<code data-result="[object Object]">- (IBAction)segmentAction:(id)sender {
    UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
    name.text = [NSString stringWithFormat:@"Segment clicked: %d", segmentedControl.selectedSegmentIndex];
}</code>

 

Кроме доступа к кнопкам — мы имеем возможность подменить представление в которое до этого вписывали только текст (title). В своем примере я туда вставил сегмент похожий на тот, который мы уже использовали. Чтобы лишний раз не дублировать в статье код — я буду добавлять только отдельные ветки условного оператора:

<code data-result="[object Object]">} else if ([selectedStudent isEqualToString:@"CustomTitleBar"]) {
    NSArray *segmentTextContent = [NSArray arrayWithObjects:
                                   @"Image",
                                   @"Text",
                                   @"Video",
                                   nil];
    UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:segmentTextContent];
    segmentedControl.selectedSegmentIndex = 0;
    segmentedControl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
    segmentedControl.frame = CGRectMake(0.0f, 0.0f, 400.0f, 30.0f);
    [segmentedControl addTarget:self action:@selector(segmentAction:) forControlEvents:UIControlEventValueChanged];

    self.navigationItem.titleView = segmentedControl;
    [segmentedControl release];
}</code>

 

Дополнительную красоту вашему Top Bar придаст элемент prompt. Он используется в тех случаях, если titleView уже занят, но при этом остается необходимость выводить текст в Top Bar.

<code data-result="[object Object]">} else if ([selectedStudent isEqualToString:@"CustomTitleBar&amp;Promt"]) {
    self.navigationItem.prompt = @"Please select the appropriate media type:";

    NSArray *segmentTextContent = [NSArray arrayWithObjects:
                                   @"Image",
                                   @"Text",
                                   @"Video",
                                   nil];
    UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:segmentTextContent];
    segmentedControl.selectedSegmentIndex = 0;
    segmentedControl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
    segmentedControl.frame = CGRectMake(0.0f, 0.0f, 400.0f, 30.0f);
    [segmentedControl addTarget:self action:@selector(segmentAction:) forControlEvents:UIControlEventValueChanged];

    self.navigationItem.titleView = segmentedControl;
    [segmentedControl release];        
}</code>

Кроме всего прочего, у нас есть возможность прятать Top Bar:

<code data-result="[object Object]">} else if ([selectedStudent isEqualToString:@"HideTopBar"]) {
    self.navigationController.navigationBar.hidden = YES;
}</code>

 

Но в этом деле важно помнить один момент. Что контроллер навигации у нас один на все приложение и если мы его спрятали в каком-то контроллере представления — он будет спрятан во всем приложении. Чтобы его показать присвойте свойству hidden значение NO. Я предлагаю это сделать в методе viewWillAppear класса RootViewController.

Это основные функции Top Bar, которые чаще всего используются. Исходный код можно скачать здесь.