为啥其他东西通过 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 状态断言?

为啥 glColorPointer 不给三角形着色 - 以及 opengl es 的其他奇怪的东西

为啥说addjavascriptinterface漏洞

为啥我收到 Interface ... 没有构造函数(我有 Kotlin 和 java 类)?