phranck
phranck

Reputation: 11

Subclass of NSButton: call of action method crashes

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. (-;

Upvotes: 1

Views: 1156

Answers (1)

Siobhán
Siobhán

Reputation: 1452

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.

Upvotes: 2

Related Questions