简体   繁体   English

为什么其他调用方法通过接口构建器xib连接到UIBarButtonItem的东西?

[英]Why is something else calling a method connected to a UIBarButtonItem via an Interface Builder xib?

My iOS app crashes when pressing a button found in a custom view for the rightBarButtonItem. 当按下rightBarButtonItem的自定义视图中找到的按钮时,我的iOS应用程序崩溃。 A custom view is used because the barButtonItem design requires more than just a button. 使用自定义视图是因为barButtonItem设计需要的不仅仅是按钮。

Here is the output of the crash: 这是崩溃的输出:

[UIViewControllerWrapperView buttonPressed:]: unrecognized selector sent to instance 0x7669430]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewControllerWrapperView buttonPressed:]: unrecognized selector sent to instance 0x7669430'

The custom view is defined in a separate view controller's xib, RightBarButtonItemVC, which also contains this linked method: 定制视图是在单独的视图控制器的xib RightBarButtonItemVC中定义的,它还包含以下链接方法:

- (IBAction)buttonPressed:(id)sender {
    NSLog(@"button pressed");
}

The rightBarButtonItemVC is used in viewDidLoad, for all views controllers that need the item: 在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;
}

Notice how I am assigning rightBarButtonItemVC's view as the view for rightBarButtonItem. 请注意,我是如何将rightBarButtonItemVC的视图分配为rightBarButtonItem的视图的。

Question

  1. Why is an instance of UIViewControllerWrapperView calling my selector instead of my instance of rightBarButtonItemVC? 为什么UIViewControllerWrapperView的实例调用我的选择器而不是rightBarButtonItemVC的实例?
  2. How can I prevent this from happening and get the button to work? 如何防止这种情况发生并让按钮起作用? Should I write a category for UIViewControllerWrapperView? 我应该为UIViewControllerWrapperView写一个类别吗? If so, where to import the file? 如果是这样,该文件导入哪里?

UIViewControllerWrapperView is not calling your selector; UIViewControllerWrapperView没有调用您的选择器; your button is calling -buttonPressed: on the UIViewControllerWrapperView . 您的按钮正在UIViewControllerWrapperView上调用-buttonPressed: Try enabling zombies . 尝试启用僵尸

