Cocoa Mac Application Title says: "untitled" - objective-c

I have created a document based Mac OSX application, and when I'm editing in Interface Builder, the title is correct (I filled out that portion of the inspector) but once the program runs, the application title is 'Untitled'. How can I change it? In my IB Doc Window, I have instances of Files Owner, First Responder, NSApplication, and NSWindow. There is no view controller, is that the issue? I'm new to Cocoa..

One solution is to override -displayName in your NSDocument subclass:
- (NSString *)displayName {
if (![self fileURL])
return #"Some custom untitled string";
return [super displayName];
}
You can also check out NSWindowController's -windowTitleForDocumentDisplayName: if you're using custom window controllers.

you have created a document based Cocoa application. For new documents, Cocoa sets the proposed name of the document to 'Untitled'.

That's because you checked Create Document-Based Application when you created this project:
You can remove it from info.plist by clicking the - button next to Document types:
Type in your own title in Storyboard and check the window to "is Inital Controller". After you run your project again, it will be OK.

Do you mean the application menu title? That is changed to match the name of the application at runtime. The simplest way to change it would be to change the Product Name build setting on your target in Xcode.

- (NSString *)displayName
{
NSMutableString *displayName = [NSMutableString stringWithString:[super displayName]];
if ([self fileURL] == nil) {
NSString *firstCharacter = [[displayName substringToIndex:1] lowercaseString];
[displayName deleteCharactersInRange:NSMakeRange(0, 1)];
[displayName insertString:firstCharacter atIndex:0];
}
return [NSString stringWithString:displayName];
}

Related

Text Replace Service with Xcode - Not replacing selected text

