Setting Position of NSWindow before Display - cocoa

Right now I'm setting the position of a window that is about to open like this:
-(void) setActiveNodeDialog:(ISKNodeDialogController *)dialog
{
if (activeNodeDialog)
[[activeNodeDialog window] close];
activeNodeDialog = dialog;
if (activeNodeDialog) {
[activeNodeDialog setMainWindowController:self];
NSRect windowRect = [[self window] frame];
NSRect dialogRect = [[activeNodeDialog window] frame];
NSPoint pos;
pos.x = windowRect.origin.x + windowRect.size.width - dialogRect.size.width - 10;
pos.y = windowRect.origin.y + 32;
[[activeNodeDialog window] setFrameOrigin:pos];
[[activeNodeDialog window] makeKeyAndOrderFront:nil];
}
}
The problem with that is, that the window will "jump" when shown. And that even though I set the position before showing the window with "makeKeyAndOrderFront". The window is a NSPanel *. Anyone any ideas how to fix the jumping?
Setting the position in awakeFromNib is not an option because the main controller is set later.

In Interface Builder, is "visible at launch" checked for the window? If so uncheck it and then you won't even need this code [[activeNodeDialog window] close];. Basically if that is checked then the window is automatically shown when the xib is instantiated... which you don't want.

Related

Can't set position of modal window

I have a window I want to display as a modal window (OS X 10.10). I'm loading the NIB for the window and am able to set the title successfully and then display the window. But whatever I do to try to affect the window position doesn't work.
This works (part of NSWindowController sub-class):
[[self window] setTitle:title];
[[NSApplication sharedApplication] runModalForWindow:[self window]];
Here are ways with which I've tried to affect the position after setting the title:
[[self window] setFrameOrigin: NSMakePoint(200.0, 200.0) ];
[[self window] setFrameTopLeftPoint: NSMakePoint(200.0, 200.0) ];
[[self window] setFrame: NSMakeRect(200, 300, [[self window] frame].size.width, [[self window] frame].size.height) display:YES];
(I've tried other values as well - just for testing, but nothing.)
I can even query the
[[self window] frame]
and it pretends to accept the new values, but the window stubbornly keeps showing up in the same position.
What gives?
I have solved this by
1- Make a new NSWindow subclass, overriding the center method, where you just make the frame of the new window positioned at whatever NSPoint you want:
class CenteredInParentWindow: NSWindow {
var parentMinX : CGFloat?
var parentMinY : CGFloat?
override func center() {
guard let parentMinX = parentMinX, let parentMinY = parentMinY else {
super.center()
return
}
self.setFrameOrigin(NSPoint(x: parentMinX, y: parentMinY))
}
}
2 - Set WindowController's window class to be the new NSWindow subclass, in Storyboard.
3- Instatiate the window controller and set the attributes of the subclassed window
let myWindowController = self.storyboard!.instantiateController(withIdentifier: "windowID") as! PlansWindowController
if let customWindow = myWindowController.window as? CenteredInParentWindow {
customWindow.parentMinX = NSApplication.shared.mainWindow?.frame.minX
customWindow.parentMinY = NSApplication.shared.mainWindow?.frame.minY
}
NSApp.runModal(for: myWindowController.window!)
}
You may need runModal method for making the window a modal one. Don't forget to include NSApp.stopModal() in the windowWillClose method which is available in NSWindowDelegate in your View controller

subclass a programmatically created NSImage

I programatically create an NSImage like this:
NSImageView *IconBox = [[NSImageView alloc] initWithFrame:CGRectMake(0, 0, 350, 300)];
NSImage *capper = [[NSImage alloc] initWithData:[self.superview dataWithPDFInsideRect:[self.superview bounds]]];
IconBox.image = capper;
[self addSubview:IconBox];
This Image gets main Part of my Window and i want to make this window draggable anywhere, i know i need to set mouseDownCanMoveWindow, but this doesn't work, while this applies to the Windoow, but while my Image is the main visible part of the Window you cannot access its background, so i need to tell the Image to drag the Window,
i read i need to subclass the NSImage and override this Method, but how can i subclass a programatically created NSImage?
I already created the Class Files in my Project, but how can i tell the newly created Image to use this custom class?
Figured it out:
Class snap = NSClassFromString(#"snapper");
object_setClass(IconBox, [snap class]);
This does the trick, also ithe default Method moveWindowByMouseDown didn't work either, but this snippet did:
In the NSImage Subclass:
-(void)mouseDragged:(NSEvent *)theEvent
{
CGFloat deltax = [theEvent deltaX];
CGFloat deltay = [theEvent deltaY];
NSRect frame = [[self window] frame];
frame.origin.x += deltax;
frame.origin.y -= deltay;
[[self window] setFrameOrigin:frame.origin];
}

Auto-resized Window Position on Restore

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.
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.

Programmatically add a close button to an NSWindow

I'd like to add a close button to an NSWindow programmatically. I can get the button to display, but there are no mouse-over or mouse-down effects. My "selector" never seems to get called when i click the button. I'm not really sure whats wrong and why this is so annoying.
Here is what I've been messing with:
closeButton = [NSWindow standardWindowButton:NSWindowCloseButton forStyleMask:self.styleMask];
NSView *themeFrame = [[self contentView] superview];
NSRect c = [themeFrame frame]; // c for "container"
NSRect aV = [closeButton frame]; // aV for "accessory view"
NSRect newFrame = NSMakeRect( c.size.width - aV.size.width - 5, // x position c.size.height - aV.size.height - 5, // y position aV.size.width, // width aV.size.height); // height
[closeButton setFrame:newFrame];
[themeFrame addSubview:closeButton];
[closeButton setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
[closeButton setEnabled:YES];
[closeButton setTarget:self];
[closeButton setAction:NSSelectorFromString(#"testClick:") ];
Where "testClick" is just a memeber function of my class and is defined as such:
- (void)testClick:(id)sender
The problem seems to be the call to:
[themeFrame addSubview:closeButton];
where the themeFrame is: [[self contentView] superview] Just adding the button to [self contentView] works, but I'd like it added to the titlebar.
No Interface Builder please...
Potential issue # 1)
The way you're calling "NSSelectorFromString" seems incorrect to me. I don't think you can pass parameters via this way in Objective C.
Try this:
[closeButton setAction: #selector(closeWindow:)];
and create a new "closeWindow:" action that looks like:
- (void) closeWindow: (id) sender;
which closes the window.
Potential issue # 2)
Instead of:
closeButton = [NSWindow standardWindowButton:NSWindowCloseButton forStyleMask:self.styleMask];
NSView *themeFrame = [[self contentView] superview];
Why not use:
NSWindow * parentWindow = [[self contentView] window];
if(parentWindow)
{
closeButton = [parentWindow standardWindowButton:NSWindowCloseButton forStyleMask:self.styleMask];
}

Drag window with NSImage as handle?

I am writing a mac app and have used this code to hide the window handle,
[self.window setStyleMask:NSBorderlessWindowMask];
Well this is great, but i still need a handle to move around the window, so is there a way to move around the window from another object like an NSImage, acting like the window handle?
The code for my image is in the .h,
#interface WOWAppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
IBOutlet NSImageView* winView;
}
and in the .m file,
- (void)awakeFromNib {
NSString *inFilePathwin = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"win.png"];
NSImage *winImage = [[[NSImage alloc] initWithContentsOfFile:inFilePathwin] autorelease];
[winView setImage:winImage];
}
So the image is working and showing so how do i link that function?
Hook the mouseDragged event on a NSView subview.
-(void)mouseDragged:(NSEvent *)theEvent
{
CGFloat deltax = [theEvent deltaX];
CGFloat deltay = [theEvent deltaY];
NSRect frame = [[self window] frame];
frame.origin.x += deltax;
frame.origin.y -= deltay;
[[self window] setFrameOrigin:frame.origin];
}
So
subclass NSView as XView
place your handle image in an instance of this view container.
Place the container where you want in the windows main view.
Use this snippet in XView to drag the window around.
Adjust the behaviour with the mouseDown and MouseUp events.

Resources