NSView scaling down

I have a window with an NSView anchored to its top. This view needs to support user configurable scaling between 0.5 and 1.0 and stay anchored at the top left corner of its parent/window.

I tried to use scaleUnitSquare which does work but I didn't find a way to reset it and to get the view properly anchored at the top left corner.

I then tried to wrap the view with an NSScrollView and set its magnification as desired. This works well but the view is anchored to the bottom left no matter what I have tried.

Is there a good way to accomplish this?

Accepted Reply

Let's say your window is 200 x 200, and your scalable view is 100 x 100 at the 1.0 scale. Its size would be 50 x 50 at the 0.5 scale.

The simplest way to get what you want for a new scale of 0.5:

  1. Compute the scaled width and height of the view. In this example, the scaled width and height would be 50 x 50.

  2. Compute a new origin that places the top left corner of the scaled view at the top left corner of the window content view. In this example, the origin would be x = 0, y = 200 - 50. That is, the new origin would be (0, 150).

  3. Use setFrame on your view to give it the size and position you calculated in steps 1 and 2.

  4. Use setBounds on your view using the original (1.0 scale) bounds. That is, using origin (0, 0) and size (100, 100). The ratio of the frame size to the bounds size is what scales your view contents down to 0.5.

  5. Make sure you do step 4 after step 3, because setting the frame can also change the view bounds.

That's it, you're done!

Note that I haven't addressed a couple of subtleties:

— I'm assuming that your scalable view is a subview of the window's content view. You can't change the size of the window's content view this way, since it's pinned to the window.

— I'm assuming that the parent view of your scalable view has an unflipped coordinate system, so that its origin is at the bottom left. If it has a flipped coordinate system, the origin is at the top left and you would never have an an issue with the anchoring, I guess.

— Be careful about coordinate systems here, since there are several in play. The frame of your scalable view is in the bounds coordinate system of its parent view, so make sure you do any necessary coordinate system conversions.

  • Thanks, this mostly works. My hierarchy is like this:

    Window

    NSBox with custom background

    +- NSStackView ...

    I added a wrapper NSView around the NSBox and followed your suggestion:

         guard let window else {       return     }     let bounds = sessionPanel.bounds     let sw = bounds.width * 0.9     let sh = bounds.height * 0.9     outerBox.frame = NSRect(x: 0, y: window.frame.height - sh, width: sw, height: sh)     outerBox.bounds = bounds
  • The code works except that I am struggling with getting the box to not expand to the right which exposes the background past the scaled down border. This is the XIB in question without these changes: https://github.com/HearthSim/HSTracker/blob/master/HSTracker/UIs/Battlegrounds/Base.lproj/BattlegroundsSession.xib

    Let me know if you have any suggestions

Add a Comment

Replies

Let's say your window is 200 x 200, and your scalable view is 100 x 100 at the 1.0 scale. Its size would be 50 x 50 at the 0.5 scale.

The simplest way to get what you want for a new scale of 0.5:

  1. Compute the scaled width and height of the view. In this example, the scaled width and height would be 50 x 50.

  2. Compute a new origin that places the top left corner of the scaled view at the top left corner of the window content view. In this example, the origin would be x = 0, y = 200 - 50. That is, the new origin would be (0, 150).

  3. Use setFrame on your view to give it the size and position you calculated in steps 1 and 2.

  4. Use setBounds on your view using the original (1.0 scale) bounds. That is, using origin (0, 0) and size (100, 100). The ratio of the frame size to the bounds size is what scales your view contents down to 0.5.

  5. Make sure you do step 4 after step 3, because setting the frame can also change the view bounds.

That's it, you're done!

Note that I haven't addressed a couple of subtleties:

— I'm assuming that your scalable view is a subview of the window's content view. You can't change the size of the window's content view this way, since it's pinned to the window.

— I'm assuming that the parent view of your scalable view has an unflipped coordinate system, so that its origin is at the bottom left. If it has a flipped coordinate system, the origin is at the top left and you would never have an an issue with the anchoring, I guess.

— Be careful about coordinate systems here, since there are several in play. The frame of your scalable view is in the bounds coordinate system of its parent view, so make sure you do any necessary coordinate system conversions.

  • Thanks, this mostly works. My hierarchy is like this:

    Window

    NSBox with custom background

    +- NSStackView ...

    I added a wrapper NSView around the NSBox and followed your suggestion:

         guard let window else {       return     }     let bounds = sessionPanel.bounds     let sw = bounds.width * 0.9     let sh = bounds.height * 0.9     outerBox.frame = NSRect(x: 0, y: window.frame.height - sh, width: sw, height: sh)     outerBox.bounds = bounds
  • The code works except that I am struggling with getting the box to not expand to the right which exposes the background past the scaled down border. This is the XIB in question without these changes: https://github.com/HearthSim/HSTracker/blob/master/HSTracker/UIs/Battlegrounds/Base.lproj/BattlegroundsSession.xib

    Let me know if you have any suggestions

Add a Comment