2

I'm creating a NSCell subclass that draws some objects directly onto the view (using drawInRect:fromRect:operation:fraction:respectFlipped:hints:) and also draws an NSButton instance simply using NSView's addSubview: selector.

While objects drawn using the first method all draw correclty, I'm having problems with drawing the NSButton correctly. The issue is that my NSButton instances will draw in the right places, but multiple times over.

I've researched this on the internet for a while and some people suggested using a cache, but I'm not sure if this is efficient. (going an array containing buttons using a for loop will definately cause slow scrolling since I display a lot of data...)

How would you do this? Am I barking up the wrong tree?

This is the relevant code:

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{    
    NSRect _controlRect = cellFrame;

    float _Y = cellFrame.origin.y;

    NSRect _accessoryRect = NSMakeRect(_controlRect.size.width - 70.0f, _Y + 9.0f, 50.0f, 23.0f);

    _switch = [self _choiceSwitch];

    [_switch setFrame:_accessoryRect];
    [controlView addSubview:_switch];
}
SimplyKiwi
  • 12,376
  • 22
  • 105
  • 191
Pripyat
  • 2,937
  • 2
  • 35
  • 69

2 Answers2

5

Long story short: Friends don't let friends addSubview, while drawing.

This a fundamental, and not particularly well-explained aspect of managing control interfaces, but is important to come to grips with.

Let your controllers dictate the "order" of subviews, and you can sleep tight knowing that that button shouldn't get overtly mucked about (which is NOT the case if it's getting jostled around inside your custom drawing routines).

It's easy to get trapped in this alley, cause, like, hey, I added an NSImageView in my initWithFrame and everything seems to be okay… But it's just sort of not how you're supposed to do it, I guess… and when you start subclassing NSControl, etc. is when you start to realize why.

Updated: Here's a really good write up on designing custom controls with an equally as great sample project attached - which embodies the kind of code organization that can help avoid this type of issue. For example.. you'll notice in the controller class how he's keeping each button seperate, unique, and independent of other views' business…

for (int butts = 0; butts < 3; butts++) {
    NSRect buttFrame = NSMakeRect(0, butts * 10, 69, 10);
    ExampleButt *butt = [[ExampleButt alloc]initWithFrame:buttFrame];
    [mainView addSubview:butt];
}

enter image description here

Alex Gray
  • 16,007
  • 9
  • 96
  • 118
2

“Drawing” NSButton by adding its instance into the view hierarchy each time you draw the cell itself is definitely a bad idea. Instead, create an NSButtonCell and configure it up to your taste. Then, in your -[NSCell drawInteriorWithFrame:inView:] use a cell ivar to draw its appearance.

If you want to have a clickable NSButton instance in each cell of the table view, try to avoid a call to addSubview: when possible. Each time you do this, the control view may invalidate its layout and re-draw everything from scratch making some kind of a recursion in your case.

Vadim
  • 9,383
  • 7
  • 36
  • 58
  • You can find an example here: https://github.com/shpakovski/MASShortcut/blob/master/MASShortcutView.m (lines 23, 43, 44, and 123). – Vadim Jul 27 '12 at 15:56
  • I've used a cell ivar too. But that only lets me draw the cell once and the amount of data I'm showing in the table varies, so a lot of cells will not show the button. – Pripyat Jul 27 '12 at 16:00
  • Sorry, but I'm not sure what you mean. You should never add a button as a subview. And the cell ivar can be used for drawing any number of times. Just you have to configure it each time before calling `drawInteriorWithFrame:inView:`. – Vadim Jul 27 '12 at 16:50
  • Yes, I get that, but `drawInteriorWithFrame:inView:` is being called over and over again. I do adjust the frame and the buttons appear in the right places, but consequently the buttons will then be re-drawn over and over again. – Pripyat Jul 27 '12 at 19:53
  • But this is how `NSCell` class works: your view asks it to draw contents piece-by-piece in `drawRect:` which can be called really often. By the way, maybe you want to have a full-featured clickable button in each table row? – Vadim Jul 27 '12 at 21:06
  • Yes, I realized that. I also want to have a clickable button in **each** row, but it's being redrawn over and over again in **each** row. :) – Pripyat Jul 27 '12 at 21:54
  • Is it critical for your app to use 10.6's `NSCell`? 10.7's `NSCellView` makes this much easier. Managing custom views in cells is a non-trivial task, so good luck. Here is a link that you may find useful: http://stackoverflow.com/questions/1058639/how-to-display-indeterminate-nsprogressindicator-in-the-nsoutlineview – Vadim Jul 27 '12 at 23:37
  • I found it, but I really require OS X 10.6 support or my users would be...unhappy ;) – Pripyat Jul 28 '12 at 00:32