为啥其他东西通过 Interface Builder xib 调用连接到 UIBarButtonItem 的方法?
Posted
技术标签:
【中文标题】为啥其他东西通过 Interface Builder xib 调用连接到 UIBarButtonItem 的方法?【英文标题】:Why is something else calling a method connected to a UIBarButtonItem via an Interface Builder xib?为什么其他东西通过 Interface Builder xib 调用连接到 UIBarButtonItem 的方法? 【发布时间】:2013-03-07 02:43:32 【问题描述】:当按下在 rightBarButtonItem 的自定义视图中找到的按钮时,我的 ios 应用程序崩溃。使用自定义视图是因为 barButtonItem 设计需要的不仅仅是一个按钮。
这是崩溃的输出:
[UIViewControllerWrapperView buttonPressed:]: unrecognized selector sent to instance 0x7669430]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewControllerWrapperView buttonPressed:]: unrecognized selector sent to instance 0x7669430'
自定义视图定义在单独的视图控制器的 xib,RightBarButtonItemVC 中,其中也包含此链接方法:
- (IBAction)buttonPressed:(id)sender
NSLog(@"button pressed");
viewDidLoad 中使用 rightBarButtonItemVC,用于所有需要该项的视图控制器:
- (void)viewDidLoad
[super viewDidLoad];
RightBarButtonItemVC *rightBarButtonItemVC = [[RightBarButtonItemVC alloc] initWithNibName:@"RightBarButtonItemVC" bundle:nil];
UIBarButtonItem *rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightBarButtonItemVC.view];
self.navigationItem.rightBarButtonItem = rightBarButtonItem;
注意我如何将 rightBarButtonItemVC 的视图分配为 rightBarButtonItem 的视图。
问题
-
为什么 UIViewControllerWrapperView 的实例调用我的选择器而不是我的 rightBarButtonItemVC 实例?
如何防止这种情况发生并让按钮工作?我应该为 UIViewControllerWrapperView 写一个类别吗?如果是,在哪里导入文件?
【问题讨论】:
我认为你不能像那样“共享”视图控制器的视图。为什么不在 xib 而不是视图控制器中创建视图? 我考虑过,但如果我这样做了,我必须在每个 viewController 中定义目标方法,对吧? 为什么?您放在按钮上的图像与其目标方法有什么关系? 我不确定您的意思,因为我正在加载带有自定义视图的 UIBarButtonItem,而不仅仅是将图像应用到按钮。 有没有办法让这个问题变得更好?我对否决票很好奇 【参考方案1】:UIViewControllerWrapperView
没有调用你的选择器;您的按钮在UIViewControllerWrapperView
上调用-buttonPressed:
。试试enabling zombies。
看起来您只是将RightBarButtonItemVC
用作视图加载器(我假设您使用的是 ARC,否则它会泄漏)。这很昂贵,并且除非您在其他地方使用视图之前设置rightBarButtonItemVC.view = nil
,否则可能会发生奇怪的事情(我忘了到底是什么)。我提出了一种从 nib here 加载视图的更好方法(我不知道 Interface Builder 是否支持协议拥有的 nib,这将是理想的)。
您的代码可能崩溃的主要原因有两个:
在 NIB 中,-buttonPressed:
连接到错误的东西。我认为这不太可能。
-buttonPressed:
将被发送到 RightBarButtonItemVC
,但 RightBarButtonItemVC
不会被任何东西保留,因此它会被释放。它被发送到分配在同一地址的下一个对象,该地址恰好是UIViewControllerWrapperView
。
有两个简单的修复方法:
在 Interface Builder 中删除连接并使用-addTarget:action:forControlEvents:
以编程方式添加它。这需要在视图层次结构中找到按钮。
首先以编程方式创建它。
我更喜欢后者;从长远来看,在代码中维护 UI 似乎更容易远,而且本地化也更容易,因为您只需要翻译单个字符串文件。
【讨论】:
TY - 我不会说 RightBarButtonItemVC 只是一个视图加载器 - 它应该响应按钮按下并采取适当的行动。如果我只制作一个单独的 xib,那么我将失去将所有按钮响应保存在一个位置的能力。如果我正确理解了您的方法,则意味着我需要在 viewDidLoad 中为每个想要使用 UIBarButtonItem 的新视图控制器添加目标。如果你有一个包含它的解决方案,我希望避免这种情况。 @kraftydevil 然后要么需要保留 RightBarButtonItemVC,要么需要此功能的 VC 需要继承 RightBarButtonItemVC(或者您可以将类别添加到UIViewController
,但这有点恶心)。也许您可以将大多数/所有 VC 子类化,例如BaseViewController
封装常用功能?
是的,不能保留 RightBarButtonItemVC。我尝试了您的 addTarget 方法,但它仍然崩溃。我想我会尝试一个新的独立 xib 与 UIViewController 上的一个类别相结合,该类别可以为任何 UIViewController 设置 self.navigationItem.rightBarButtonItem。至少每个 viewDidLoad 中只有一行。
@kraftydevil 为什么要用笔尖来加载单个按钮?
tc。当我设置 rightBarButtonItem 时,它不应该完全是一个按钮。加载的自定义视图包含一个图像,然后是 2 个按钮。【参考方案2】:
直接回答:
-
正如@tc. 的回答所建议的那样,在 xib 中定义视图和使用视图控制器 (RightBarButtonItemVC) 在 UIBarButtonItem 上定义自定义视图之间存在脱节,这在 UIViewControllerWrapperView 的事实中很明显接收 buttonPressed 调用而不是 RightBarButtonItemVC。似乎没有保留某些内容,但我不确定是什么。
以下是我实施的具体工作解决方案。我确实做了一个类别,但没有像前面提到的那样为 UIViewControllerWrapperView。
具体解决方案:
首先在 UIViewController 上创建一个 Objective-C 类别 BarButtonItemLoader:
@interface UIViewController (BarButtonItemLoader)
在UIViewController+BarButtonItemLoader.h中,定义这个方法:
- (UIBarButtonItem *) rightBarButtonItem;
由于您无法跟踪类别中的状态,因此在 AppDelegate.h 中定义一个 UIBarButtonItem:
@property (strong, nonatomic) UIBarButtonItem *rightBarButtonItem;
接下来,通过从 AppDelegate 延迟加载 rightBarButtonItem 开始实现类别的 rightBarButtonItem 方法(不要忘记#import "AppDelegate.h")。这确保了 AppDelegate 中只会创建和保留一个 rightBarButtonItem:
- (UIBarButtonItem *) rightBarButtonItem
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.rightBarButtonItem)
//create a rightBarButtonItem (see below)
return appDelegate.rightBarButtonItem;
开始组装将设置为 rightBarButtonItem 的 UIView/UIBarButtonItem。从旧的 Interface Builder / xib 实现中传输每个元素/配置。最重要的是记下 Size 检查器中的框架信息,这样您就可以通过编程方式定位子视图,就像您在 .xib 文件中手动定位它们一样。
- (UIBarButtonItem *) rightBarButtonItem
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.rightBarButtonItem)
UIView *rightBarView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 264, 44)];
UIBarButtonItem *rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightBarView];
UIImageView *textHeader = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"textHeader.png"]];
textHeader.frame = CGRectMake(2, 14, 114, 20);
[rightBarView addSubview:textHeader];
UIButton *button1 = [[UIButton alloc] initWithFrame:CGRectMake(100, 2, 70, 44)];
[button1 setImage:[UIImage imageNamed:@"button1.png"] forState:UIControlStateNormal];
[button1 setImage:[UIImage imageNamed:@"button1Highlighted.png"] forState:UIControlStateHighlighted];
[button1 addTarget:self action:@selector(button1Pressed) forControlEvents:UIControlEventTouchUpInside];
[rightBarView addSubview:button1];
UIButton *button2 = [[UIButton alloc] initWithFrame:CGRectMake(194, 2, 70, 44)];
[button2 setImage:[UIImage imageNamed:@"button2.png"] forState:UIControlStateNormal];
[button2 setImage:[UIImage imageNamed:@"button2Highlighted.png"] forState:UIControlStateHighlighted];
[button2 addTarget:self action:@selector(button2Pressed) forControlEvents:UIControlEventTouchUpInside];
[rightBarView addSubview:button2];
appDelegate.rightBarButtonItem = rightBarButtonItem;
return appDelegate.rightBarButtonItem;
最后,实现 UIViewController+BarButtonItemLoader.m 中的 buttonXPressed 方法来达到你的目的:
- (void) button1Pressed
NSLog(@"button1 Pressed");
- (void) button2Pressed
NSLog(@"button2 Pressed");
...
通过将此代码添加到任何 UIViewController 或其子类来使用该类别:
#import "UIViewController+BarButtonItemLoader.h"
- (void)viewDidLoad
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = [self rightBarButtonItem];
总结
这种方法允许您将 UIBarButtonItem 动态添加到任何 UIViewController。缺点是你必须将上面的代码添加到你创建的所有 UIViewControllers 中。
另一种选择
如果您想进一步封装 UIBarButtonItems(或其他任何内容)的添加,避免在每个 View Controller 中添加代码,您应该创建一个 BaseViewController,然后您可以从该 BaseViewController 子类化所有其他 View Controller。从那里您可以考虑要包含在所有视图控制器中的其他项目。选择 Category 或 Subclass 路径就变成了一个粒度问题。
【讨论】:
我也鼓励大家支持@tc. 的回答,因为这让我找到了解决方案。以上是关于为啥其他东西通过 Interface Builder xib 调用连接到 UIBarButtonItem 的方法?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Interface Builder 中向 NSMenu 添加其他项目?
为啥 []string 不能在 golang 中转换为 []interface [重复]
为啥 TS 没有从 Interface 获得 React 状态断言?