1

In my application there is a subclass of NSWindowController. In - (void)awakeFromNib I create an instance of a NSView subclass to create a bottom placed button bar:

self.buttonBar = [[CNButtonBar alloc] initWithSize:NSMakeSize(tableViewRect.size.width-1, 25)];

This CNButtonBar thing has a method to add buttons to itself. I create two of it:

[buttonBar addButtonWithTitle:nil
                        image:[NSImage imageNamed:NSImageNameAddTemplate]
                       target:self
                       action:@selector(btnAccountAddAction:)
                    alignment:CNBarButtonAlignLeft];

[buttonBar addButtonWithTitle:nil
                        image:[NSImage imageNamed:NSImageNameRemoveTemplate]
                       target:self
                       action:@selector(btnAccountRemoveAction:)
                    alignment:CNBarButtonAlignLeft];

These two action methods are defined in the same instance of NSViewController where I send the two addButtonWithTitle:... messages.

The methods, used to set the action are defined as followed:

- (void)btnAccountAddAction:(id)sender
{
    ....
}

- (void)btnAccountRemoveAction:(id)sender
{
    ....
}

But, it doesn't work. Each time if I click one of these two buttons my application crashes throwing an exception like this one:

-[__NSArrayM btnAccountAddAction:]: unrecognized selector sent to instance 0x1001454e0

Examined this exception is absolutely correct. NSArray doesn't have such a method I want to call. But, from time to time the object that throws this exception is changing. Some times there is a __NSData object, some times a __NSDictionary and so on. It seems that the receiver is anything but my NSWindowController subclass. But the given instance ID 0x1001454e0 is the same my NSWindowController has.

It makes me want to tear my hair out! What's going wrong here...? Thanks.


UPDATE

I have to say, that both, the CNButtonBar and CNButtonBarButton are completely drawn by code. The complete method of addButtonWithTitle:image:target:action:alignment: is here (remember, this happens in CNButtonBar, a subclass of NSView (should I use NSViewController instead of it?), CNButtonBarButton is a subclass of NSButton):

- (void)addButtonWithTitle:(NSString*)title image:(NSImage*)image target:(id)target action:(SEL)action alignment:(CNBarButtonAlign)align
{
    if (self.buttons == nil)
        self.buttons = [[NSMutableArray alloc] init];

    CNButtonBarButton *button = [[CNButtonBarButton alloc] init];
    [self.buttons addObject:button];

    button.title = title;
    button.image = image;
    button.target = target;
    button.action = action;
    button.align = align;

    NSRect buttonRect;
    NSSize titleSize = NSMakeSize(0, 0);
    if (button.title.length > 0) {
        NSMutableParagraphStyle* textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
        [textStyle setAlignment: NSCenterTextAlignment];

        NSColor *textColor = [[NSColor blackColor] colorWithAlphaComponent:0.9];
        NSShadow* textShadow = [[NSShadow alloc] init];
        [textShadow setShadowColor: [NSColor whiteColor]];
        [textShadow setShadowOffset: NSMakeSize(0, -1)];
        [textShadow setShadowBlurRadius: 0];

        button.titleTextAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                      [NSFont fontWithName:@"Helvetica Neue" size:11.0], NSFontAttributeName,
                                      textShadow,                     NSShadowAttributeName,
                                      textColor,                      NSForegroundColorAttributeName,
                                      textStyle,                      NSParagraphStyleAttributeName,
                                      nil];
        titleSize = [title sizeWithAttributes:button.titleTextAttributes];
        buttonRect = NSMakeRect(0, 0, roundf(titleSize.width) + kTextInset * 2, NSHeight(self.frame) - 1);

    }

    if (button.image != nil) {
        NSSize imageSize = [image size];
        if (button.title.length == 0) {
            buttonRect = NSMakeRect(0, 0, imageSize.width + kImageInset * 2, NSHeight(self.frame) - 1);

        } else {
            buttonRect = NSMakeRect(0, 0,
                                    (imageSize.width + kImageInset * 2) + (roundf(titleSize.width) + kTextInset),
                                    NSHeight(self.frame) - 1);
        }
    }

    switch (self.align) {
        case CNButtonBarAlignNormal: {
            switch (button.align) {
                case CNButtonBarButtonAlignLeft: {
                    button.frame = NSMakeRect(offsetLeft, 0, NSWidth(buttonRect), NSHeight(buttonRect));
                    offsetLeft += 1 * [self.buttons indexOfObject:button] + NSWidth(button.frame);
                    break;
                }

                case CNButtonBarButtonAlignRight: {
                    button.frame = NSMakeRect(offsetRight - NSWidth(buttonRect), 0, NSWidth(buttonRect), NSHeight(buttonRect));
                    offsetRight -= 1 * [self.buttons indexOfObject:button] + NSWidth(button.frame);
                    break;
                }
            }
            break;
        }

        case CNButtonBarAlignCentered: {
            break;
        }
    }
    [self addSubview:button];
}

UPDATE 2

The problem is solved. After some debugging I found out that it must be a problem of ARCs auto retain/release stuff. And I'm right. But the problem was my NSViewController subclass. It was leaking because of making an instance as a local variable (without a strong pointer). Thanks to Sean D., that pointed me to the right way. (-;

Community
  • 1
  • 1
phranck
  • 11
  • 3
  • You should probably show the code for: btnAccountAddAction – Sverrisson Jul 28 '12 at 23:53
  • Its more likely the problem is in `CNButtonBar` but we can't diagnose without some code or info from `addButtonWithTitle:`. – torrey.lyons Jul 29 '12 at 00:11
  • "I create an instance of a NSView subclass to create a bottom placed button bar" -- that sounds like some *other* class, not the window controller, is creating the CNButtonBar instance. If that is the case, that other class is setting itself as the target, but the selector methods are held by the win ctrllr. That would account for the "unrecognized selector" exception -- but not for the weird assortment of called classes. – Wienke Jul 29 '12 at 04:02
  • @HannesSverrisson These methods are empty, actually. There is just a log message. I give an update. – phranck Jul 29 '12 at 10:00

1 Answers1

2

Looks like one of two things is happening. The first possibility is that your CNButtonBar object is getting released while the buttons are still around. If the buttons are still there, they'll try to send those selectors to whatever happens to occupy the memory the CNButtonBar used to be in. I'd say make sure it isn't autoreleasing itself anywhere, and that you're not accidentally autoreleasing it in the window controller's -awakeFromNib method. (If you're anything like me, that's the most likely culprit.)

The second possibility is that your -addButtonWithTitle:image:target:action:alignment: method is setting the buttons' actions but not their targets. Make sure your implementation of that method calls -setTarget: as well as -setAction: on the button.

Siobhán
  • 1,451
  • 10
  • 9
  • Actually, I'm using ARC. So I think the `CNButtonBar` will be allready alive. But I have to test it again. Target and action are both set and not `nil`. – phranck Jul 29 '12 at 08:15
  • Are you keeping a strong pointer to it somewhere? From the code you posted above, it looks like you're just creating it as a local variable, in which case ARC will autorelease it at the end of the method it's created in. – Siobhán Jul 29 '12 at 18:57
  • Yes. The example was wrong, I've fixed it now. This button bar is a property with a strong pointer. – phranck Jul 29 '12 at 20:08
  • Glad to see you got it sorted out! If my answer was helpful, would you mind marking it as accepted? Thanks! :-) – Siobhán Jul 30 '12 at 20:44