I am trying to build a standalone system service (app with .service extension, saved to ~/Library/Services/) to replace user-selected text in Mac OS X.
I want to build it with Xcode and not Automator, because I am more accustomed to Objective-C than Applescript.
I found several examples on the internet, e.g. this and also Apple's documentation. I got the Xcode project appropriately configured and building without problems. However, when I install my service and try to use it, nothing happens.
The service method itself is executed: I placed code to show an NSAlert inside its method body and it shows. However, the selected text does not get replaced.
Any idea what might be missing? This is the method that implements the service:
- (void) fixPath:(NSPasteboard*) pboard
userData:(NSString*) userData
error:(NSString**) error
{
// Make sure the pasteboard contains a string.
if (![pboard canReadObjectForClasses:#[[NSString class]] options:#{}])
{
*error = NSLocalizedString(#"Error: the pasteboard doesn't contain a string.", nil);
return;
}
NSString* pasteboardString = [pboard stringForType:NSPasteboardTypeString];
//NSAlert* alert = [[NSAlert alloc] init];
//[alert setMessageText:#"WORKING!"];
//[alert runModal];
// ^ This alert is displayed when selecting the service in the context menu
pasteboardString = #"NEW TEXT";
NSArray* types = [NSArray arrayWithObject:NSStringPboardType];
[pboard clearContents];
[pboard declareTypes:types owner:nil];
// Set new text:
[pboard writeObjects:[NSArray arrayWithObject:pasteboardString]];
// Alternatively:
[pboard setString:pasteboardString forType:NSStringPboardType];
// (neither works)
return;
}
After careful reading of Apple's documentation, I found the answer: My service app's plist file was missing a key under the Services section:
<key>NSReturnTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
I only had the opposite NSSendTypes key, which lets you send data from the client app to the service. This one is needed to send the modified text back (in the other direction).
It is weird because, Apple's documentation seems to imply that specifying these two is no longer necessary since 10.6 (Snow Leopard).
For (hopefully) useful console spew, in terminal type:
defaults write -g ViewBridgeLogging -bool YES
Note: useful for services and extensions also.

Cocoa Document Won't Open Again After It's Closed

This is a pretty weird issue. I have a table in my Cocoa application that displays a list of recently opened files. You can double click on an entry to open the associated file. Trouble is, though, after the file is opened once, whether through the Open panel, the Recent Documents menu, or through the aforementioned table, it can't be opened again until the application has quit and re-opened. Other documents, however, can be opened, but once they're closed they can't be opened again either.
This is pretty odd behavior, and I'm not sure what's causing it. But it's certainly annoying. For reference, the Release on Closed attribute of the window from Xcode does nothing and, if selected, does not do anything. I can't think of any other attributes which might cause this behavior. For reference, here's a photo of the attributes panel:
Here's the code for the table which opens the recently opened file:
- (void)respondToRecentFileDoubleClick {
NSInteger clickedRow = [_recentFileBrowser clickedRow];
if (clickedRow != -1) { // We're in the row.
NSDocumentController *docControl = [NSDocumentController sharedDocumentController];
NSURL *selectedDocument = (NSURL *)[docControl recentDocumentURLs][clickedRow];
NSLog(#"Selected row %ld.", (long)clickedRow);
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:selectedDocument display:YES completionHandler:nil];
}
}
The documentation for openDocumentWithContentsOfURL: says that the document won't be opened if it's already opened, but in this case, all of the document windows are closed, so that can't be what causes this behavior. And the NSLog() statement inside the if block prints, so I know the code is being executed.
Anyone know what might be causing this bizarre issue?
From the Xcode image, it appears that you are using your own WindowController. The default close menu item is wired up to call performClose. The performClose method is implemented in NSWindow. So, what is happening is that the window is closing, but the document is not removed from the open document list. Try adding < NSWindowDelegate> to your WindowController interface (in the .h file). Then add to your WindowController .m file:
- (void) windowWillClose:(NSNotification *)notification {
[ourdoc close];
}
Substitute whatever variable you using to hold your document reference for ourdoc. Typically the method setDocument will get called with your document reference. (Also in your WindowController.)
- (void) setDocument:(NSDocument *)document {
ourdoc = (yourNSDocumentsubclass *)document;
}
completionHandler:nil change for debugging:
completionHandler:^(NSDocument *doc, BOOL documentWasAlreadyOpened, NSError *error) {
if (documentWasAlreadyOpened) {
NSLog(#"document was already opened");
NSArray *rats = [[NSDocumentController sharedDocumentController] documents];
NSLog(#"%s seriously: %#", __PRETTY_FUNCTION__, rats);
}
}

Document based OSX app - Limit number of open documents to one

I'm trying to figure out how to limit my NSDocument based application to one open document at a time. It is quickly becoming a mess.
Has anyone been able to do this in a straightforward & reliable way?
////EDIT////
I would like to be able to prompt the user to save an existing open document and close it before creating/opening a new document.
////EDIT 2
I'm now trying to just return an error with an appropriate message if any documents are opening -- however, the error message is not displaying my NSLocalizedKeyDescription. This is in my NSDocumentController subclass.
-(id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError{
if([self.documents count]){
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObject:#"Only one document can be open at a time. Please close your document." forKey:NSLocalizedDescriptionKey];
*outError = [NSError errorWithDomain:#"Error" code:192 userInfo:dict];
return nil;
}
return [super openUntitledDocumentAndDisplay:displayDocument error:outError];
}
It won't be an easy solution, since it's a pretty complex class, but I would suggest that you subclass NSDocumentController and register your own which disables opening beyond a certain number of documents. This will allow you to prevent things like opening files by dropping them on the application's icon in the dock or opening in the finder, both of which bypass the Open menu item.
You will still need to override the GUI/menu activation code to prevent Open... from being available when you have a document open already, but that's just to make sure you don't confuse the user.
Your document controller needs to be created before any other document controllers, but that's easy to do by placing a DocumentController instance in your MainMenu.xib and making sure the class is set to your subclass. (This will cause it to call -sharedDocumentController, which will create an instance of yours.)
In your document controller, then, you will need to override:
- makeDocumentForURL:withContentsOfURL:ofType:error:
- makeUntitledDocumentOfType:error:
- makeDocumentWithContentsOfURL:ofType:error:
to check and see if a document is already open and return nil, setting the error pointer to a newly created error that shows an appropriate message (NSLocalizedDescriptionKey).
That should take care of cases of drag-and-drop, applescript,etc.
EDIT
As for your additional request of the close/save prompt on an opening event, that's a nastier problem. You could:
Save off the information (basically the arguments for the make requests)
Send the -closeAllDocumentsWithDelegate:didCloseAllSelector:contextInfo: with self as a delegate and a newly-created routine as the selector
When you receive the selector, then either clear out the saved arguments, or re-execute the commands with the arguments you saved.
Note that step 2 and 3 might need to be done on delay with performSelector
I haven't tried this myself (the rest I've done before), but it seems like it should work.
Here's the solution I ended up with. All of this is in a NSDocumentController subclass.
- (NSInteger)runModalOpenPanel:(NSOpenPanel *)openPanel forTypes:(NSArray *)extensions{
[openPanel setAllowsMultipleSelection:NO];
return [super runModalOpenPanel:openPanel forTypes:extensions];
}
-(NSUInteger)maximumRecentDocumentCount{
return 0;
}
-(void)newDocument:(id)sender{
if ([self.documents count]) {
[super closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(newDocument:didCloseAll:contextInfo:) contextInfo:(void*)sender];
}
else{
[super newDocument:sender];
}
}
- (void)newDocument:(NSDocumentController *)docController didCloseAll: (BOOL)didCloseAll contextInfo:(void *)contextInfo{
if([self.documents count])return;
else [super newDocument:(__bridge id)contextInfo];
}
-(void)openDocument:(id)sender{
if ([self.documents count]) {
[super closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(openDocument:didCloseAll:contextInfo:) contextInfo:(void*)sender];
}
else{
[super openDocument:sender];
}
}
- (void)openDocument:(NSDocumentController *)docController didCloseAll: (BOOL)didCloseAll contextInfo:(void *)contextInfo{
if([self.documents count])return;
else [super openDocument:(__bridge id)contextInfo];
}
Also, I unfortunately needed to remove the "Open Recent" option from the Main Menu. I haven't figured out how to get around that situation.

Setting NSMatrix selection programmatically causing mutex lock when trying to later get selected cell

Overview
I'm seeing a strange issue in a Mac OS X Cocoa app I'm developing w/Xcode 4.3.2 and testing on Mac OS X 10.7.5 (targeting Mac OS X 10.6): I have a basic NSMatrix radio group in my main NIB that has one outlet and one action in my main controller, but is also used in two other methods (also in the main controller). The action saves the selected tag to NSUserDefaults & enables/disables an NSTextField based on which is selected, one method is a preferences loader and so selects a cell by tag upon load, and the other method looks up it's selected tag for other logic (code samples below).
Basic stuff, but in one case (when only having selected a cell in the NSMatrix programmatically, not via clicking a radio button in the GUI), the app PWODs in a mutex lock.
The Code & Functionality
I've renamed variables & methods below, but have not changed any of the other structure of the code. passwordRadioGroup is the NSMatrix* IBOutlet.
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
// get logging up and running
[self initLogging];
// load various prefs
// ...
[self loadPasswordSettings];
}
- (void)loadPasswordSettings {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// load password settings from our prefs
NSNumber *passwordTypeTag = [defaults objectForKey:kPasswordTypeDefaultKey];
if ( passwordTypeTag != nil ) {
[passwordRadioGroup selectCellWithTag:[passwordTypeTag intValue]];
} else {
[passwordRadioGroup selectCellWithTag:kPasswordTypeRadioRandomTag];
}
[self selectPasswordType:passwordRadioGroup];
// ...
}
- (IBAction)selectPasswordType:(NSMatrix *)sender {
NSInteger tag = [[sender selectedCell] tag];
switch ( tag ) {
case kPasswordTypeRadioRandomTag:
// disable the password text field
[passwordField setEnabled:NO];
break;
case kPasswordTypeRadioManualTag:
// enable the password text field
[passwordField setEnabled:YES];
break;
}
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:tag] forKey:kPasswordTypeDefaultKey];
}
- (NSString *)generateUserPassword {
NSString *password;
// which type of password should we be generating (random or from a specifed one)?
NSInteger tag = [[passwordRadioGroup selectedCell] tag];
switch ( tag ) {
// manual password
case kPasswordTypeRadioManualTag:
password = [passwordField stringValue];
break;
// random password
case kPasswordTypeRadioRandomTag:
// password = randomly generated password;
break;
}
return password;
}
When the app is launched, my main controller is sent a applicationDidFinishLaunching message which then sends the loadPasswordSettings message to itself. loadPasswordSettings then sends [[passwordRadioGroup selectedCell] tag] and sends [passwordRadioGroup selectCellWithTag:]. This works correctly and the correct radio button is selected in the GUI. It finally calls [self selectPasswordType:passwordRadioGroup (which also successfully calls [[passwordRadioGroup selectedCell] tag]) and appropriately enables/disables the text field and writes the tag back out to NSUserDefaults (usually, but not always redundant).
You can select any of the radio buttons, which sends a selectPasswordType: message to my main controller (passing it the instance of passwordRadioGroup; I've verified the memory address in the debugger and can inspect its ivars). This successfully calls [[passwordRadioGroup selectedCell] tag] and saves the tag to NSUserDefaults.
You can do the above two as many times as you like without issue. Quitting & relaunching correctly restores the radio buttons to the state you left them last, so it's definitely correctly getting the selected tag, storing it in NSUser defaults, retrieving it from NSUserDefaults, and setting the selected cell by tag on the NSMatrix.
Here's where it gets screwy:
There's another button that, when clicked, does a bunch of other work and ultimately then sends the generateUserPassword message to itself to generate a password (again, this is all in the main controller and running in the main thread). What's the first thing that it does? Calls [[passwordRadioGroup selectedCell] tag]. Fine, you can safely do that as much as you want, as illustrated above, right?
If you select one of the radio buttons, therefore changing the selected cell via the GUI and sending the selectPasswordType: message to my main controller again, yes. You will encounter no issues (although, I admit it seems a bit slow and PWODs for a second).
If you do not click on the NSMatrix after launch (so not forcing the selection/action again from the GUI), that [[passwordRadioGroup selectedCell] tag] call in generateUserPassword will immediately PWOD. If I hit the pause button in Xcode's debugger to see where it's at, it's always in psych_mutexwait (called from class_lookupMethodAndLoadCache) in the main thread. If selectPasswordType: weren't called programmatically and able to run [[passwordRadioGroup selectedCell] tag] without issue, I'd have at least some sanity left.
Help!
To reiterate, I've followed this through in the debugger and can verify that the memory address & ivars confirm that passwordRadioGroup is not being changed out from under me, nor is it being deallocated (I've tried with & without "Zombie Objects" enabled). The only references to passwordRadioGroup in my main controller are those seen above. Googling for all sorts of NSMatrix/radio/selectCellWithTag/selectedCell/selectedTag/class_lookupMethodAndLoadCache/mutex terms/combinations has not been fruitful.
Any solutions, troubleshooting suggestions, or thoughts would be greatly appreciated. Slaps for stupidity also welcome, if deserved.

Detect if NSString contains a URL and generate a "link" to open inside the app a Safari View

I have am reading a twitter feed in my iPhone application and can do it correctly, but I'd like to evolve it in a way to detect if the whole NSString contains any URL or URLs and create a "link" that will open a UIWebView within the same application.
Can you guide me on how to perform this task?
-(void) setTextTwitter:(NSString *)text WithDate:(NSString*)date
{
[text retain];
[textTwitter release], textTwitter = nil;
textTwitter = text;
[date retain];
[dateTwitter release], dateTwitter = nil;
dateTwitter = date;
[self setNeedsDisplay];
}
Check out Craig Hockenberry's IFTweetLabel, which can be found here.
Or you can use the label provided by Three20 library, which can be found here.
Or the simplest solution: use UIWebView with dataDetectorTypes set to UIDataDetectorTypeLink.
Cheers, Paul