It looks like you're using RightBarButtonItemVC simply as a view loader (I assume you're using ARC, or it would leak). 看起来您只是将RightBarButtonItemVC用作视图加载器(我假设您使用的是ARC,否则它将泄漏)。 This is expensive, and strange things can happen unless you set rightBarButtonItemVC.view = nil before using the view elsewhere (I forget exactly what). 这很昂贵,除非您在其他地方使用视图之前设置rightBarButtonItemVC.view = nil ,否则可能会发生奇怪的事情(我确切地忘记了什么)。 I present a better way to load views from nibs here (I don't know if Interface Builder supports nibs owned by a protocol, which would be ideal). 在这里提供了一种从笔尖加载视图的更好方法(我不知道Interface Builder是否支持协议拥有的笔尖,这是理想的选择)。

There are two main reasons your code might be crashing: 您的代码可能崩溃的主要原因有两个:

  • In the NIB, -buttonPressed: is connected to the wrong thing. 在NIB中, -buttonPressed:连接到错误的对象。 I don't think this is likely. 我认为这不太可能。
  • -buttonPressed: would get sent to the RightBarButtonItemVC , except the RightBarButtonItemVC is not retained by anything so it gets dealloced. -buttonPressed:将被发送到RightBarButtonItemVC ,除了RightBarButtonItemVC没有被任何东西保留,因此将其取消分配。 It gets sent to the next object that is allocated at the same address, which happens to be a UIViewControllerWrapperView . 它被发送到分配给相同地址的下一个对象,该对象恰好是UIViewControllerWrapperView

There are two easy fixes: 有两个简单的修复程序:

  • Remove the connection in Interface Builder and add it programmatically with -addTarget:action:forControlEvents: . 在Interface Builder中删除连接,并使用-addTarget:action:forControlEvents:编程方式添加该连接。 This requires finding the button in the view hierarchy. 这需要在视图层次结构中找到按钮。
  • Create it programmatically in the first place. 首先以编程方式创建它。

I prefer the latter; 我更喜欢后者。 in the long run it seems to be far easier to maintain UI in code, and is much easier to localize since you only need to translate a single strings file. 从长远来看,它似乎是容易保持代码的用户界面,并且更容易本地化,因为你只需要翻译一个字符串文件。

Direct Answers: 直接答案:

  1. As suggested by @tc.'s answer, there is a disconnect somewhere between defining the view in a xib and using a View Controller (RightBarButtonItemVC) to define a custom view on a UIBarButtonItem, which is evident in the fact that UIViewControllerWrapperView receives the buttonPressed call instead of RightBarButtonItemVC. 正如@tc。的答案所建议的,在xib中定义视图与使用视图控制器(RightBarButtonItemVC)在UIBarButtonItem上定义自定义视图之间存在某种脱节,这在以下事实中很明显:UIViewControllerWrapperView接收到buttonPressed调用而不是RightBarButtonItemVC。 It looks like something is not being retained, although I'm not sure what. 似乎没有保留某些内容,尽管我不确定。
  2. What follows is the specific working solution that I implemented. 接下来是我实施的特定工作解决方案。 I did make a category, but not for UIViewControllerWrapperView as previously mentioned. 我确实做了一个类别,但没有为UIViewControllerWrapperView做一个类别,如前所述。

Specific Solution: 具体解决方案:

First create BarButtonItemLoader, an Objective-C category on UIViewController: 首先创建BarButtonItemLoader,这是UIViewController上的Objective-C类别:

@interface UIViewController (BarButtonItemLoader)

In UIViewController+BarButtonItemLoader.h, define this method: 在UIViewController + BarButtonItemLoader.h中,定义以下方法:

- (UIBarButtonItem *) rightBarButtonItem;

Since you can't keep track of state in a category, define a UIBarButtonItem in AppDelegate.h: 由于您无法跟踪类别中的状态,因此请在AppDelegate.h中定义UIBarButtonItem:

@property (strong, nonatomic) UIBarButtonItem *rightBarButtonItem;

Next, start implementing the category's rightBarButtonItem method by lazy loading the rightBarButtonItem from the AppDelegate (don't forget to #import "AppDelegate.h"). 接下来,通过从AppDelegate延迟加载rightBarButtonItem来开始实现类别的rightBarButtonItem方法(不要忘记#import“ AppDelegate.h”)。 This ensures only one rightBarButtonItem will be created and retained in the AppDelegate: 这样可以确保仅创建一个rightBarButtonItem并将其保留在AppDelegate中:

- (UIBarButtonItem *) rightBarButtonItem {

    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    if(!appDelegate.rightBarButtonItem) {
        //create a rightBarButtonItem (see below)
    }
    return appDelegate.rightBarButtonItem;
}

Start assembling a UIView/UIBarButtonItem that will be set to the rightBarButtonItem. 开始组装将设置为rightBarButtonItem的UIView / UIBarButtonItem。 Transfer each element/configuration from the old Interface Builder / xib implementation. 从旧的Interface Builder / xib实现中传输每个元素/配置。 Most importantly take note of the frame information in the Size inspector so you can programmatically position your subviews just how you had them manually positioned in the .xib file. 最重要的是,请注意“大小”检查器中的帧信息,以便您可以通过编程方式将子视图放置到手动放置在.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;
}

Finally, implement the buttonXPressed methods in UIViewController+BarButtonItemLoader.m to your purpose: 最后,按照您的目的在UIViewController + BarButtonItemLoader.m中实现buttonXPressed方法:

- (void) button1Pressed {
      NSLog(@"button1 Pressed");
}

- (void) button2Pressed {
      NSLog(@"button2 Pressed");
}

... ...

Use the category by adding this code to any UIViewController or subclass thereof: 通过将此代码添加到任何UIViewController或其子类来使用类别:

#import "UIViewController+BarButtonItemLoader.h"

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.rightBarButtonItem = [self rightBarButtonItem];
}

Summary 摘要

This approach allows you to add a UIBarButtonItem on-the-fly to any UIViewController. 这种方法允许您即时将UIBarButtonItem添加到任何UIViewController。 The drawback is that you must add the above code to all UIViewControllers you create. 缺点是必须将以上代码添加到创建的所有UIViewController中。

Another Option 另外一个选项

If you want to further encapsulate the addition of UIBarButtonItems (or anything else), avoiding the need to add code in each View Controller, you should create a BaseViewController from which you then subclass all of your other View Controllers. 如果要进一步封装UIBarButtonItems(或其他任何东西)的添加,以避免需要在每个View Controller中添加代码,则应创建一个BaseViewController,然后从中继承所有其他View Controller。 From there you can consider other items that you want to include in all your View Controllers. 从那里,您可以考虑要包含在所有View Controller中的其他项目。 Choosing the Category or Subclass route then becomes a question of granularity. 选择类别或子类路由就成为了粒度问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM