2
votes

I have an app that resizes itself for different views using:

NSSize currentSize = [[box contentView] frame].size;
NSSize newSize = [v frame].size;
float deltaWidth = newSize.width - currentSize.width;
float deltaHeight = newSize.height - currentSize.height;
NSRect windowFrame = [w frame];
windowFrame.size.height += deltaHeight;
windowFrame.origin.y -= deltaHeight;
windowFrame.size.width += deltaWidth;

[box setContentView: nil];
[w setFrame: windowFrame
    display: YES
    animate: YES];

[box setContentView: v];

When you change the view, the window grows/shrinks based on the upper-left corner of the app. The app always starts on the first view, no matter which view the user was in when they quit, because this is a summary view. I also want it to be restorable so the last-used document is open on launch.

The problem: since most views are taller than the summary view, changing to one pushes the bottom left corner of the window farther down the screen. Now quit the app and relaunch, and the app positions the window to where that bottom left corner was previously. I know this is because that's where Cocoa puts the origin, but it makes more sense for the user to have the window restart with the same top left corner, otherwise the app is shifting down the screen each time it's opened.

I tried observing NSWindowWillCloseNotification and calling the above method again to reset the app to the summary view just before closing, but even though the code works, the window still starts in the wrong position - I'm guessing Cocoa sets an app's restorable defaults before the notification is sent.

It's been done before: System Preferences does it - it auto-resizes for views, dictates what your starting view is, but you can move the window and next time you open it, it'll be in the new position based on the top left corner. Anyone have an idea how to emulate that?

Edit: Document.xib has a small window with a popup button and a box. The methods below change the views and resize the window to fit those views. Everything works fine while the app is open, the window shifting is only an issue when the app is closed and re-opened with an active document (ie. using Lion's restore).

     ============================
    | Jump to:___                |    The "===" is the top and bottom edge of the window
    |                            |    The "___" is the pop-up menu for selecting the view
    | -------------------------- |    The dashed box on the inside is "box", which holds the views
    ||                          ||
    ||                          ||
    ||                          ||
    ||                          ||
    ||                          ||
    ||                          ||
    | -------------------------- |
     ============================
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
    [super windowControllerDidLoadNib:aController];

    //Note that init creates the array viewControllers
    NSMenu *menu = [viewMenu menu];
    NSUInteger i, itemCount;
    itemCount = [viewControllers count];

    for (i = 0; i < itemCount; i++) {
        NSViewController *vc = [viewControllers objectAtIndex:i];
        NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: [vc title] action: @selector(changeViewController:) keyEquivalent:@""];
        [mi setTag: i];
        [menu addItem: mi];
    }

    [self displayViewController: [viewControllers objectAtIndex: 0]];
    [viewMenu selectItemAtIndex: 0];
}

- (void) displayViewController: (ManagingViewController *) vc {
    //End editing
    NSWindow *w = [box window];
    BOOL ended = [w makeFirstResponder:w];
    if (!ended) {
        NSBeep();
        return;
    }

    //Put view in box
    NSView *v = [vc view];

    NSSize currentSize = [[box contentView] frame].size;
    NSSize newSize = [v frame].size;
    float deltaWidth = newSize.width - currentSize.width;
    float deltaHeight = newSize.height - currentSize.height;
    NSRect windowFrame = [w frame];
    windowFrame.size.height += deltaHeight;
    windowFrame.origin.y -= deltaHeight;
    windowFrame.size.width += deltaWidth;

    [box setContentView: nil];
    [w setFrame: windowFrame display: YES animate: YES];

    [box setContentView: v];

}

- (IBAction)changeViewController:(id)sender {
    NSUInteger i = [sender tag];
    ManagingViewController *vc = [viewControllers objectAtIndex: i];
    [self displayViewController: vc];
}

I tried putting the first block of suggested code from @trudyscousin just below the for loop in -windowControllerDidLoadNib: and that anchored the window correctly, but when the code reaches -displayViewController: it sets currentSize = (6,6). I'm not sure if that's because the box is empty in IB. It then adds the difference between (6,6) and the size of the SummaryView to the size of the window when the app last quit, making it huge.

I then tried using NSUserDefaults by adding two lines to -changeViewController:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger: i forKey: @"lastViewController"];

and in -windowControllerDidLoadNib:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    if ([userDefaults integerForKey: @"lastViewController"]) {
        NSInteger i = [userDefaults integerForKey: @"lastViewController"];
        ManagingViewController *vc = [viewControllers objectAtIndex: i];
        NSView *v = [vc view];
        NSSize currentSize = [v frame].size;
        [box setFrameSize: currentSize];
        [self displayViewController: vc];
        [viewMenu selectItemAtIndex: i];
    } else {
        [self displayViewController: [viewControllers objectAtIndex: 0]];
        [viewMenu selectItemAtIndex: 0];
    }

This works, but forces the app to launch with the last used view, which wouldn't be so bad except that it will even do that when a new document is opened/created. If I get rid of the else (i.e., always run those last 2 lines), I'm back to where I started - the app launches being shifted vertically down the screen. It looks like you can't set the window's origin at launch, or maybe it reloads its own defaults at a later point.

The last thing I did was go back to my starting code (not using the mask code provided by @trudyscousin) but at the bottom of -windowControllerDidLoadNib: I added:

[self performSelector: @selector(adjustOrigin) withObject: nil afterDelay:0.0f];

which calls:

- (void) adjustOrigin {
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    if ([userDefaults integerForKey: @"lastViewController"]) {
        NSInteger i = [userDefaults integerForKey: @"lastViewController"];
        ManagingViewController *vc = [viewControllers objectAtIndex: i];
        NSView *v = [vc view];
        NSSize previousSize = [v frame].size;
        NSSize currentSize = [[[viewControllers objectAtIndex: 0] view] frame].size;
        CGFloat delta = previousSize.height - currentSize.height;
        NSRect windowFrame = [[box window] frame];
        windowFrame.origin.y += delta;
        [[box window] setFrame: windowFrame display:YES];
    }
}

That actually does work, but if multiple things are running, the lag is enough that you can see the window make the "jump" to its new origin, which is a little inelegant.

1
It does look as if I've misunderstood your problem. Your illustration made things much more clear. I'm deleting my answer; perhaps with a clearer idea of your issue, I'll have another go at it.Extra Savoir-Faire
@trudyscousin No need to delete - don't be so hard on yourself! It's my fault for not being clearer that the action is inside the box, especially after I compared it to System Preferences. I can see your method would work well in that kind of program, built with a toolbar for navigation. I'll definitely be saving that code snippet. Thanks for your help so far!LighteningKid

1 Answers

2
votes

Your code appears to be sound, but the real problem may be your document xib.

If your document xib is set up for Auto Layout, turn that off. You can do that by selecting the file in Xcode. In the File inspector, turn off the "Use Auto Layout" check box.

Once you've done that, select your popup button. In the Size inspector, anchor the button at the top and left side. Select your box. In the Size inspector, anchor the box on all four sides, and make sure it expands horizontally and vertically.

To recap from my earlier answer, here's your -windowControllerDidLoadNib: method with the changes I had suggested earlier:

- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
    [super windowControllerDidLoadNib:aController];

    //Note that init creates the array viewControllers
    NSMenu *menu = [viewMenu menu];
    NSUInteger i, itemCount;
    itemCount = [viewControllers count];

    for (i = 0; i < itemCount; i++) {
        NSViewController *vc = [viewControllers objectAtIndex:i];
        NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: [vc title] action: @selector(changeViewController:) keyEquivalent:@""];
        [mi setTag: i];
        [menu addItem: mi];
    }

    // ---

    NSWindow *myWindow = [[[self windowControllers] objectAtIndex:0] window];

    NSUInteger styleMask = [myWindow styleMask];

    styleMask ^= NSResizableWindowMask;
    [myWindow setStyleMask:styleMask];

    if ([myWindow setFrameUsingName:@"windowFrameAutosaveName"] == NO)
    {
        [myWindow center];
    }

    (void) [myWindow setFrameAutosaveName:@"windowFrameAutosaveName"];

    styleMask &= ~NSResizableWindowMask;
    [myWindow setStyleMask:styleMask];

    // ---

    [self displayViewController:[viewControllers objectAtIndex:0]];
    [viewMenu selectItemAtIndex:0];
}

I removed the older comments but now indicate where the additional code is. With this, you shouldn't have to bother with maintaining your own defaults for the document, and you don't have to bother with adjusting the origin.

Put these two things together, and you (largely) have your fix.

Yes, I needed to qualify that somewhat. Yours is a document-based application, and so you're going to have to either come up with either a unique name under which to save your document's window location in your user defaults, or perhaps a means of somehow storing that information with the document itself.

As always, good luck to you in your endeavors.