Подтвердить что ты не робот

Рекомендации по обработке изменений в UINavigationItem контроллеров дочерних представлений в контроллере контейнера?

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

Каждый дочерний контроллер выполняет запрос к API и загружает список элементов, отображаемых в виде таблицы. На основе отображаемых элементов на навигационной панели может быть добавлена ​​кнопка, которая позволяет пользователю действовать со всеми элементами в представлении таблицы.

Поскольку UINavigationController использует только UINavigationItems своих дочерних контроллеров представления, контроллер контейнера должен обновить свой UINavigationItem, чтобы синхронизировать с UINavigationItem своих дочерних элементов.

Кажется, существуют два сценария, которые должен обрабатывать контроллер контейнера:

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

Лучшие решения, которые я придумал:

  • В методе setSelectedViewController: запросите элемент навигации выбранного контроллера представления и обновите свойства leftBarButtonItems, rightBarButtonItems и свойства заголовка контроллера UINavigationItem контейнера, чтобы он был таким же, как выбранный UINavigationItem контроллера.
  • В методе setSelectedViewController KVO на свойства leftBarButtonItems, rightBarButtonItems и свойство title выбранного контроллера UINavigationItem для просмотра и всякий раз, когда одно из этих свойств изменяет контейнерный контроллер UINavigationItem.

Это повторяющаяся проблема со многими контроллерами контейнеров, которые я написал, и я не могу найти каких-либо документированных решений этих проблем.

Какие решения люди нашли для этой проблемы?

4b9b3361

Ответ 1

Таким образом, решение, которое я в настоящее время реализует, - это создать категорию на UIViewController с помощью методов, которые позволяют вам устанавливать кнопки правого штриха элемента навигации контроллера, а затем этот контроллер отправляет уведомление, позволяющее любому, кто заботится, знать, что правая полоса элементы кнопки были изменены.

В моем контроллере контейнера я слушаю это уведомление от текущего выбранного контроллера представления и соответствующим образом обновляю элемент навигации контроллера контейнера.

В моем сценарии контейнерный контроллер переопределяет метод в категории, чтобы он мог сохранить локальную копию элементов правой кнопки панели, которые ему были назначены, и, если какие-либо уведомления подняты, он объединяет элементы правой кнопки панели с ее а затем отправляет уведомление только в том случае, если оно также находится внутри контроллера контейнера.

Вот код, который я использую.

UIViewController + ContainerNavigationItem.h

#import <UIKit/UIKit.h>

extern NSString *const UIViewControllerRightBarButtonItemsChangedNotification;

@interface UIViewController (ContainerNavigationItem)

- (void)setRightBarButtonItems:(NSArray *)rightBarButtonItems;
- (void)setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem;

@end

UIViewController + ContainerNavigationItem.m

#import "UIViewController+ContainerNavigationItem.h"

NSString *const UIViewControllerRightBarButtonItemsChangedNotification = @"UIViewControllerRightBarButtonItemsChangedNotification";

@implementation UIViewController (ContainerNavigationItem)

- (void)setRightBarButtonItems:(NSArray *)rightBarButtonItems
{
    [[self navigationItem] setRightBarButtonItems:rightBarButtonItems];

    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter postNotificationName:UIViewControllerRightBarButtonItemsChangedNotification object:self];
}

- (void)setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem
{
    if(rightBarButtonItem != nil)
        [self setRightBarButtonItems:@[ rightBarButtonItem ]];
    else
        [self setRightBarButtonItems:nil];
}

@end

ContainerController.m

- (void)setRightBarButtonItems:(NSArray *)rightBarButtonItems
{
    _rightBarButtonItems = rightBarButtonItems;

    [super setRightBarButtonItems:_rightBarButtonItems];
}

- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
    if(_selectedViewController != selectedViewController)
    {
        if(_selectedViewController != nil)
        {
            // Stop listening for right bar button item changed notification on the view controller.
            NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
            [notificationCenter removeObserver:self name:UIViewControllerRightBarButtonItemsChangedNotification object:_selectedViewController];
        }

        _selectedViewController = selectedViewController;

        if(_selectedViewController != nil)
        {
            // Listen for right bar button item changed notification on the view controller.
            NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
            [notificationCenter addObserver:self selector:@selector(_childRightBarButtonItemsChanged) name:UIViewControllerRightBarButtonItemsChangedNotification object:_selectedViewController];
        }
    }
}

- (void)_childRightBarButtonItemsChanged
{
    NSArray *childRightBarButtonItems = [[_selectedViewController navigationItem] rightBarButtonItems];

    NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray:_rightBarButtonItems];
    [rightBarButtonItems addObjectsFromArray:childRightBarButtonItems];

    [super setRightBarButtonItems:rightBarButtonItems];
}

Ответ 2

