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.
Related
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.
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)
When the user selects a row in my table, I need to load a bunch of data with CoreData. This takes several seconds (at least when running on the simulator - haven't tested on a device yet, but I imagine it will still be pretty long). I want to display a loading popover screen (I'm developing for iPad) when the user selects a row, then have it disappear once the data is loaded.
When working with UIButtons, I've always done this by triggering both a TouchDown and TouchUp method. I put the code to display the popover on the TouchDown, then do all my actual work (so in this case loading from CoreData) in the TouchUp. Then at the end of TouchUp, I close the popover.
Is there a way to split up touches in a table view the same way as ones on a button?
So while I was typing up the question I came across something in the Apple Docs for willSelectRowAtIndexPath and it looks like that is there to address this very issue. I didn't see any other topics on this on SO, so I figured I'd make use of this "Answer own question - Q&A" feature on here, so maybe people looking for this in the future will find it a little easier.
Huge FYI block:
I should note that willSelectRowAtIndexPath is actually supposed to return an NSIndexPath, unlike didSelectRowAtIndexPath (which returns void). However, I originally had
-(void)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//stuff
}
Note the void return type. Xcode (Version 4.5) allowed this without any errors or warnings (and even had it for me in the autocomplete list). When I ran the code this way, I always hit an invisible breakpoint in libobjc.A.dylib'objc_autorelease: (maybe becaues I have the "All Exceptions" breakpoint enabled?). I couldn't get past this breakpoint, I never got any debug information in the debugger output console, and the app never terminated. I happened to look at the Apple Doc again and notice that it was supposed to return an NSIndexPath (specifically, the index path the code should interpret as being the one that was selected), but if I didn't happen to see it there, I never would have known what the problem was.
It may sound as a dumb question. In fact, I am still thinking if that might be the cause but working with XCode and having set iOS 4.3 as my Deployment Target and iPad as my Device I am getting an unexpected error.
While running my app through the Simulator I can get it working. But when I run it through my iPad a single IBAction, fired when user taps an UIButton, that takes almost 4 minutes is not completing. In fact it's getting stopped at the same point, in a for loop.
I searched for memory leaks using XCode and it didn't find any, therefore I'm asking if there's a time limit for IBActions. I read there are 10 minutes limit for methods in background but I didn't find anything related to IBActions in foreground yet.
I think the user gets tired of waiting for a response long before the system. Normally, a button press or similar should finish after one second at most (well, maybe 1.5s), or the user thinks that the system is "slow".
I would make sure that you have your button outlets properly hooked up. It is possible that it just isn't throwing the exception so you get a slow system. Put NSLog displays in your IBAction method and make sure it is executing it properly. You should be able to track it back to figure out what is going on.
I'm writing a mozilla plugin on the mac. I'm trying to capture events like button clicks, etc. I've done the normal thing of creating my views in inteface builder, and linking the sentActions to methods in my program. This works in stand-alone programs.
However, in my NPAPI plugin, those methods never get called. The button reacts, depresses, whatever, but it doesn't do its action.
Instead, the NPP_HandleEvent method gets called, but I never get the MouseDown or MouseUp event, only the UpdateEvt.
I set up the buttons to accept clicks via: (superview is the Mozilla view, topview is the top of my view hierarchy.)
[superView setNextResponder: topView];
[topView setNextResponder: nil];
[browserWindow makeFirstResponder: topView];
NEVER MIND: I'm an idiot. It IS calling the button sent actions. I was looking at the wrong method. That'll teach me to leave around a zoom: method when I'm actually using a doZoom: method... D'oh,.
So, the problem was that I wasn't able to get buttons to work. The buttons were supposed to (for example) zoom an image in an IKImageView. (or rather, zoom the view). It didn't appear that it was working. The screen was flashing a lot, but nothing was happening... I put a printf in my zoom method, and it was NEVER GETTING CALLED! and so I asked the question.
Later, I noticed that I wasn't TRYING to call zoom, I was calling doZoom! doZoom WAS being called. And the reason that it wasn't zooming was an unrelated problem.
The problem ended up being that I was sending setImage to my IKImageView on every event, which re-set the view to 1-1, rightside up mode. Once I took out the extra setImage call, things started to work.
In the unlikely event that anyone else every experiences this, the answer is my cunning plan for world domination:
step 1: Don't be an idiot.
step 2: ???????
step 3: Dominate the world.
(If I could master step 1, I might just be able to figure out what step 2 was B-)