0

按下在 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");
}

rightBarButtonItemVC 用于 viewDidLoad 中,用于所有需要该项目的视图控制器:

- (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 的视图。

问题

  1. 为什么 UIViewControllerWrapperView 的实例调用我的选择器而不是我的 rightBarButtonItemVC 实例?
  2. 我怎样才能防止这种情况发生并让按钮工作?我应该为 UIViewControllerWrapperView 写一个类别吗?如果是这样,在哪里导入文件?
4

2 回答 2

1

UIViewControllerWrapperView没有调用你的选择器;您的按钮正在调用-buttonPressed:. UIViewControllerWrapperView尝试启用僵尸

看起来您RightBarButtonItemVC只是将其用作视图加载器(我假设您正在使用 ARC,否则它会泄漏)。rightBarButtonItemVC.view = nil这很昂贵,并且除非您在其他地方使用视图之前进行设置(我忘了具体是什么),否则可能会发生奇怪的事情。我在这里提出了一种从 nib 加载视图的更好方法(我不知道 Interface Builder 是否支持协议拥有的 nib,这将是理想的)。

您的代码可能崩溃的主要原因有两个:

  • 在NIB中,-buttonPressed:是接错了东西。我认为这不太可能。
  • -buttonPressed:将被发送到RightBarButtonItemVC,除了RightBarButtonItemVC没有被任何东西保留,因此它被释放。它被发送到在同一地址分配的下一个对象,该地址恰好是UIViewControllerWrapperView.

有两个简单的修复:

我更喜欢后者;从长远来看,在代码中维护 UI 似乎要容易得多,而且本地化也容易得多,因为您只需要翻译一个字符串文件。

于 2013-03-07T05:12:16.323 回答
0

直接回答:

  1. 正如@tc.的回答所建议的那样,在 xib 中定义视图和使用视图控制器 (RightBarButtonItemVC) 在 UIBarButtonItem 上定义自定义视图之间存在脱节,这在 UIViewControllerWrapperView 接收到 buttonPressed 的事实中很明显调用而不是 RightBarButtonItemVC。看起来有些东西没有被保留,尽管我不确定是什么。
  2. 以下是我实施的具体工作解决方案。我确实做了一个类别,但不是为 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 实现中传输每个元素/配置。最重要的是记下大小检查器中的框架信息,这样您就可以通过编程方式定位子视图,就像您在 .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,然后您可以从中继承所有其他 View Controller。从那里您可以考虑要包含在所有视图控制器中的其他项目。那么选择类别或子类路径就变成了一个粒度问题。

于 2013-03-14T10:33:26.213 回答