views:

34

answers:

3

Hey stackoverflow,

Cocoa's NSScrollView is horribly under-explained. I hope someone here knows what it's all about and can spare me a few seconds.

So I have a custom NSView. I implement -drawRect: for it to draw something, fill itself with colour, whatever. Then I have an NSScrollView wrapping it (set up through the interface Builder).

Now the inner, custom, view must have a size larger than that which fits in the outer scroll view—for it to scroll. That much I realise. I have incidentally configured it so that the scroll view adjusts to the surrounding window’s size, but that shouldn’t matter.

I override my inner view’s -frame method to return a frame sized at least 1000x1000.

- (NSRect)frame {
    CGFloat w = 1000;
    CGFloat h = 1000;
    if (self.superview.bounds.size.width > w)
        w = self.superview.bounds.size.width;
    if (self.superview.bounds.size.height > h)
        h = self.superview.bounds.size.height;
    return NSMakeRect(0, 0, w, h);
}

Here’s the outcome, which I have trouble interpreting:

  • I can scroll when the scroll view encloses an area smaller than 1000x1000

BUT

  • The only area filled with colour (i.e. that my -drawRect: method has any effect on) is

    • as large as the scroll view’s bounds
    • located at (0,0. I use flipped, so that’s top left, and it ends up being outside the visible area after scrolling.
  • The visible area that lies outside this irrelevant rectangle is not painted at all.

I don’t know anything beyond this point. It seems like the rect for drawing is clipped to the scroll view’s position in the window, and size, or something—but it does not take the scrolled "location" into account.

It should be noted that I don't really expect anything else to happen. I feel I am missing a piece, but can't find which. Sorry for the wall of text, but I can’t explain better right now. I hope it is easier to answer than it is to ask.

Regards and hope,

Not Rick Astley

+1  A: 

It's a very very very bad idea to overwrite -frame. There is so much that depends on the actual instance variable having a correct value. Instead try to set the frame to the one you want using setFrame:, that might fix all your problems if you're lucky...

Max Seelemann
I’ll try this right away, since there seems to be a general urge toward not overriding -frame. Didn’t know this would matter—I really only wanted to set the frame once, and thought this’d be a quick way.
Not Rick Astley
Sweet, it worked. Thanks a million. I will remember this distinction and hopefully not pester you about novice things again.
Not Rick Astley
You're welcome.
Max Seelemann
+2  A: 

I agree with Max's warning that you shouldn't override -frame. If you want to constrain the set frame, override its setter ( -setFrame: ) and the designated initializer ( -initWithFrame: ) and adjust the proposed frame as desired.

Regarding your overall problem, I wonder if your problem is conceptual. The argument for -drawRect: (the dirty rectangle you're asked to redraw) is useful if you're drawing something that you can redraw incrementally in parts (like a grid - any grid blocks intersecting dirtyRect can be redrawn and the rest can be ignored). If you're doing something that has to be completely redrawn, you should use [self bounds] and not the dirty rect passed at drawRect.

For example, if you have just a standard gradient background, it's difficult to tell from dirtyRect which part of the gradient to redraw and infinitely easier just to redraw the whole view with the gradient, ignoring dirtyRect altogether.

You're right in assuming that only the area of your view exposed by the scroll view's clip rect will normally be asked to redraw when scrolling. There're also interactions with the scroll view's -copiesOnScroll to consider.

I hope this helps.

Joshua Nozzi
Thanks, this confirms some suspicions about optimisation. This is all prototype work at the moment, since I want to nail these things for several projects that could use some scrolling.
Not Rick Astley
+1  A: 

Use of the NSScroller really relies on a solid understanding of the MVC paradigm. Apple's docs really focus on showing a photo and a set of text, but not much else. The use of NSScrollView is something that I've struggled with in the past.

First off, do not override frame. Use setFrame to tell the scrollView how large the working area is, and then just simply draw in the area the frame encompasses. As I understand it, a custom NSView and the encompassing NSScrollView takes care of the rest, such as what to draw where when. In other words, ignore the bounds of the rect passed into drawRect and instead draw within the bounds of the frame you sent to scrollView; don't worry about what is visible and what isn't because that is the job of the framework.

Here is where the MVC paradigm comes in: setFrame should be used when your Model is updated. So, if an object falls outside of the current bounds of the frame, then use setFrame to set the newly expanded bounds, and then draw within that area.

Philip Regan