9
votes

I have created a custom view that implements initWithFrame and drawRect. In IB, I created a window and placed the custom view in the center of the window. I then used Editor>Embed In>Scroll View to place the custom view into a scroll view. And then, nothing? The initWithFrame message is sent to the custom view, but the drawRect message is never sent.

Similar behavior can be produced without my specific custom view. If a wrapping label is embedded in a scrollview in the same way, it will also not be drawn.

How do I get the scrollview to draw its contents?

Also, on a somewhat related note, what is the correct way to programmatically resize the view embedded in the scrollview?

Thanks!

3

3 Answers

24
votes

NSScrollView is not like other views. It does things just differently enough that it makes everyone crazy at first, and especially once you've gotten used to Auto Layout thinking. It is also one of those views (along with NSTableView, NSOutlineView and NSCollectionView and more) where there are a lot of pieces to set up before you can start seeing things working. Auto Layout adds to this a bit, too.

NSScrollView contains at least 3 subviews by default:

  • One NSClipView object. (Apple calls this the Content View. It is what is visible.)
  • Two NSScroller objects.

The NSClipView or Content View object contains an NSView of some sort.

(Apple calls this the Document View)

If you add a plain NSScrollView to your xib from the object library, that will appear to have a size in Interface Builder, but at run time it will have a zero size. This will kick your butt every time. You can see this by adding logging it.

NSLog(@" size of document view:%@", NSStringFromRect(_myScrollView.documentView.frame);

Even if you add a subview and call setNeedsDisplay:YES all day long, you won't see a damn thing.

There are things you can do though. First, you can set the size of the documentView with setFrameSize:NSMakeSize(somePointsWide, somePointsHigh)

You can also make it a custom NSView subclass that has an implementation of intrinsicContentSize that figures this out based on whatever logic rules you want to put in there (including some default minimum).

You could also simply say that all subviews must be at least N points from the documentView or more on each edge with Auto Layout. The VFL (visual format language) would look like this: @"H:|-nPoints-[aSubviewOfDocumentView]-nPoints-|" and this @"V:|-nPoints-[aSubviewOfDocumentView]-nPoints-|" (make sure your subviews have an intrinsic size or some subview of their own that gives them a minimum size... and implements - (BOOL)translatesAutoresizingMaskIntoConstraints { return NO; } and turn off Auto Resizes Subviews for your documentView by the checkbox in IB, or by calling setAutoResizesSubviews:NO or you'll be hating life.)

Now this leads to a weird thing. With Auto Layout, it's generally good to stop thinking about frames and rects as much as possible, but with the NSScrollView documentView, you might want to consider not using Auto Layout for positioning subviews. In some cases it might be simpler not to, and just let the documentView grow to accommodate. If you use the above "nPoints" approach, and your subviews are draggable, you want to simply capture the NSPoint for the subview's frame origin (use it to determine your delta) and then the mouseDown point and when a drag finished, capture the mouse point and calculate the delta for your new frame origin point. Then call setFrameOrigin: with the new point.

4
votes

Are you using autolayout or old-fashioned view resizing? If your init method is being called but not the draw method, it sounds like either your entire scrollView or your custom view is zero size.

If you have a custom view and you are using autolayout, did you implement the -intrinsicContentSize method in your custom view subclass?

0
votes

I just finished fighting coaxing NSScrollView to work with a custom view. As uchuugaka pointed out a few years ago, resizing the NSScrollView with the auto layout system enabled can cause the scroll view to grow your custom view's bounds when the scrolled content view is larger than your actual document view.

There are two things to watch for, and both can be corrected in your customView's drawRect()

  1. When the window shrinks again, the grown document view will not shrink with it, and the scroll bars will be sized incorrectly.
  2. Sometimes with vigorous resizing the custom view's area will become slightly larger than the actual documentVisibleRect. This is a bug. Once this happens, no matter how big you drag the scroll view, the scroll bars will never go away.

To combat this, my custom view (which only scrolls vertically) checks for these cases in drawRect() and corrects the sizes depending on whether we've grown or shrunk.

 if scrollView.documentVisibleRect.size.height >= actualDocumentHeight {
        //
        // Sometimes the scroller machinery can enlarge the document rect a little
        // extra when resizing the window quickly.  We should check for that.
        //
        if self.bounds.size.height > scrollView.contentSize.height {
            self.frame.size.height = scrollView.contentSize.height
            self.bounds.size.height = scrollView.contentSize.height
        }
    } else {
        //
        // If it's smaller, set our size to the actual size and the scroll
        // bars will pop on.
        //
        self.frame.size.height = actualDocumentHeight
        self.bounds.size.height = actualDocumentHeight
    }