Scrollers on custom view appearing but not disappearing

  • I have a custom view into which I can draw a background color and a
    centered rectangle. As the window is resized, the rectangle stays
    centered and is clipped when the window gets small.

    I want to define a canvas size slightly bigger than the rectangle and
    have the scrollers appear when the available space is less than the
    canvas needs. I override setFrameSize to do this:

    - (void) setFrameSize:(NSSize)newSize
    {
    NSSize cSize;
    cSize = [self canvasSize];  // Provides a size

    // Use the larger dimensions of the two rects
    if(newSize.width > cSize.width)
      cSize.width = newSize.width;
    if(newSize.height > cSize.height)
      cSize.height = newSize.height;

            [super setFrameSize:cSize];
    }

    And this works as long as the window is only made smaller. If the
    window is made larger then the scrollers do not disappear.

    Is my approach the right one for what I am trying to achieve?

    If it is, how can I fix the scroller problem?

    --
    Blog:    Photos: <A href="http://bagelturf.smugmug.com/">http://bagelturf.smugmug.com/
  • On Feb 29, 2008, at 16:00, Steve Weller wrote:

    > - (void) setFrameSize:(NSSize)newSize
    > {
    > NSSize cSize;
    > cSize = [self canvasSize];  // Provides a size
    >
    > // Use the larger dimensions of the two rects
    > if(newSize.width > cSize.width)
    > cSize.width = newSize.width;
    > if(newSize.height > cSize.height)
    > cSize.height = newSize.height;
    >
    > [super setFrameSize:cSize];
    > }
    >
    > And this works as long as the window is only made smaller. If the
    > window is made larger then the scrollers do not disappear.
    >
    > Is my approach the right one for what I am trying to achieve?

    Probably not. There are various ways the view frame can change, and
    they don't all funnel through the setFrameSize method.

    If you register to receive NSViewFrameDidChangeNotification's for your
    view (remembering to call setPostsFrameChangedNotifications: YES), it
    might work to do something like this in the notification method:

    [[NSNotificationCenter defaultCenter] removeObserver: self];
    NSRect frame = [self frame];
    // calculate new frame
    [self setFrame: frame];
    [[NSNotificationCenter defaultCenter] addObserver: self ...
  • On Feb 29, 2008, at 4:46 PM, Quincey Morris wrote:

    >
    > On Feb 29, 2008, at 16:00, Steve Weller wrote:
    >
    >> - (void) setFrameSize:(NSSize)newSize
    >> {
    >> NSSize cSize;
    >> cSize = [self canvasSize];  // Provides a size
    >>
    >> // Use the larger dimensions of the two rects
    >> if(newSize.width > cSize.width)
    >> cSize.width = newSize.width;
    >> if(newSize.height > cSize.height)
    >> cSize.height = newSize.height;
    >>
    >> [super setFrameSize:cSize];
    >> }
    >>
    >> And this works as long as the window is only made smaller. If the
    >> window is made larger then the scrollers do not disappear.
    >>
    >> Is my approach the right one for what I am trying to achieve?
    >
    > Probably not. There are various ways the view frame can change, and
    > they don't all funnel through the setFrameSize method.
    >
    > If you register to receive NSViewFrameDidChangeNotification's for
    > your view (remembering to call setPostsFrameChangedNotifications:
    > YES), it might work to do something like this in the notification
    > method:
    >
    > [[NSNotificationCenter defaultCenter] removeObserver: self];
    > NSRect frame = [self frame];
    > // calculate new frame
    > [self setFrame: frame];
    > [[NSNotificationCenter defaultCenter] addObserver: self ...
    >

    I implemented this with no change no behavior.

    The higher level problem I am trying to solve is this:

    My custom view spans the window's content view. I want to draw a white
    rectangle (think of a document) centered in that area and fill the
    remainder gray. In my drawRect method I first draw gray into the
    rectangle passed. Then I calculate where the white rectangle should be
    and draw it.

    I need the scrollers to act on the bounds of the white rectangle so
    that they are present when any part of it is not visible.

    Should I be putting my custom view inside another view and doing the
    centering and background color with IB instead?

    What is the best way of implementing this?
  • On Feb 29, 2008, at 18:10, Steve Weller wrote:

    > My custom view spans the window's content view. I want to draw a
    > white rectangle (think of a document) centered in that area and fill
    > the remainder gray. In my drawRect method I first draw gray into the
    > rectangle passed. Then I calculate where the white rectangle should
    > be and draw it.
    >
    > I need the scrollers to act on the bounds of the white rectangle so
    > that they are present when any part of it is not visible.

    The question is, where are your scroll bars coming from? Did you put
    your custom view inside a scroll view, or did you add scroll bars
    manually?

    When the scroll bars fail to disappear, if you manually drag the
    thumb, what happens? It's possible you'll have to adjust the frame
    origin as well as the width.
  • On Feb 29, 2008, at 18:46, Steve Weller wrote:

    > There is always too much frame once the scroll bars appear.

    That's something only you can debug. If you break on the frame change
    notification, you should be able to watch the wrong frame being
    calculated.
  • On Feb 29, 2008, at 4:00 PM, Steve Weller wrote:

    >
    > I have a custom view into which I can draw a background color and a
    > centered rectangle. As the window is resized, the rectangle stays
    > centered and is clipped when the window gets small.
    >
    > I want to define a canvas size slightly bigger than the rectangle
    > and have the scrollers appear when the available space is less than
    > the canvas needs. I override setFrameSize to do this:
    >
    > - (void) setFrameSize:(NSSize)newSize
    > {
    > NSSize cSize;
    > cSize = [self canvasSize];  // Provides a size
    >
    > // Use the larger dimensions of the two rects
    > if(newSize.width > cSize.width)
    > cSize.width = newSize.width;
    > if(newSize.height > cSize.height)
    > cSize.height = newSize.height;
    >
    > [super setFrameSize:cSize];
    > }
    >
    > And this works as long as the window is only made smaller. If the
    > window is made larger then the scrollers do not disappear.
    >
    > Is my approach the right one for what I am trying to achieve?
    >
    > If it is, how can I fix the scroller problem?

    I figured it out. Key to my confusion was that I was not involving the
    superview in the calculation.
    In my -awakeFromNib I put this code:

    // Receive notifications if the frame changes
    [self setPostsBoundsChangedNotifications: YES];

    NSNotificationCenter *center = [NSNotificationCenter defaultCenter] ;
        [center addObserver: self
                 selector: @selector(frameDidChangeNotification:)
                     name: NSViewFrameDidChangeNotification
                    object: self];

    NSSize cSize;
    cSize = [self canvasSize];
    [self setFrame:NSMakeRect(0,0,cSize.width,cSize.height)];

    and I added

    // The frame has changed
    -(void)frameDidChangeNotification:(NSNotification *)notification
    {

    [[NSNotificationCenter defaultCenter] removeObserver: self];
    NSRect frame = [[self superview] frame];
    NSSize cSize;
    cSize = [self canvasSize];

    // Use the larger dimensions of the canvas and the superview
    if(frame.size.width > cSize.width)
      cSize.width = frame.size.width;
    if(frame.size.height > cSize.height)
      cSize.height = frame.size.height;

    [self
    setFrame:NSMakeRect
    (frame.origin.x,frame.origin.y,cSize.width,cSize.height)];
    // NSLog(@"%f %f",newSize.width, newSize.height);

    NSNotificationCenter *center = [NSNotificationCenter defaultCenter] ;
        [center addObserver: self
                 selector: @selector(frameDidChangeNotification:)
                     name: NSViewFrameDidChangeNotification
                    object: self];

    }

    This figures out the rectangle that encloses both the canvas and the
    superview's frame and makes my custom view's frame equal to that. The
    only remaining thing to fix is that the lower left point is always
    shown in the view, when I actually want the center point to be shown.
    So I have to shift the frame origin as part of the calculation.
  • On Feb 29, 2008, at 21:31, Steve Weller wrote:

    > -(void)frameDidChangeNotification:(NSNotification *)notification
    > {
    >
    > [[NSNotificationCenter defaultCenter] removeObserver: self];
    > NSRect frame = [[self superview] frame];
    > NSSize cSize;
    > cSize = [self canvasSize];
    >
    > // Use the larger dimensions of the canvas and the superview
    > if(frame.size.width > cSize.width)
    > cSize.width = frame.size.width;
    > if(frame.size.height > cSize.height)
    > cSize.height = frame.size.height;
    >
    > [self
    > setFrame:NSMakeRect
    > (frame.origin.x,frame.origin.y,cSize.width,cSize.height)];
    > //    NSLog(@"%f %f",newSize.width, newSize.height);
    >
    > NSNotificationCenter *center = [NSNotificationCenter defaultCenter] ;
    > [center addObserver: self
    > selector: @selector(frameDidChangeNotification:)
    > name: NSViewFrameDidChangeNotification
    > object: self];
    >
    > }
    >
    > This figures out the rectangle that encloses both the canvas and the
    > superview's frame and makes my custom view's frame equal to that.
    > The only remaining thing to fix is that the lower left point is
    > always shown in the view, when I actually want the center point to
    > be shown. So I have to shift the frame origin as part of the
    > calculation.

    A couple of small points:

    -- It's not quite correct to use the superview's frame to calculate a
    view's frame, since they are in different coordinate systems. You
    really should use [[self superview] bounds], which is in the same
    coordinate system as [self frame].

    The problem is harmless in this case, because the superview is a
    NSClipView, which happens to keep its frame coordinate system
    synchronized with that of view it contains, but this is not generally
    true of view-superview geometry.

    -- If you want to refer to the clip view, [[self enclosingScrollView]
    contentView] is more correct than [self superview]. The fact that
    they're the same thing is an implementation detail. (But if you're
    going to pretend not to know they're the same you really should do an
    explicit coordinate conversion when combining their dimensions.)

    -- As someone suggested on this list a few weeks ago, it's perhaps
    marginally more elegant to use [self visibleRect] instead of the clip
    view bounds. Although the purpose of the clip view is to manage the
    visible rect of the view it contains, using the visible rect directly
    means you don't have to build in knowledge of that implementation
    detail. And there's no coordinate conversion needed.
  • On Feb 29, 2008, at 10:33 PM, Quincey Morris wrote:

    > A couple of small points:
    >
    > -- It's not quite correct to use the superview's frame to calculate
    > a view's frame, since they are in different coordinate systems. You
    > really should use [[self superview] bounds], which is in the same
    > coordinate system as [self frame].
    >
    > The problem is harmless in this case, because the superview is a
    > NSClipView, which happens to keep its frame coordinate system
    > synchronized with that of view it contains, but this is not
    > generally true of view-superview geometry.
    >
    > -- If you want to refer to the clip view, [[self
    > enclosingScrollView] contentView] is more correct than [self
    > superview]. The fact that they're the same thing is an
    > implementation detail. (But if you're going to pretend not to know
    > they're the same you really should do an explicit coordinate
    > conversion when combining their dimensions.)
    >
    > -- As someone suggested on this list a few weeks ago, it's perhaps
    > marginally more elegant to use [self visibleRect] instead of the
    > clip view bounds. Although the purpose of the clip view is to manage
    > the visible rect of the view it contains, using the visible rect
    > directly means you don't have to build in knowledge of that
    > implementation detail. And there's no coordinate conversion needed.

    Thank you. All very helpful information. I have incorporated the
    changes, added origin offsetting to keep things central, and it works
    perfectly. The last change I made was to manually send the
    notification once the content parameters were set up. That ensured
    that the scrollers appeared when the window was first created.

    --
    Blog:    Photos: <A href="http://bagelturf.smugmug.com/">http://bagelturf.smugmug.com/
  • On Fri, Feb 29, 2008 at 7:00 PM, Steve Weller <bagelturf...> wrote:
    > I have a custom view into which I can draw a background color and a
    > centered rectangle. As the window is resized, the rectangle stays
    > centered and is clipped when the window gets small.
    >
    > I want to define a canvas size slightly bigger than the rectangle and
    > have the scrollers appear when the available space is less than the
    > canvas needs. I override setFrameSize to do this:

    FWIW, I've always thought the proper way to do this is to subclass
    NSClipView, but I've never gotten it to work quite right, so I've
    implemented it as you describe.  By redrawing the view so that your
    contents are centered based on the scroll view, you eliminate the clip
    view's ability to copy the image around.

    Perhaps this would make a nice Cocoa sample project...

    --Kyle Sluder