假设我有一个容器控制器,该容器控制器接受UIViewControllers数组并将其布局,以便用户可以左右滑动以在它们之间切换。此容器控制器包装在导航控制器中,并成为应用程序主窗口的根视图控制器。
每个子控制器向API发出请求,并加载在表格视图中显示的项目列表。基于显示的项目,可以将一个按钮添加到导航栏,该按钮允许用户对表格视图中的所有项目进行操作。
因为UINavigationController仅使用其子视图控制器的UINavigationItems,所以容器控制器需要更新其UINavigationItem以便与其子视图的UINavigationItem同步。
似乎容器控制器需要处理两种情况:
我想出的最佳解决方案是:
这是我编写的许多容器控制器的重复出现的问题,我似乎找不到针对这些问题的任何书面解决方案。
人们对此问题有什么解决方案?
因此,我当前实现的解决方案是使用方法允许在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];
}
可接受的答案有效,但是它违反了UIViewController的约定,您的子控制器现在与您的自定义类别紧密结合,必须使用其替代方法才能正常工作...我在使用RBStoryboardLink容器以及我自己的自定义标签栏控制器时遇到了这个问题,因此将其封装在给定的容器类之外非常重要,因此我创建了一个具有mirrorVC属性的类(通常设置为容器,一个将监听通知的容器)和一些注册/注销方法(根据需要,用于navigationItems,toolbarItems,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];
}
观察动作将处理接收新值并将其转发到mirrorVC:
- (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属性不是。我基于此要点(https://gist.github.com/brentdax/5938102)创建了一个UIViewController类别,该类别为其启用了KVO通知,因此可以在这种情况下使用。注意:上面的要点对于UINavigationItem并不是必需的,iOS 5〜7会为此发送适当的KVO通知,使用该类别,我将获得UINavigationItems的双重通知。它对于toolbarItems而言完美无缺!
[您是否考虑过不将容器视图控制器包装在UINavigationController中,而只是将UINavigationBar添加到视图中?然后,您可以将子视图控制器的导航项直接推到该导航栏。本质上,您的容器视图控制器将替换普通的UIViewController。
我知道这个问题很旧,但是我认为我找到了解决这个问题的方法!
navigationItem
的UIViewController
属性在UINavigationController
头文件的类别/扩展名中定义。
此属性定义为:
open var navigationItem: UINavigationItem { get }
所以,正如我刚刚发现的那样,在我的情况下,您可以在容器视图控制器中覆盖属性:
public override var navigationItem: UINavigationItem {
return child?.navigationItem ?? super.navigationItem
}
我尝试了这种方法,现在对我有用。在包含的视图控制器上更改时,所有按钮,标题和视图都会显示并更新。
我知道这是一个老话题,但我刚遇到这个问题,并认为其他人也可能会。
因此,为了将来参考,我这样做如下:我向子视图控制器发送了一个块,该子视图控制器仅设置了父视图的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有关的逻辑,而该控制器实际上是其中的[[interested
注意:我应该说我不是专业人士。这可能只是一种糟糕的方法。但这似乎很好用。