In most systems, the default behaviour for "open a new window" is that it appears at the front. This doesn't happen in Cocoa, and I'm trying to find the "correct" way to make this standard behaviour. Most things I've tried only work for a maximum of one window.
I need to open multiple windows on startup:
(N x NSDocuments (one window each)
1 x simple NSWindowController that opens a NIB file.
Things that DON'T work:
Iterate across all the NSDocuments I want to open, and open them.
What happens? ... only the "last" one that call open on comes to the front - the rest are hidden, invisible, nowhere on the screen, until you fast-switch or use the Window menu to find them.
Code:
...documents is an array of NSPersistentDocument's, loaded from CoreData...
[NSDocumentController sharedDocumentController];
[controller openDocumentWithContentsOfURL:[documents objectAtIndex:0] display:YES error:&error];
Manually invoking "makeKeyAndOrderFront" on each window, after it's opened
What happens? nothing different. But the only way I can find to get the NSWindow instance is so horribly hacky it seems totally wrong (but is mentioend in several blogs and mailing list posts)
Code:
[NSDocumentController sharedDocumentController];
NSDocument* openedDocument = [controller openDocumentWithContentsOfURL:[documents objectAtIndex:0] display:YES error:&error];
[[[[openedDocument windowControllers] objectAtIndex:0] window] makeKeyAndOrderFront:nil];
...I know I'm doing this wrong, but I can't find out why/what to do differently :(.
Something that works, usually, but not always:
As above, but just use "showWindow" instead (I took this from the NSDocument guide).
Bizarrely, this sometimes works ... even though it's the exact code that Apple claims they're calling internally. If they're calling it internally, why does it behave different if I re-invoke it after they've already done so?
[[[openedDocument windowControllers] objectAtIndex:0] showWindow:self];
You can just open all the documents without displaying and then tell the documents to show their windows:
NSArray* docs = [NSArray arrayWithObjects:#"doc1.rtf", #"doc2.rtf",#"doc3.rtf",#"doc4.rtf",nil];
for(NSString* doc in docs)
{
NSURL* url = [NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingPathComponent:doc]];
NSError* err;
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:url display:NO error:&err];
}
[[[NSDocumentController sharedDocumentController] documents] makeObjectsPerformSelector:#selector(showWindows)];
Won't this work?
For 10.6 or greater
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
This often has something to do with the app itself: your other windows are behind other apps (in particular, behind Xcode!), and would have appeared with a Hide Others command.
The solution to that problem would be that after you send showWindow to all of your windows (making sure you do the key one last), you tell the app to come forward, relative to other apps.
NSApp.activateIgnoringOtherApps(true) // Swift
or
[NSApp activateIgnoringOtherApps:YES]; // Objective-C
See also: How to bring NSWindow to front and to the current Space?
Related
I've got an OS X app syncing a single document through a ubiquity container back and forth to an iOS equivalent app. The iOS app receives data whenever it changes on the Mac side and sends it whenever it changes on the iOS side (so the iOS app is working all around), and the Mac app sends the data whenever it is changed on the Mac side and it receives the data when the app is launched, but it doesn't seem to be checking again for any data while it runs. I'd like it to update with any changes automatically and immediately, like the OS X "Notes" app does from changes on the iOS side.
At launch, this is the relevant function that gets called:
+(NSMutableDictionary *)getAllNotes {
if(allNotes == nil) {
allNotes = [[NSMutableDictionary alloc]initWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey:kAllNotes]];
cloudDoc = [[CloudDocument alloc]initWithContentsOfURL:[self notesURL] ofType:NSPlainTextDocumentType error:nil];
[cloudDoc saveToURL:[self notesURL] ofType:NSPlainTextDocumentType forSaveOperation:NSSaveOperation error:nil];
}
return allNotes;
}
and that "CloudDocument" class (which is a subclass of NSDocument) includes:
#import "Data.h"
#implementation CloudDocument
-(NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
return [NSKeyedArchiver archivedDataWithRootObject:[Data getAllNotes]];
}
-(BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
NSDictionary *dict = (NSDictionary *)[NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)data];
[Data didReceiveCloudData:dict];
return YES;
}
+(BOOL)autosavesInPlace {
return YES;
}
#end
which kicks it back to:
+(void)didReceiveCloudData:(NSDictionary *)d {
allNotes = [[NSMutableDictionary alloc]initWithDictionary:d];
[[NSUserDefaults standardUserDefaults] setObject:allNotes forKey:kAllNotes];
[cloudDoc updateChangeCount:NSChangeDone];
}
I think the problem is just that I don't have any part of my code that is equivalent to the phrase "check periodically to see if the ubiquity container has changed, and then do..." etc. I'm sure there's a well-known process for this (some notification event in NSDocument or something), but I've searched around and everything I find is either for iOS/UIDocuments instead of OS X/NSDocuments, or it's all theory and over my head without any tangible code samples to comb through and pick apart.
Can anyone help me out with a method for registering that an iCloud document in the ubiquity container has changed, and ideally where to put it (AppDelegate, CloudDocument.m, etc)? I only have one file syncing around, signified by the constant kAllNotes, so I don't need to track a bunch of different files or anything. I'm pretty sure I can use the code that runs at launch to do what needs to be done, I just can't figure out what to do to start the auto-syncing process.
Thank you in advance!
PS I'm still a beginner, so tutorials and code samples are much appreciated.
You're looking for NSMetadataQuery - it does a spotlight-like, continuously running search for any type of file - and is available on both iOS and OS X (indeed on iOS it can only be used for observing changes to the ubiquity container). I don't have any one link on how to use this - the Apple docs are too general to make much sense initially but do a search on 'NSMetadataQuery iCloud' and you should be sorted - there is tons of information out there on this topic.
With NSMetadataQuery you receive a notification every time something in the observed folder system changes, and it's not just applicable to UIDocument files even if a lot of examples are worried about UIDocuments.
Indeed, NSMetadataQuery it is - it feels like a hack but appears to be the only way to monitor changes to standard (non-UIDocument) files.
Here's a sample project I've compiled. It lets you upload an image from the camera roll, then monitors changes to the ubiquitous Documents folder:
https://github.com/versluis/iCloud-Images
I wondering how you would retrieve the path of the file that a user has dragged and dropped into a cocoa application. For example: User drags a file named test from his/her desktop. Then the cocoa application would say: Users/currentusername/Desktop/test
Thanks for the help!
I just downloaded Apple's "CocoaDragAndDrop" sample code and tried it out.
When I drag in a PNG file from the Finder into the running app, the title of the window changes to the path of the image that was dragged in.
Looking inside the sample code, I can see a file URL is included in the Pasteboard:
//if the drag comes from a file, set the window title to the filename
fileURL=[NSURL URLFromPasteboard: [sender draggingPasteboard]];
[[self window] setTitle: fileURL!=NULL ? [fileURL absoluteString] : #"(no name)"];
Try this technique in your own code and modify it for taste.
The accepted answer is no longer working with Xcode 6.
I've found this methode to get the same result:
NSURL*fileURL = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
NSString *filePath = [fileURL path];
[[self window] setTitle:filePath];
Currently working on developing a similar interface, I’ve understood that the OP had asked for path, not URL retrieval. It seems the suggested OS X 10.10 (XCode6) workaround for the accepted answer has issues in refusing to drag and drop content between windows.
However, avoiding declaring NSString *filePath, but simply substituting the [fileURL absoluteString] method with [fileURL path] method in line 175 of DragDropImageView.m of the suggested sample code instead, seems to solve it:
fileURL=[NSURL URLFromPasteboard: [sender draggingPasteboard]];
[[self window] setTitle: fileURL!=NULL ? [fileURL path] : #"(no name)"];
It compiles and runs as devised in Xcode4 through Xcode6, SDK 10.8-10.10, AFAICT.
Hope this can help.
I am having a trouble try to display a NSWindow with out using Interface Builder. The initialization of the window was quite confusing since I am more familiar with iPhone (which does not have an NSWindow equivalent). So I searched Google for some code and I eventually found this:
NSRect windowRect = NSMakeRect(10.0f, 10.0f, 800.0f, 600.0f);
NSWindow *window = [[NSWindow alloc] initWithContentRect:windowRect
styleMask:( NSResizableWindowMask | NSClosableWindowMask | NSTitledWindowMask)
backing:NSBackingStoreBuffered defer:NO];
[window makeKeyAndOrderFront:nil];
So I copied that code and placed it in the applicationDidFinishLaunching and thought all would be good. But all is not good. Xcode did not display any errors (or warnings) in the Build Results. But, I do get this message in the display log:
2010-06-26 13:33:47.170 FooApp[283:a0f] Could not connect the action buttonPressed: to target of class NSApplication
I don't know how to interpret this as Google has failed me on searching for a solution on this display log error. And, as far as I can tell, I have no actions at the moment including a buttonPressed one. As a side note: I do not know if this is relevant or not, but I deleted the Main Window.xib and its accompanying property in the info.plist.
Any help would be greatly appreciated.
UPDATE: I tried doing some printf debugging (never really bothered learning NSLog) and the thing won't even printf if the thing is at the very beginning of the appliactionDidFinishLaunching or even worst, at the start of main (before the return if incase some of you are tempted to ask me if I put the printf before or after the return statement).
MainWindow.xib is part of the iphone App template, isn't it? What exactly did you delete? You still have the MainMenu.xib, right?
As you have discovered, having a nib file is not optional for a Cocoa app. You must have at least one nib (or xib, for you youngsters) and it must have a main menu in it.
I want to open NSPersistentDocuments and load them into the same window one at a time. I'm almost there but missing some steps. Hopefully someone can help me.
I have a few saved documents on the hard drive. On launch my app opens to an untitled NSPersistentDocument and creates a separate NSWindowController. When I press the button to load file 1 off the hard drive the data appears in the fields but two things are wrong that I can see:
1) changing the data doesn't make the document dirty
2) choosing save updates the persistentstore (I know this because when I open the file again I see the changes) but I get an error: +entityForName: could not locate an NSManagedObjectModel for entity name 'Book'
Here's my code which is in the WindowController that was launched initially with the untitled document. This code isn't perfect. For example, I know I should processPendingChanges and save the current doc before I load the new one. This is test code to try to get over this hurdle.
- (IBAction)newBookTwo:(id)sender {
NSDocumentController *dc = [NSDocumentController sharedDocumentController];
NSURL *url = [NSURL fileURLWithPath:[#"~/Desktop/File 2.binary" stringByExpandingTildeInPath]];
NSError *error;
MainWindowDocument *thisDoc = [dc openDocumentWithContentsOfURL:url display:NO error:&error];
[self setDocument:thisDoc];
[self setManagedObjectContext:[thisDoc managedObjectContext]];
}
Thanks!
Assuming you have an entity called "Book" or "book" then the second problem is most likely a typo swapping case. Depending on the entity graph that may be triggering your first problem as well.
Put a break point in there and see if your model is nil.
The Background
I've built a source list (similar to iTunes et al.) in my Cocoa app.
I've got an NSOutlineView, with Value
column bound to arrangedObjects.name
key path of an NSTreeController.
The NSTreeController accesses
JGSourceListNode entities in a Core
Data store.
I have three subclasses of
JGSourceListNode - JGProjectNode,
JGGroupNode and JGFolderNode.
I have selectedIndexPaths on NSTreeController bound to an NSArray called selectedIndexPaths in my App Delegate.
On startup, I search for group nodes and if they're not found in the core data store I create them:
if ([allGroupNodes count] == 0) {
JGGroupNode *rootTrainingNode = [JGGroupNode insertInManagedObjectContext:context];
[rootTrainingNode setNodeName:#"TRAIN"];
JGProjectNode *childUntrainedNode = [JGProjectNode insertInManagedObjectContext:context];
[childUntrainedNode setParent:rootTrainingNode];
[childUntrainedNode setNodeName:#"Untrained"];
JGGroupNode *rootBrowsingNode = [JGGroupNode insertInManagedObjectContext:context];
[rootBrowsingNode setNodeName:#"BROWSE"];
JGFolderNode *childFolder = [JGFolderNode insertInManagedObjectContext:context];
[childFolder setNodeName:#"Folder"];
[childFolder setParent:rootBrowsingNode];
[context save:nil];
}
What I Want
When I start the app, I want both top level groups to be expanded and "Untrained" to be highlighted as shown:
My Window http://synapticmishap.co.uk/Window.jpeg
The Problem
I put the following code in the applicationDidFinishLaunching: method of the app delegate:
[sourceListOutlineView expandItem:[sourceListOutlineView itemAtRow:0]];
[sourceListOutlineView expandItem:[sourceListOutlineView itemAtRow:2]];
NSIndexPath *rootIndexPath = [NSIndexPath indexPathWithIndex:0];
NSIndexPath *childIndexPath = [rootIndexPath indexPathByAddingIndex:0];
[self setSelectedIndexPaths:[NSArray arrayWithObject:childIndexPath]];
but the outline view seems to not have been prepared yet, so this code does nothing.
Ideally, eventually I want to save the last selection the user had made and restore this on a restart.
The Question
I'm sure it's possible using some crazy KVO to observe when the NSTreeController or NSOutlineView gets populated then expand the items and change the selection, but that feels clumsy and too much like a work around.
How would I do this elegantly?
Elegantly? This isn't elegant but it's how I'm doing it. I just do it manually. At app quit I write this value to user defaults:
lastSelectedRow = [outlineView selectedRow]
Then at app launch I run this in app did finish launching:
[self performSelector:#selector(selectLastNoteOrCreateDefaultNote) withObject:nil afterDelay:1];
Notice I just use a delay because I noticed the same as you that "the outline view seems to not have been prepared yet". Then in that selector I use this.
[outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:lastSelectedRow] byExtendingSelection:NO];
It works but better (more elegant) solutions are welcome from me too.