Enforcing a window's aspect ratio (spoiler alert)

I’ve tried a couple of things to do the challenge of enforcing a window’s aspect ration through the windowWillResize(:toSize:) delegate method and I wonder if it is actually possible to do it that way.

My simple implementation checks if the new width or height have changed and then I return an NSSize back with modified values. But when I resize the window it correctly keeps its aspect ratio but it also slowly moves on the screen.

The most simple solution here did not actually involve the delegate: I simply set the NSWindow.contentAspectRatio property. But that may be considered cheating :-/

This is how I did it. I didn’t notice any window movement, I double check.

func windowWillResize(sender: NSWindow, toSize frameSize: NSSize) -> NSSize { let mySize = NSSize(width: frameSize.width, height: frameSize.width * 2) return mySize }

Try resizing the window from the corner and from the sides. You will see that it moves around.

I wonder if there is a way to keep the window frame origin the same. I think it would be more natural to just resize the window and not move it.

If I drag from the corner and the left and right sides, I see normal behaviour. However I cannot drag from the bottom.

EDIT: I was thinking about what you said about expecting the window not to move. For me, I would expect the element of the window opposite the thing I drag to remain unmoved. So for example if I drag the bottom right corner I would expect the top left corner to remain where it is. If I drag the left edge I would expect the right edge to remain where it is. The constraints we are holding the window to would necessarily change the other dimensions. Let’s not forget that constraining a window’s aspect ratio is a very unusual thing to do and so there isn’t some well defined, correct way for it to behave.

If you expect the top left corner to stay where it is during dragging then dragging the left edge would give the user a very odd experience.

Both of you are on the right track to implementing the solution we were looking for as an exercise in delegation, implementing windowWillResize(_:toSize:). Well done using the window’s contentAspectRatio as well!

To get dragging from both the left/right and top/bottom sides to work, you’ll need to sometimes calculate the height based on the width (NSSize(width: frameSize.width, height: frameSize.width * 2.0)) and sometimes calculate the width based on the height (NSSize(width: frameSize.height / 2.0, height: frameSize.height)) depending on whether the height or width is changing.

It’s true that the window’s resizing may seem a bit wonky at first. Here’s how to think about it: When you resize the window by dragging from one of the window’s edges, notice that the edge follows your mouse, and the opposite edge stays fixed. Similarly, when you drag from the corner, the corner tries to follow your mouse, and the opposite corner stays fixed. This is perfectly intuitive when the we’re not adjusting the window’s size as the user resizes the window. When we are adjusting the size, it does appear a bit wonkier. The reason, though, is that the control point (whether a corner or an edge) that we’re dragging is trying to follow the mouse, even if we’re hampering it a bit. Notice that you get the same behavior when using the contentAspectRatio approach.