Принятый ответ работает, но он нарушает контракт на UIViewController, ваши дочерние контроллеры теперь тесно связаны с вашей настраиваемой категорией и должны использовать свои альтернативные методы для правильной работы... У меня была проблема с контейнером RBStoryboardLink, а также с собственным контроллером панели вкладок, поэтому было важно, чтобы он был инкапсулирован за пределы заданного класса контейнера, поэтому я создал класс, обладающий свойством mirrorVC (обычно устанавливается как контейнер, тот, кто будет прослушивать уведомления) и несколько методов регистрации/отмены регистрации (для элементов навигации, элементов панели инструментов, tabBarItems, так как ваши потребности подходят). Например, при регистрации/незарегистрировании для элементов панели инструментов:

static void *myContext = &myContext;
-(void)registerForToolbarItems:(UIViewController*)viewController {
    [viewController addObserver:self forKeyPath:@"toolbarItems" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:myContext];
}
-(void)unregisterForToolbarItems:(UIViewController*)viewController {
    [viewController removeObserver:self forKeyPath:@"toolbarItems" context:myContext];
}

Действие наблюдения будет обрабатывать получение новых значений и пересылку их в зеркало:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if(context == myContext) {
        id newKey = [change objectForKey:NSKeyValueChangeNewKey];
        id oldKey = [change objectForKey:NSKeyValueChangeOldKey];
        //no need to mirror if the value is the same
        if ([newKey isEqual:oldKey]) return;
        //nil values comes packaged in NSNull
        if (newKey == [NSNull null]) newKey = nil;
        //handle each of the possibly registered mirrored properties... 
        if ([keyPath isEqualToString:@"navigationItem.leftBarButtonItem"]) {
            self.mirrorVC.navigationItem.leftBarButtonItem = newKey;
        }
        //...
        //as many more properties as you need forwarded...
        else if ([keyPath isEqualToString:@"toolbarItems"]) {
            [self.mirrorVC setToolbarItems:newKey animated:YES];
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

Затем в вашем контейнере, в нужные моменты, вы регистрируете и отменяете регистрацию

[_selectedViewController unregister...]
_selectedViewController = selectedViewController;
[_selectedViewController register...]

Вы должны знать о потенциальной ловушке: не все желательные свойства соответствуют требованиям KVO, а те, которые это делают, не документированы, - поэтому они могут перестать быть или ошибочно вести себя в любое время. Свойство toolbarItems, например, нет. Я создал категорию UIViewController, основанную на этом gist (https://gist.github.com/brentdax/5938102), который позволяет KVO-уведомлениям, чтобы он работал в этом сценарии. Примечание: для UINavigationItem не было необходимости в том, что для UINavigationItem, iOS 5 ~ 7 отправляет для него соответствующие уведомления KVO, причем эта категория я получаю двойные уведомления для UINavigationItems. Он работал безупречно для toolbarItems!

Ответ 3

Считаете ли вы, что НЕ упаковываете контроллер контейнера в UINavigationController и просто добавляете UINavigationBar к вашему представлению? Затем вы можете направить навигационные элементы контроллера вашего ребенка прямо на эту панель навигации. По сути ваш контроллер контейнера заменит обычный UIViewController.

Ответ 4

Я знаю, что это старый поток, но я просто столкнулся с этой проблемой и подумал, что кто-то еще может это сделать.

Итак, для справок в будущем я сделал это следующим образом: я отправил блок в контроллер дочернего представления, который просто устанавливает правильную кнопку родительского UINavigationItem. Затем я создал UIBarButtonItem как обычно в контроллере дочерних представлений, вызывая некоторый метод в том же контроллере.

Итак, в ChildViewController.h:

// Declare block property
@property (nonatomic, copy) void (^setRightBarButtonBlock)(UIBarButtonItem*);

И в ChildViewController.m:

self.myBarButton = [[UIBarButtonItem alloc] 
    initWithTitle:@"My Title" 
    style:UIBarButtonItemStylePlain 
    target:self 
    action:@selector(didPressMyBarButton:)];

...

// Show bar button in navigation bar
// As normal, just call it with 'nil' to hide the button
if (self.setRightBarButtonBlock) {
    self.setRightBarButtonBlock(self.myBarButton);
}

...

- (void)didPressMyBarButton:(UIBarButtonItem *)sender {
    // Do something here
}

И, наконец, в ParentViewController.m

// Initialise child view controller
ChildViewController *child = [[ChildViewController alloc] init];

// Give it block for changing bar button item
__weak typeof(self) weakSelf = self;
child.setRightBarButtonBlock = ^void(UIBarButtonItem *barButtonItem) {
    [weakSelf.navigationItem setRightBarButtonItem:barButtonItem animated:YES];
};

// Finish the parent-child VC dance

Что это. Это хорошо для меня, потому что он сохраняет логику, относящуюся к UIBarButtonItem, в контроллере представления, который действительно заинтересован в этом.

Примечание. Я должен упомянуть, что я не профессионал. Это может быть просто ужасным способом сделать это. Но, похоже, все работает нормально.