Laszlo Korte
Laszlo Korte

Reputation: 1179

Stretch documentView to fill NSScrollView

What I would like to achieve:

A rectangle (paper) of a given size which is draw inside a view. If it is larger than the view I would like it to be scrollable. If it is smaller than the view I would like it be centered.

A list of shapes should be drawn on top of this rectangle. If they are outside the rectangle they should not be clippd but be drawn anyway.

The scrollable area is only affected by the rect(paper) not by the shapes. So a shape outside the scrollable area does not need to be drawn.

I am using Swift2.0, auto layout and InterfaceBuilder

What I have now:

I have a custom CanvasView inside an NSScrollView.

I used this trick to center the CanvasView if the canvas size is smaller than the NSScrollView: https://stackoverflow.com/a/28154936/1533291

My CanvasView is defined like this:

class CanvasView : NSView {
  var canvasSize = Vec2d(x: 100, y: 100) {
    didSet {
      invalidateintrinsiccontentsize()
    }
  }

  var shapes : [MyShape] = []

  override func drawRect(dirtyRect: NSRect) {
    if let context = currentContext {
      let offsetX = (bounds.width-CGFloat(canvasSize.x))/2.0
      let offsetY = (bounds.height-CGFloat(canvasSize.y))/2.0
      CGContextTranslateCTM(context, offsetX, offsetY)

      CGContextSetRGBFillColor(context, 1, 1, 1, 1)
      CGContextFillRect(context, CGRect(x:0,y:0, width: CGFloat(canvasSize.x), height: CGFloat(canvasSize.y)))

      for shape in shapes {
          shape.shape.render(context)
      }
    }
  }

  override var intrinsicContentSize : NSSize {
    return NSSize(width: canvasSize.x, height: canvasSize.y)
  }
}

So as you can see I draw everything centered inside the canvas view.

The problem is if one of the shapes is outside the canvas it get's clipped because drawRect can only draw inside the view's bounds.

Thats why I would like to stretch the CanvasView so it's width and height are alway equal to or greater than the size of the scrollview. But I do not know how to do it and I am not even sure if this is the most elegant approach.

Update: The answer I posted below does not work when the ScrollView is zoomable and the zoom factor is less than one. I this case the canvas is neither stretched to full size nor is it centred. I am still interested in a working solution for zoomable Scrollviews.

Upvotes: 0

Views: 341

Answers (1)

Laszlo Korte
Laszlo Korte

Reputation: 1179

Update: Does not work when the ScrollView is zoomable and the zoom factor is less than 1...


After some more experimenting I found a working solution myself. The core solution is quite straightforward:

  1. Create a NSScrollView in InterfaceBuilder
  2. Specify the class for the custom NSView inside the Clip View (you do not need to set change the ClipView's class)

  3. Now add two Constraints:

    1. customView.height >= clipView.height (prio:1000)
    2. customView.width >= clipView.width (prio:1000)

This is what I already tried before but it would result in InterfaceBuilder complaining the constraint definitions not being sufficient because it does not know that the alignment is done at runtime by the NSScrollView itself.

  1. That's why you need to add two more constraints:
    1. customView.centerX == clipView.centerX (prio: 1000)
    2. customView.centerY == clipView.centerY (prio: 1000)

But as these two constraints are not needed at runtime you can select the option "remove at build time" for both of them

But even now InterfaceBuilder is complaining. It does not know the intrinsicSize of the customView even though it is defined in the customView class. It marks the width/height constraints as ambiguous.

  1. That's why finally you have to set a placeholder for the customView's intrinsic size.

Now InterfaceBuilder is happy and when running the app the customView never is smaller than the scrollView.

All the compression resistance and content hugging priorities can be left set to their defaults.

The following screenshot shows the described options in InterfaceBuilder:

A screenshot of InterfaceBuilder showing of the described options

Upvotes: 1

Related Questions