For an extra challenge, when live resizing begins, determine whether the user is changing the width or the height, and update the other component (height and width, respectively) based on the first component. To do this, you’ll need to implement windowDidEndLiveResize(_:slight_smile:. It will be helpful to have an enum which keeps track of whether the user is changing the height or the width like this:

enum ResizeStyle {
    case Height
    case Width
    case None
}

Simple solution. Works both when adjusting the width and when adjusting the height. Also works from corner point, but a bit more jerky.
Why does this work, but using if-else does not work? (only smooth for one of the sides) I didn’t see any use for windowDidEndLiveResize.

[code] func windowWillResize(sender: NSWindow, toSize frameSize: NSSize) -> NSSize {

    let heightAdjusted = NSSize(width: (frameSize.height / 2.0), height: frameSize.height)
    let widthAdjusted = NSSize(width: frameSize.width, height: frameSize.width * 2)
    
    return sender.frame.height != frameSize.height ? heightAdjusted : widthAdjusted
    
}[/code]

Mackzyme, I’m seeing the same problem with your solution as the OP had. No matter which edge you drag or if you drag from the corner, the top left corner of the window moves in response.

Yes, you’re correct the origin (origo)changes. It’s probably possible to lock that although I haven’t tried. I just posted a solution for the resizing.

I’m confused as to why windowDidEndLiveResize(:slight_smile: is necessary to determine whether the user is changing the height or the width. I implemented it here and tested it with a console message, but did not end up using it for anything important. I instead made the height/width determination by setting oldHeight and oldWidth variables to match the initial size values I gave my window in the Interface Builder, and comparing the values coming from windowWillResize(:slight_smile: to them. At the end of each call to windowWillResize(_:), oldHeight and oldWidth are updated to the new height and width values being passed back to the window. My solution seems to work fine, but I want to make sure I’m not missing the point of the challenge. Any other comments or critiques are welcome. I’m VERY new at this. Thanks!

var oldHeight: CGFloat = 400.0
    var oldWidth: CGFloat = 200.0
    
    
    func windowDidEndLiveResize(notification: NSNotification) {
        println("Done resizing")
    }
    
    
    func windowWillResize(sender: NSWindow, toSize frameSize: NSSize) -> NSSize {
        let userSize = frameSize
        // println("The user wants a height of \(userSize.height) and a width of \(userSize.width).")
        
        var desWidth = userSize.width
        var desHeight = userSize.height
        
        var newHeight: CGFloat = 0.0
        var newWidth: CGFloat = 0.0
        
        
        
        if desHeight != oldHeight {
            newHeight = desHeight
            newWidth = desHeight / 2.0
            println("User is changing the height")
        } else if desWidth != oldWidth {
            newWidth = desWidth
            newHeight = desWidth * 2.0
            println("User is changing the width")
        }
        
        let mySize = NSSize(width: newWidth, height: newHeight)
        
        oldHeight = mySize.height
        oldWidth = mySize.width
        
        return mySize
    }

Yes, the window moves around at some implementations like this one:

func windowWillResize(sender: NSWindow, toSize frameSize: NSSize) -> NSSize {
        var newSize = frameSize
        newSize.height = frameSize.width / 2
        return newSize
}

But that is just natural behavior if you drag it from the lower left or lower right corner. Drag my implementation from the upper right/left corner and you will notice no wandering.

Mackyzme’s implementation has the problem that it has crazy flackering, which makes me crazy. :wink:

My question is: Why has the mouse such a strange behavior as soon you implement this delegate method? The double arrow for the resizing cursor always becomes a normal mouse pointer at some points where you should see a double arrow… That’s pretty nasty.

While reading the documentation, I found a very easy way to implement this. There is a property of NSWindow which allows us to set an aspect ratio so…

//
//  MainWindowController.swift
//  AspectRatioEnforcedWindow
//

import Cocoa

class MainWindowController: NSWindowController, NSWindowDelegate {

    override var windowNibName:String {
        return "MainWindowController"
    }

    override func windowDidLoad() {
        super.windowDidLoad()
    }

    func windowWillResize(sender: NSWindow, toSize frameSize: NSSize) -> NSSize {
        sender.aspectRatio = NSSize(width: 2.0, height: 1.0)
        return frameSize
    }
    
}

I think your solution is very similar to mine. If you drag on a corner it can cause odd behavior. As it can jump between the two branches of the if statement. So the window flickers. That is, while you are draging from a corner, sometimes you are changing the height, and sometimes you are changing the width. and sometimes you are changing both.

The resizing was very weird for me too. Not to mention keeping the controls looking right (which requires constraints). I fully-formed another idea while looking at this thread. If you have one side twice as long as its perpendicular other side, then their hypotenuse is sqrt(5) times the length of the shorter side. So I took the hypot(width, height) and scaled both dimensions to hypot/sqrt(5) and 2*hypot/sqrt(5) and the resizing was smoother!

I was able to overcome the wandering window problem. When the returned size contains a fractional component for one of the dimensions (i.e. width = frameSize.height / 2.0), Cocoa seems to move the window a bit. I was able to prevent this by applying ceil() to the calculation:

width = ceil(frameSize.height / 2.0)