How to change a deprecated beginSheetForDirectory method - objective-c

I have an app that was using beginSheetForDirectory:file:modalForWindow:modalDelegate:didEndSelector:contextInfo:.
I checked Apple documentation which said it's deprecated and to use another method instead:
Presents a Save panel as a sheet with a specified path and,
optionally, a specified file in that path. (Deprecated in Mac OS X
v10.6. Use beginSheetModalForWindow:completionHandler: instead.)
My question is how to change this code to the new one?
// [savePanel setRequiredFileType:#"png"];
[savePanel beginSheetForDirectory:nil
file:nil
modalForWindow:[self window]
modalDelegate:self
didEndSelector:#selector(didEndSaveSheet:returnCode:conextInfo:)
contextInfo:NULL];

You're looking for the beginSheetModalForWindow:completionHandler: method.
Example:
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel beginSheetModalForWindow:_window completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
NSURL *savePath = [[savePanel URLs] objectAtIndex:0];
} else {
[savePanel close];
}
}];

Related

Objective C - Asking to save before you quit

I have an Objective-C/Cocoa text-editor I'm working on(It's a mac app, not iOS).
The current challenge I'm facing is having a dialog when someone try to quit without saving.
I already have a shared bool called issavedsomewhere to tell if the user has saved or not. I even have the textview data available as a shared variable, so I can access it from any class.
I'm thinking that I'd put the save dialog in the (void)applicationWillTerminate method.
My current saving code is simple:
NSSavePanel *panel = [NSSavePanel savePanel];
// NSInteger result;
[panel setAllowedFileTypes:#[#"txt"]];
[panel beginWithCompletionHandler:^(NSInteger result){
//OK button pushed
if (result == NSFileHandlingPanelOKButton) {
// Close panel before handling errors
[panel orderOut:self];
// Do what you need to do with the selected path
NSString *selpath = [[panel URL] path];
NSError *error;
BOOL didOK = [[theDATA.textvieww string]writeToFile:selpath atomically:NO encoding:NSUTF8StringEncoding error:&error];
if(!didOK){
//error while saving
NSLog(#"Couldn't Save!!! -> %#", [error localizedFailureReason]);
}else{
//success!
theDATA.issavedsomewhere=YES;
theDATA.filepath=selpath;
theDATA.filename=[[[panel URL] path] lastPathComponent];
}
}/*Button other than the OK button was pushed*/
else{
}
}];
All it is, is an NSSavePanel that pops up and asks where you want to save.
The problem is that when I add it to (void)applicationWillTerminate, it doesn't wait for the user to answer.
Your help and ideas are appreciated:)
There are better ways to do this within the Cocoa framework, such as by using NSDocument and its ilk. However, it is possible to do what you want to do.
You first want to return NSTerminateLater in applicationShouldTerminate::
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
if (theDATA.issavedsomewhere) {
return NSTerminateLater;
}
return NSTerminateNow;
}
Then, you handler should ultimately call [NSApp replyToApplicationShouldTerminate:YES] when it is done:
NSSavePanel *panel = [NSSavePanel savePanel];
// NSInteger result;
[panel setAllowedFileTypes:#[#"txt"]];
[panel beginWithCompletionHandler:^(NSInteger result){
//OK button pushed
if (result == NSFileHandlingPanelOKButton) {
// Close panel before handling errors
[panel orderOut:self];
// Do what you need to do with the selected path
NSString *selpath = [[panel URL] path];
NSError *error;
BOOL didOK = [[theDATA.textvieww string]writeToFile:selpath atomically:NO encoding:NSUTF8StringEncoding error:&error];
if(!didOK){
//error while saving
NSLog(#"Couldn't Save!!! -> %#", [error localizedFailureReason]);
}else{
//success!
theDATA.issavedsomewhere=YES;
theDATA.filepath=selpath;
theDATA.filename=[[[panel URL] path] lastPathComponent];
}
}/*Button other than the OK button was pushed*/
else{
}
[NSApp replyToApplicationShouldTerminate:YES];
}];
One possibility is to just save in a temp file and on launch check to see if the tempfile exists and perhaps ask the user if he want to use it or not.
Since changes to my data can happen in multiple places I simply post a "data modified" notification whenever this happens:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"DataModifiedNotification"
object:self];
My app delegate has a dataSaved property and adds itself as an observer of this notification and sets its value to NO whenever the data is mutated:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.dataSaved = YES; // set to NO when data mutated
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(receiveDataModifiedNotification:)
name:#"DataModifiedNotification"
object:nil];
}
-(void)receiveDataModifiedNotification:(NSNotification *) notification {
self.dataSaved = NO;
}
The the app delegate asks the user if they really want to quit to give
them the opportunity to save the data (done elsewhere):
-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
if (!self.dataSaved) {
NSAlert *alert = [[NSAlert alloc] init];
alert.alertStyle = NSAlertStyleWarning;
alert.messageText = #"Data unsaved!";
alert.informativeText = #"Do you really want to Quit the application?";
[alert addButtonWithTitle:#"Quit"];
[alert addButtonWithTitle:#"Cancel"];
[alert beginSheetModalForWindow:self.window
completionHandler:^(NSModalResponse returnCode) {
const BOOL shouldQuit = returnCode == NSAlertFirstButtonReturn;
[NSApp replyToApplicationShouldTerminate: shouldQuit];
}];
return NSTerminateLater;
}
return NSTerminateNow;
}
Note: Set app property NSSupportsSuddenTermination to NO
which is labeled "Application can be killed immediately when user is shutting down or logging out" in Info.plist.

NSSavePanel Changing File Name Extensions With AccessoryView

I have NSSavePanel with accessoryView to let the user select a graphic format so that they can save an image (NSImage) as a file. So far, I have the following. (I'm skipping some lines to make it short.)
- (void)exportFile {
NSString *filename;
if (formatIndex1 == 0) { // Default selection by user in Preferences
filename = #"Untitled.bmp";
}
else if (formatIndex1 == 1) {
filename = #"Untitled.gif";
}
...
[panel setAllowedFileTypes:[[NSArray alloc] initWithObjects:#"bmp",#"gif",#"jpg",#"jp2",#"png",nil]];
[panel setAllowsOtherFileTypes:NO];
[panel setExtensionHidden:NO];
[panel setCanCreateDirectories:YES];
[panel setNameFieldStringValue:filename];
[panel setAccessoryView:accessoryView1];
[formatMenu1 setAction:#selector(dropMenuChange:)]; // formatMenu1 is NSPopUpButton
[formatMenu1 setTarget:self];
[panel beginSheetModalForWindow:window completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
// getting panel url
}
}];
}
-(void)dropMenuChange:(NSPopUpButton *)sender {
NSSavePanel *savePanel = (NSSavePanel *)[sender window];
[savePanel setNameFieldStringValue:#"..."];
}
I'm not 100% sure that I'm doing it right. What I want to achieve is that I want to append the right extension to the current file name whenever the user selects a file format on accessoryView's NSPopUpButton. Is there a magical way of doing that? Or do I have to set the current file name with the right extension to setNameFieldStringValue programmatically for myself?
Thank you for your help.
What I want to achieve is that I want to append the right extension to the current file name whenever the user selects a file format on accessoryView's NSPopUpButton. Is there a magical way of doing that?
Yes, there is. You need not do it yourself with setNameFieldStringValue: , let the savePanel do it. Let us assume fileName is a full path like /Users/hg/Pictures/2013/08/Airplanes/pic123.png and it exists an accessoryView for the savePanel with a matrix of radio buttons. Each button has a title like #"jpg" or #"png" or... The action of the matrix is -selectFileType:
- (IBAction) selectFileType:(id)sender
{
[savePanel setAllowedFileTypes:#[ [[sender selectedCell] title] ] ];
// this will set the right extension
}
For using the savePanel I tried the following code:
- (void) saveImage:(NSImage *) theImg
{
savePanel = [NSSavePanel savePanel];
NSString *imageName = [fileName lastPathComponent];
NSString *suffix = [imageName pathExtension];
NSString *baseName = [imageName stringByDeletingPathExtension];
// prepare the savePanel
[savePanel setAccessoryView:accessoryView];
[savePanel setAllowedFileTypes:#[ suffix ] ];
[savePanel setDirectoryURL:[NSURL fileURLWithPath:fileName]]; // convert to URL
[savePanel setNameFieldStringValue:baseName ]; // without extension !
// savePanel does append the suffix
// and now start the savePanel and choose the wanted fileType
int rtn = [savePanel runModal]; // preferred method since 10.6
if( rtn==NSFileHandlingPanelCancelButton) return; // do nothing
// finally create and save the file
if( [[[savePanel allowedFileTypes] objectAtIndex:0] isEqualToString:#"jpg" ){
// save as jpg-file
}
// check for other fileTypes
. . .
}
You have to set current file name with the right extension using setNameFieldStringValue
-(void)dropMenuChange:(NSPopUpButton *)sender {
NSSavePanel *savePanel = (NSSavePanel *)[sender window];
NSString *nameFieldString = [savePanel nameFieldStringValue];
NSString *nameFieldStringWithExt = [NSString stringWithFormat:#"%#.%#",[savePanel nameFieldStringValue], popupvalue];
[savePanel setNameFieldStringValue:nameFieldStringWithExt];
}

Accept drag'n'drop from iPhoto or Aperture

I've created an app, containing an ImageView subclass which accepts drag'n'dropping files/folders directly from Finder.
The thing is I'm now trying to make it accept photos, either from iPhoto or Aperture, as well.
Which PboardTypes should I register for?
All I'm currently doing is :
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
Any ideas?
Using Pasteboard Peeker (from Apple) shows me that Aperture gives you file names/URLs as well as "aperture image data" (whatever that is). iPhoto appears only to give "ImageDataListPboardType", which is a PLIST. I'm guessing you could NSLog() that out to see its structure and pull the image information from it. It may possibly include the filename/URL info as well as the actual image as data.
You are correct to register for NSFilenamesPboardType. To complete the task:
1: Make sure you accept the copy operation in draggingEntered. The generic operation is insufficient.
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
NSPasteboard *pasteboard = [sender draggingPasteboard];
if ( [[pasteboard types] containsObject:NSFilenamesPboardType] ) {
if (sourceDragMask & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}
2: There will be one filename per photo. Do something with them.
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pasteboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:NSFilenamesPboardType])
{
NSData* data = [pasteboard dataForType:NSFilenamesPboardType];
if(data)
{
NSString *errorDescription;
NSArray *filenames = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:kCFPropertyListImmutable format:nil errorDescription:&errorDescription];
for (NSString* filename in filenames)
{
NSImage* image = [[NSImage alloc]initWithContentsOfFile:filename];
//Do something with the image
}
}
}
return YES;
}

Opening 'New' Document?

I have some code implemented in my myDocument.m file that simply attempts to load the last used document upon launch. However, upon launching from a fresh install, (or running after deleting the last used file) a 'new' document window does not appear. Does anyone know what to add to my code to do this? Here it is:
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
NSURL *lastURL=[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:lastURL display:YES error:nil];
if (lastURL!=nil)
{
[docController openDocumentWithContentsOfURL:lastURL display:YES error:nil];
return NO;
}
return YES;
}
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
NSArray* urls = [[NSDocumentController sharedDocumentController] recentDocumentURLs];
if ([urls count] > 0){
NSURL *lastURL= [urls objectAtIndex: 0];
if ([[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:lastURL display:YES error:nil]){
return NO;
}
}
return YES;
}
EDIT
I changed it and tried it out it should work now.
What is docController, and why are you sending -openDocumentWithContentsOfURL:display:error: twice? Note that that method returns a document, not a URL, so using the return value as a URL wouldn’t work anyway.
The following is a cleaner, equivalent code:
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
id lastDoc = [[NSDocumentController sharedDocumentController]
openDocumentWithContentsOfURL:lastURL
display:YES error:NULL];
return (lastDoc == nil);
}
However, it still doesn’t explain why you don’t get an untitled document. What happens if you comment out -applicationShouldOpenUntitledFile: so that the application follows standard Cocoa behaviour? It could be the case that the problem lies elsewhere.

How to add file contents by dragging that file

I am using the following code to perform drag operation on NSTextView object.
- (BOOL)performDragOperation:(id )sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] )
{
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
if ([[fileURL path] hasSuffix:#"plist"])
{
NSString *code = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:NULL];
int cnt = [[self string] length];
if (cnt) [self setSelectedRange:NSMakeRange(0, cnt)];
[self insertText:code];
return YES;
}
}
return NO;
}
I have declared this method in the .h file as well.
But after running the code it showing following warnings.
warning: 'AppConroller' may not respond to '-string'
(Messages without a matching method signature will be assumed to return 'id' and accept'...' as arguments.)
warning: 'AppConroller' may not respond to '-setSelectedRange:'
warning: 'AppConroller' may not respond to '-insertText:'
You're sending self (the AppController) messages it doesn't support. I think you mean to have [sender string], [sender setSelectedRange:], and [sender insertText:].
Is it possible it's just a typo? All your warnings refer to "AppConroller", but I bet the class is actually named "AppController".