Create a sheet that isn't a warning message - objective-c

I have an app that reads data from a smartcard. Since doing so can take a while, I'm using a modal sheet to prevent interaction with the app while data is being read:
[_spinner startAnimation:self];
[NSApp beginSheet:_CardReadSheet modalForWindow:_window modalDelegate:self didEndSelector:#selector(endSheet:returnCode:contextInfo:) contextInfo:nil];
Where _CardReadSheet shows _spinner and a localized text to the effect of "Reading data, please wait", _window is my main window, and endSheet:returnCode:contextInfo just tells the sheet to close.
The problem is that using a modal sheet is apparently meant only as an error or warning message, so showing it to the user in this way results in the default warning sound being produced. This is not the intent.
How can I make it not produce a sound? If I'm using the wrong way to show the sheet, or if using a sheet is not the right thing to do, what should I do instead?

The warning sound is because you're asking NSApp to provide the sheet: it does so as an alert. I can't find an official source explaining why but this is what is happening.
To get a similar sheet but without any alert sound, call beginSheet on NSWindow instead of NSApp.
Quick example in Swift:
myExistingWindow.beginSheet(myNewSheet, completionHandler: nil)
And to close it:
myExistingWindow.endSheet(myNewSheet)

Related

Modal NSSavePanel disappears after animating in when begun from a completion block

G'day!
Note: Minimal example linked below. I'll refrain from longish code excerpts and rather explain the problem concisely.
I am in the process of updating an old (but small) Cocoa application to current APIs.
One of the places that looked easy enough at first: When the user tries to close the application window with unsaved changes, the app first displays an NSAlert asking "Save your stuff?". If that is confirmed a modal NSSavePanel is shown. In the original code they were opened via, respectively:
beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:
beginSheetForDirectory:file:modalForWindow:modalDelegate:didEndSelector:contextInfo:
Current Cocoa API uses completion blocks and thus the alert prefers to be shown via beginSheetModalForWindow:completionHandler:. So I moved the code from the didEndSelector into the completionHandler.
Unfortunately the modal NSSavePanel does animate in but disappears immediately together with the application main window if it is shown from the NSAlert's completion block. If I switch the alert back to the didEndSelector I can show the NSSavePanel either selector-basedly or completion block-ly just fine.
Here's the NSAlert's completion block that forwards to the disappearing save panel.
I have thought about threading issues. All of this is happening on the main thread. Maybe there's something subtle going on with run loop modes that I'm missing?
The minimal example is available over on GitHub. You can switch between selectors and blocks with defines in AppDelegate.h. All the interesting code is in AppDelegate.m. (Unless the problem is somewhere else...)
As #Willeke pointed out this wasn't an overly mysterious issue with threading and whatnot. No. It was just me having looked at the code way too often over the course of days.
The solution is simple:
The breaks in the switch statement in confirmUnsavedChanges are missing.

How can I open a NSOpenPanel not modally (since I'm opening to an existing sheet)

I have a NSPanel and I need to load a file path into one of its fields.
I'm currently using:
NSOpenPanel *browsePanel = [[NSOpenPanel alloc] init];
[browsePanel setCanChooseFiles:YES];
[browsePanel setCanChooseDirectories:NO];
[browsePanel setCanCreateDirectories:NO];
[browsePanel beginSheetForDirectory:nil
file:nil
types:nil
modalForWindow:[self window]
modalDelegate:self
didEndSelector:#selector(browsePanelPanelDidEnd:returnCode:contextInfo:)
contextInfo:[NSNumber numberWithInteger:[sender tag]]
];
But the NSOpenPanel sheet doesn't show up, and I don't get any error message in the console.
I guess it is because I'm opening it from an existing sheet and I can't load more than 1 sheet per time.
So I'm now using [browsePanel makeKeyAndOrderFront:[self window]]; but I get 2 problems:
the panel is always on the back of my modal sheet, I can't display it on top. Also, I can't move the focus which remains on the original sheet.
How do I assign the end selector, to process the data, if I don't load it modally ?
Thanks
From Presenting a Series of Sheets:
Cocoa does not support the notion of cascading, or nested sheets. Per Apple Human Interface Guidelines, “when the user responds to a sheet, and another sheet for that document opens, the first sheet must close before the second one opens.”
The doc goes on to tell you how to present a series of sheets, as the second opens it closes the first, etc.
You either need to adopt such a series or change your design so your first sheet, the one calling NSOpenPanel, is not a sheet at all.
FYI: In the sandboxed world NSOpenPanel is a very different beast while maintaining essentially the same UI. Among the changes are that NSOpenPanel is no longer a subclass of NSWindow... Tricks you might play now with NSOpenPanel may crash and burn under sandboxing, tread lightly.
You are using :
- (void)beginSheetForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes modalForWindow:(NSWindow *)docWindow modalDelegate:(id)delegate didEndSelector:(SEL)didEndSelector contextInfo:(void *)contextInfo NS_DEPRECATED_MAC(10_0, 10_6);
Which is deprecated. You should use
[browsePanel beginSheetModalForWindow:self.window completionHandler:nil];
EDIT:
Check this running project.
One sheet per window. More sheets means more windows and weird UI.

Scrolling process status window

As part of a Mac application I am working on, the user fills out a screen full of stuff and then presses a 'process' button. There are edits performed and if everything passes the edit, a couple of minute process is performed which either end ok or not. I would like to have that process spit out a series of status and processing messages into a separate scrolling window so that if something goes bad, the user can go back through the log and see if anything shows up there.
What would be the best objects and methods for me to review and use for this type of processing?
Added 11/24/2011
As per the first suggestion, I created a second XIB, created a NSWindowController to match and put it all together as some prep work. When the button in pressed in the app delegate, I have this thing do the following:
- (IBAction)runButtonPressed:(id)sender {
RunResultWindow *wc;
wc = [[RunResultWindow alloc] initWithWindowNibName:#"RunResultWindow"];
[wc showWindow:self];
}
RunResultWindow is the name of the XIB and the NSWindowController class that controls it. I also added a finish button and wired that up with the intention of having the results of the process fill up the text window and then hang there until the user presses 'done' or 'finish' or whatever I wind up calling the button.
It actually shows the window when I press the button on the main window but when the code for the button finishes, the window vanishes. Clearly I am leaving out (an important) step.
Once I get the window then I can add the text view etc.... and get that working. What I would like is for the new Window to get focus and then close out when the user presses the 'done' button.
Additionally, I got the window for the window controller from the window method (it returned an address) and tried a couple of window focus methods in the windowDidLoad method of the NSWindowController but no dice.
Thanks again for whatever info I can get on this.
Added 11/25/2011
Duh. Maybe if I make the class instance an ivar instead of embedding it in the button method it will work and, lo, it did. Le Oops.
Sounds like you want to drop a NSTextView into a window where one can select & scroll the text but not edit the contents.
You can insert text as easily as using the insertText: method.
So... The NSTextView and insertText combination worked out somewhat ok but I don't' think it is the final answer. First, my understanding is that insertText is really only meant for user input and not background 'system' input to a NXTextStorage object. I'm not sure why but that's fine so I'll avoid it. There are other options. I did find the beginEdit and endEdit methods and it works pretty much about the same way though I have some more work to do on some detail delegate methods.
The part that doesn't work so well is getting the NSScrollView in the window to update on demand. I do the beginEdit and endEdit stuff and am able to update the NSTextStorage object properly. I can do this multiple times in the same method (a test button on the window containing the scroll view). I can tell that because print-object in debug shows me what I'm expecting at the right times. However, I'd like to be able to show an updated NSScrollView multiple times during the court of the windowDidLoad method. The scroll view updates properly when the button push method ends.
Here is some sample code. I do mix insertText and the begin/end edit methods in here but it was more of a test thing than any code I would use for real....
(IBAction)FinishButtonPush:(id)sender {
NSString *teststring;
teststring = [NSString stringWithString: #"show"];
[RunResultWindowTextView setString:teststring];
teststring = [NSString stringWithString: #"show show"];
[RunResultWindowTextView setString:teststring];
teststring = [NSString stringWithString: #"show show show show"];
[RunResultWindowTextView setString:teststring];
[RunResultWindowTextView insertText:#"123"];
NSTextStorage *tempTextStorage;
tempTextStorage = [RunResultWindowTextView textStorage];
[tempTextStorage beginEditing];
[tempTextStorage replaceCharactersInRange:NSMakeRange(5,13)
withString:#"Hello to you!"];
[tempTextStorage endEditing];
[tempTextStorage beginEditing];
[tempTextStorage replaceCharactersInRange:NSMakeRange(10,13)
withString:#"second change"];
[tempTextStorage endEditing];
[RunResultWindowTextView insertText:#"xxx123"];
[RunResultWindowTextView insertText:#"xxx123567"];
}
Even though the NSTextStorage object is updated properly, the scroll view only updates when the method completes. My understanding is that processEdit is called automatically during endEdit. I added processEdit in there just to see and all I got was either abends or no change depending on where I put the command.
This got deleted and I'm not sure why. If you're going to gong the post, please let me know why you did so. Can't improve my post unless I have an idea what was wrong with it....

Over-release that makes the window go numb

I'm using Xcode 4.2 to write and Clang 3.0 to build a program that demonstrates a particular crash.
The program has a window that it means to keep around that is set in the nib to “Release When Closed”, so it's over-released in subsequent uses. It's meant to be a sheet, so it's shown using beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo:. Trying to show the window a second or third time should crash the app.
I remember that happening a year ago, with the program receiving EXC_BAD_ACCESS and that triggering the debugger to stop at that point. I also remember being able to hunt the problem down in Instruments using the Zombies template.
That's what I want (this program is part of a presentation to show debugging techniques), but that's not what's happening now. Now, the program doesn't crash; Instruments shows that the retain count on the window gets down to 1 twice, but no lower, so it does not get deallocated.
That would be fine if the problem stopped there; I could simply hide and show the sheet another time or two. The problem is, the second time I bring up the (should-be-dead-but-still-has-at-least-one-retain-keeping-it-alive) sheet, it's numb.
By that I mean that neither the sheet nor any control in it (it contains a field, a text view, and two buttons) responds to events. The heartbeat does nothing in it; the window has an OK button, but when the window is numb, the OK button does not pulse. Nothing works to dismiss the sheet.
But the program is not crashed. I can still interact with the menus and the Dock shows that the program is responding. If I try to quit it, it beeps, since it has a sheet up.
What's causing the window to go numb, and what can I do about it?
Here's a reduced version of the program that also exhibits the problem: https://github.com/boredzo/NumbWindow
I don't think you should be using -close to make the sheet go away. If you change the [sheet close]; line to be [sheet orderOut:self];, then it works properly.
As for why they're different, I don't know. But my experience has been to always use -orderOut: to dismiss sheets, and never -close. The documentation backs me up on this:
Listing 3 Did-end selector
- (void)didEndSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
[sheet orderOut:self];
}
tl;dr:
You're using the wrong method to make the panel go away.

Why did UIAlertView dismissAnimated: work at all?

I was just trying to dismiss a UIAlertView using the following call:
[serverConnectionClosedAlertView dismissAnimated:YES];
I did some testing and everything worked nicely. When I got back to the Xcode window I saw the warning, "UIAlertView may not respond to '-dismissAnimated'. I looked up the documentation and noticed that this method is indeed not defined on UIAlertView or even UIView. The correct call should have been
[serverConnectionClosedAlertView dismissWithClickedButtonIndex:0 animated:YES];
So, I am wondering
Why did Xcode suggest the original method name (I had pressed ESC to get the list of suggestions and just picked the method above; old Eclipse/Java habit, I guess), and
Why did the code work at all? It actually did dismiss the UIAlertView without any crashes or log entries.
It was in fact called dismissAnimated: in previous versions of the SDK. It has since either been deprecated or made a private API in favor of dismissWithClickedButtonIndex:animated:, but it'll still work if you call it.