views:

155

answers:

2

I'm writing a preference pane to control a script I created, and I want to show a sheet on first launch so the user can tell the pane where they installed the script. The sheet appears fine, the problem is that it appears right after you click the button for my pane. This causes System Preferences to Segmentation Fault immediately.

I'm using the command to show the sheet inside awakeFromNib, which may have something to do with it, but I can't get anything inside of mainViewDidLoad to actually execute. Any ideas on what I can do?

Code:

- (void) awakeFromNib
{
NSLog(@"hi");
[sheetController setParentWindow:[NSApp mainWindow]];
BashScript *script = [[BashScript alloc] init];
if (![script loadScriptFromLocation:[self retrieveScriptLocation]])
{
    NSLog(@"Error loading script.");
}
else {
    [advancedEditor setString:[script getScript]];
}
}


- (NSString*) retrieveScriptLocation
{
NSUserDefaults *preferences = [[NSUserDefaults standardUserDefaults] retain];
NSString *location = [preferences stringForKey:@"scriptLocation"];
if (location != nil)
{
    return location;
}
else
{
    return [self askForScript];
}
}

- (NSString*) askForScript
{
[sheetController openSheet:nil];
}

EDIT: Here's the stack trace from the crash: http://files.spherecat1.com/prefpanestacktrace.txt

+1  A: 
  1. You could try delaying the opening of the sheet, performSelector:withObject:afterDelay: but that wouldn't be a good solution (for instance a slow mac would take a lot of time to load etc.

read the following : http://gemma.apple.com/mac/library/documentation/UserExperience/Conceptual/PreferencePanes/Tasks/Sample.html it might help as well.

I think its better if you would prompt (open the sheet) when your pref pane cannot find the path of the script in its preferences (e.g. path length < 1 or path == nil).

Anyway what I would try is - (void)willSelect; wich is sent before the pref pane is displayed and - (void)didSelect; wich is sent when the pref pane is actually displayed.

Also I wouldn't recommend using BWSheetController for your sheets, it is unneeded overhead and opening a sheet yourself is just as easy and you might want to set the parentWindow in IB (that's possible right with BWSheetController?)

Antwan van Houdt
Thanks for the info on willSelect and didSelect! I'm also using BWToolkit for other things, so I figured I might as well use it here. I will look into the normal method, though.
SphereCat1
+2  A: 

The stack trace reveals the cause of your crash:

- (void) awakeFromNib
{
  if (![script loadScriptFromLocation:[self retrieveScriptLocation]])

- (NSString*) retrieveScriptLocation
{
  return [self askForScript];

- (NSString*) askForScript
{
  [sheetController openSheet:nil];
}

You haven't returned a value from this method that you've declared as returning a value. The compiler should be warning you about this.

The crash happens because the method returns with effectively random bytes (possibly a valid object pointer, possibly not) where the return value should be. You can tell that this return value was supposed to be a string by looking at the attempted message in the stack trace:

Application Specific Information:
objc_msgSend() selector name: getFileSystemRepresentation:maxLength:

That's an NSString method, so something—in this case, the implementation behind -[NSData initWithContentsOfFile:] and -[NSFileManager contentsAtPath:]—tried to treat the address as that of an NSString. It isn't, which is what caused the crash, and is because you didn't return a string from a method where you said you would.

Of course, you can't just make the method return a string, because you don't have it to return yet. You need to refactor your code to reflect the asynchronous nature of the Open panel and sheets in general. Recognize that you do not have a string yet and will not for one or more minutes; just begin the Open panel sheet and don't attempt to load the script until it succeeds.

In a nutshell, try to load the script, and if you don't have a path yet, just begin the Open panel sheet and then continue on with other initialization.

You also shouldn't assume the Open panel will succeed. The user may cancel, especially if this is their first time opening the prefpane and they want to take a look around first. In fact, you shouldn't show an Open panel immediately on prefpane load in the first place.

Instead, have a file-picker view showing where the current script is, if there is one, and make clear, by its placement and explanatory text underneath it and possibly judicious application of color, that your program cannot work until the user chooses a script. Let the user choose the script and thereby enable your program whenever they're ready to.

Peter Hosey
There's so much useful info in here that I haven't soaked it all up yet! Thanks so much for a great answer.
SphereCat1