0,0
рейтинг
30 августа 2015 в 18:51

Разработка → Навигация между экранами с использованием xib файлов

При чтении различных исходников сталкиваюсь с проектами, реализованными с использованием xib файлов. Мне самому больше нравится использование xib, вместо storyboard (не холивара ради пишу, storyboard тоже хорош), однако часто изучение навигации между экранами превращается в пытку. И поэтому хотелось бы поделится собственным опытом.



Чем так хорош storyboard? В первую очередь тем, что он позволяет собрать всю навигацию и отобразить визуально большинство переходов.
Да, используя xib для каждого экрана мы лишаемся возможности визуально увидеть все переходы (ну и еще пары возможностей), однако мы получаем немного своих плюсов. Я не стану явно описывать плюсы и минусы использования одного и другого во избежании холивара, только лишь покажу как можно собрать всю навигацию, используя xib файлы, избавиться от лишнего использования singleton'ов, а так же как устранить связность между контроллерами.

Подход очень простой. Используем Router объекты для связи между экранами. Разделяем Router на пользовательские истории. Взаимодействуем, используя callback.

Мини демонстрация на практике


  • Экран с таблицей и кнопкой добавления записи
  • Экран создания записи
  • Экран детального просмотра записи

Первоначальная настройка


Создадим роутер и отобразим пустой экран
RXAppDelegate.m
#import "RXAppDelegate.h"
#import "RXRouter.h"


@implementation RXAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = [[RXRouter alloc] initRouter];
    [self.window makeKeyAndVisible];
    
    return YES;
}

@end
RXRouter.h
#import <UIKit/UIKit.h>


@interface RXRouter : UINavigationController

- (instancetype)initRouter;

@end
RXRouter.m
#import "RXRouter.h"


@implementation RXRouter

- (instancetype)initRouter {
    UIViewController *rootViewController = [self createRootViewController];
    self = [super initWithRootViewController:rootViewController];
    if (self != nil) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    return self;
}

- (UIViewController *)createRootViewController {
    UIViewController *controller = [[UIViewController alloc] init];
    return controller;
}

@end

Реализация


Реализуем создание контроллера, который будет показывать записи. Так же сразу свяжем этот экран с другими экранами и взаимодействие между ними.
RXRoute.m
#import "RXRouter.h"
#import "RXNoteListViewController.h"
#import "RXCreateNoteViewController.h"
#import "RXDetailNoteViewController.h"


@implementation RXRouter

- (instancetype)initRouter {
    UIViewController *rootViewController = [self createRootViewController];
    self = [super initWithRootViewController:rootViewController];
    if (self != nil) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    return self;
}

- (UIViewController *)createRootViewController {
    RXNoteListViewController *noteListController = [[RXNoteListViewController alloc] init];
    __weak RXRouter *weakSelf = self;
    __weak RXNoteListViewController *weakNoteListController = noteListController;
    noteListController.createNoteBlock = ^{
        RXCreateNoteViewController *createNoteViewController = [weakSelf createNoteViewController];
        createNoteViewController.createNoteBlock = ^(RXNote *note){
            [weakNoteListController addNote:note];
            [weakSelf popViewControllerAnimated:YES];
        };
        [weakSelf pushViewController:createNoteViewController animated:YES];
    };
    noteListController.detailNoteBlock = ^(RXNote *note){
        RXDetailNoteViewController *detailNoteViewController = [weakSelf createDetailNoteViewControllerWithNote:note];
        [weakSelf pushViewController:detailNoteViewController animated:YES];
    };
    return noteListController;
}

- (RXCreateNoteViewController *)createNoteViewController {
    return [[RXCreateNoteViewController alloc] init];
}

- (RXDetailNoteViewController *)createDetailNoteViewControllerWithNote:(RXNote *)note {
    RXDetailNoteViewController *controller = [[RXDetailNoteViewController alloc] init];
    [controller showNote:note];
    return controller;
}

@end
RXNoteListViewController.h
#import <UIKit/UIKit.h>


@class RXNote;

typedef void (^RXNoteListViewControllerCreateNoteBlock)();
typedef void (^RXNoteListViewControllerDetailNoteBlock)(RXNote *note);


@interface RXNoteListViewController : UIViewController

@property (copy, nonatomic) RXNoteListViewControllerCreateNoteBlock createNoteBlock;
@property (copy, nonatomic) RXNoteListViewControllerDetailNoteBlock detailNoteBlock;

- (void)addNote:(RXNote *)note;

@end
RXCreateNoteViewController.h
#import <UIKit/UIKit.h>


