4
votes

I'm having a few problems with the NSScrollView I'm trying to use and could use some help. I've read the NSView, NSScrollView and a few other guides and references, along with questions here and cocoadev, but still can't figure these out.

(Code for my view subclass can be found below.)

My overall goal with this is an interface like that for KGChart, a needlepoint chart maker that is PC exclusive. (KGChart website)

  1. How can I keep things I draw in the scroll view? My -drawStitch method is executed during mouse events, where it draws a rect with a symbol on it (currently only black with a "+") to the document view of the scrollview. When I scroll it out of sight and back, it is gone and I need to retain it somehow. My idea is to try a 2D array to keep track of the stitches, but I think performance would suffer with large canvases (I would like to do 2,000x2,000, but I would settle for 500x500).

  2. If you look at the picture of my program running, you can see the difference in the grid color. The code for drawing the grid is in -drawRect and the color is set as grayColor. When the program starts, the visible grid is the lighter color, but it's darker on the parts of the canvas revealed when I scroll. I think it has to do with -drawRect being called, causing it to redraw the grid, but I don't know why it darkens the strokes as their opacity should be 1.0 already. Also, if I scroll the original frame away completely and go back, the grid is darker there as well.

  3. Is there a way for me to draw the grid at the beginning of the program that's not in drawRect so it doesn't get redrawn? I tried in -awakeFromNib and -initWithFrame, even with -lockFocus, but it didn't work. I realize the first probably didn't work because there wasn't a frame to draw to.

  4. Eventually I'll need to figure out things like the fill tool and things, so would it be better to use something like Core Graphics for this, or is the regular API OK? I would rather use the later as I know more of it than CG, but I read a bit on it too.

Final notes: I am using garbage collection and I've flipped the view and the window it's in, if those things matter at all.

Picture of the program running:

Picture of the program running

    - (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    [self setFrameSize:NSMakeSize(3000, 3000)];
    return self;
}

- (void)drawRect:(NSRect)rect {

    width = [self frame].size.width;
    height = [self frame].size.height;

    [[NSColor grayColor] setStroke];

    NSBezierPath* drawingPath = [NSBezierPath bezierPath];
    [NSBezierPath setDefaultLineWidth:2.0];
    [self addDashStyleToPath:drawingPath];

    int i;

    for( i = 0 ; i <= width ; i=i+30) {
        [drawingPath moveToPoint:NSMakePoint(i, 0)]; 
        [drawingPath lineToPoint:NSMakePoint(i, height)]; 
    } 

    for( i = 0 ; i <= height ; i=i+30) { 
        [drawingPath moveToPoint:NSMakePoint(0,i)]; 
        [drawingPath lineToPoint:NSMakePoint(width, i)]; 
    }

    [drawingPath stroke];

}
-(void)drawStitch{

    NSPoint thisPoint;
    NSPoint fillPoint;
    NSRect fillRect;


    float thisPointX = [self calculatedItemBounds].origin.x + 5.0;
    float thisPointY = [self calculatedItemBounds].origin.y - 8.0;

    float fillPointX = [self calculatedItemBounds].origin.x + 1.0;
    float fillPointY = [self calculatedItemBounds].origin.y + 1.0;

    thisPoint.x = thisPointX;
    thisPoint.y = thisPointY;

    fillPoint.x = fillPointX;
    fillPoint.y = fillPointY;

    fillRect.origin.x = fillPoint.x;
    fillRect.origin.y = fillPoint.y;

    fillRect.size.width = 28;
    fillRect.size.height = 28;

    NSMutableDictionary *theAttributes;

    theAttributes = [[NSMutableDictionary alloc] init];
    [theAttributes setObject: [NSColor whiteColor] forKey:NSForegroundColorAttributeName];
    [theAttributes setObject: [NSFont fontWithName:@"Helvetica" size: 32] forKey: NSFontAttributeName];

    NSString *theString = @"+";
    [self lockFocus];
    [[NSColor blackColor] setFill];
    [NSBezierPath setDefaultLineWidth:2.0];
    [self addDashStyleToPath:[NSBezierPath bezierPathWithRect:[self calculatedItemBounds]]];
    [NSBezierPath fillRect:fillRect];

    NSNumber* myInteger = [NSNumber numberWithInt:310];

    [myArray setObject:myInteger :location.x/30 :location.y/30];
    NSLog(@"%@", [myArray objectInSection:location.x/30 :location.y/30]);
    NSLog(@"%f, %f", location.x/30,location.y/30);

    [theString drawAtPoint:thisPoint withAttributes:theAttributes];
    [self displayIfNeededInRectIgnoringOpacity:[self calculatedItemBounds]];

    [self unlockFocus];
    [theAttributes release];
}

I'm pretty noobish at this, obviously, so thank you for all your help!

2
Alright, I got problems 2 and 3 taken care of with an if statement that draws the grid only the first time -drawRect is called. Knowing whether or not Core Graphics would work better would still help. Number one is still my major problem though. If I make a "stitch" on the screen and scroll it so it bumps against the edge of the scrollview, when I scroll the stitch I made back towards the center, it copies that stitch like a tail from the edge. Also, I've been trying to use a 2D array to store the drawn stitches, but it lags noticeably. A link in the right direction would be most helpful. – nintandrew

2 Answers

2
votes

By default, the content view (an instance of NSClipView) of an NSScrollView does some performance optimisation by copying its existing rendered image when scrolling. Much of the time this works fine but when you are drawing a complex custom view it is often unwanted behaviour, so you should turn it off.

If you're using a nib to define your UI, just select the scroll view in IB and un-check the "Copies on Scroll" checkbox.

You can set this programmatically using the ‑setCopiesOnScroll: method of NSClipView:

[[scrollView contentView] setCopiesOnScroll:NO];
1
votes

I do believe I got this fixed. For it to work, I had to rearrange my code to do the drawing in drawRect, rather than using lockFocus in my drawStitch method (I need to read up on MVC designing). Also, the scrollview only seems to redraw the latest bezier path, so to make multiple "stitches" I need to keep appending the path with rects.