@class RXNote;
typedef void (^RXCreateNoteViewControllerCreateNoteBlock)(RXNote *note);


@interface RXCreateNoteViewController : UIViewController

@property (copy, nonatomic) RXCreateNoteViewControllerCreateNoteBlock createNoteBlock;

@end
RXDetailNoteViewController.h
#import <UIKit/UIKit.h>


@class RXNote;
typedef void (^RXDetailNoteViewControllerDoneBlock)();


@interface RXDetailNoteViewController : UIViewController

- (void)showNote:(RXNote *)note;

@end

Таким образом мы сразу видим навигацию между экранами, а каждый экран ничего не знает о других экранах. Более того, используя блоки, удалось устранить необходимость информации экрана о роутере.
Так же мы можем без проблем передавать данные из экрана в экран и уменьшить использование singleton'ов.

Ссылка на проект
Мыльников Артем @ajjnix
карма
25,0
рейтинг 0,0
iOS Developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (13)

  • 0
    Эм… Если честно, то немного не понял какую проблему вы решили. Чем вам не понравился UINavigationController, который для этого вполне себе ок?

    И зачем вам было нужно делать синглтоны для передачи данных между контролерами.
    Так же мы можем без проблем передавать данные из экрана в экран и уменьшить использование singleton'ов.
    • 0
      Собрать переходы между экранами, изолировать контроллеры.
      А singleton'ы, к примеру сервисы. Здесь мы можем передать экземпляр.
      Точно так же как мы открываем сторибоард и смотрим навигацию, открываем роутер и смотрим навигацию.
    • +1
      >какую проблему

      Отделили view от логики переходов, как минимум.
  • +1
    Не очень понятно, почему логика роутера (логика переходов) определена вместе с контейнером для самих контроллеров. При таком подходе для каждого варианта контейнера придется создавать свой базовый роутер. Более логично смотрится вариант с отделением логики переходов от контроллеров в отдельном объекте. Конечно, тогда контроллер будет вынужден что-то знать о роутере, но это нормально: контроллер должен быть главным объектом и, соответственно, общим связующим звеном.
  • +2
    А, собсна, при чем тут навигация в приложении и xib'ы?
    Xib'ы — это разметка, Storyboard — это навигация. Если нравится верстать в отдельных файлах (лично я 90% верстаю в отдельных xib'ах), то вполне себе можно накидать экраны и переходы в сториборде, а затем у контроллеров в сториборде удалить корневой View.
    По умолчанию, если в сториборде у контроллера нет View, то будет выполнен поиск xib-файла с таким же названием как и у контроллера.
    Итого имеем: верстка в xib'е, навигация в storyboard, все довольны.
    • 0
      При таком подходе надо быть осторожным: можно получить ошибку Missing proxy for identifier UIStoryboardPlaceholder. Решения есть (например), но прежде всего надо быть внимательным, потому что используется не поведение по умолчанию, а некий недокументированный хак, который загружает view для контроллера из другого файла (xib'а).

      P.S. Если это не хак, а документированное поведение, — дайте знать.
    • +2
      Storybords могут быть и без навигации.
  • 0
            self.interactivePopGestureRecognizer.enabled = NO;
    

    А зачем было блочить swipe-to-back?
    • 0
      извиняюсь, кусок скопированного кода (
  • 0
    А вот этот код работает в надежде, что рантайм всегда будет создавать self заранее?
    UIViewController *rootViewController = [self createRootViewController];
    self = [super initWithRootViewController:rootViewController];
    

    Ну и про две буквы в префиксе своего класса уже сто раз проговорено — так нельзя, надо минимум три.
    • 0
      память под объект уже выделена в момент вызова, без всякой надежды
      как минимум метод initRouter вызван у объекта (который и будет self) — подробнее
      максимум возможного — [super initWithRootViewController:rootViewController] вернет nil и rootViewController будет уничтожен
      • 0
        Вопрос скорее про моветон, нежели про особенности текущей реализации рантайма.
        Скажите, пожалуйста, для вас приведённый код не «пахнёт»?
        • 0
          нет, для меня этот кусок кода приемлемый
          1) я использую возможность языка
          2) мне не нужно делать лишние манипуляции с initWithRootViewController
          3) метод createRootViewController использует self только для связи. Можно было сперва просто создать контроллер, потом вызвать initWithRootViewController и только потом связать, но если это делается проще, почему не использовать
          4) если речь про подмену объекта в init, то сам этот момент кейс «пахнет»

          это ведь не конструктор, если в пример хочется привести swift

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.