objective c WatchKit WKInterfaceController openParentApplication call blocks indefinitely - xcode

I'm using the following code to "simply" determine the application state of the parent application from my watch app:
WatchKit Extension:
[WKInterfaceController openParentApplication:[NSDictionary dictionary] reply:^(NSDictionary *replyInfo, NSError *error)
{
UIApplicationState appState = UIApplicationStateBackground;
if(nil != replyInfo)
appState = (UIApplicationState)[((NSNumber*)[replyInfo objectForKey:kAppStateKey]) integerValue];
//handle app state
}];
Main App:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply
{
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply([NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:[[UIApplication sharedApplication] applicationState]], kAppStateKey, nil]);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
reply([NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:[[UIApplication sharedApplication] applicationState]], kAppStateKey, nil]);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}
When the app is in the foreground this works 100% of the time. When the app is "minimized" or "terminated" this maybe works 50% of the time (maybe less). When it doesn't work it appears to be blocking indefinitely. If after 1 minute, for example, I launch the parent app, the call (openParentApplication) immediately returns with the state "UIApplicationStateBackground" (the state it was before I launched the app as clearly the app isn't in the background state if I launched it).
BTW: I'm testing with real hardware.
What am I doing wrong? Why is iOS putting my main app to sleep immediately after receiving the call even though I create a background task? This is a complete show-stopper.
Any thoughts or suggestions would be greatly appreciated!!

After some research it looks to be a known issue. For example, the following link identifies this issue and provides a solution:
http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
However, this solution did not work for me. As a result I implemented the following solution (its a little sloppy, but this is intentional to help condense the solution):
//start the timeout timer
timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeOutTime target:self selector:#selector(onTimeout) userInfo:nil repeats:NO];
//make the call
messageSent = [WKInterfaceController openParentApplication:[NSDictionary dictionary] reply:^(NSDictionary *replyInfo, NSError *error)
{
if(nil != _stateDelegate)
{
UIApplicationState appState = UIApplicationStateBackground;
if(nil != replyInfo)
appState = (UIApplicationState)[((NSNumber*)[replyInfo objectForKey:kAppStateKey]) integerValue];
[_stateDelegate onOperationComplete:self timeout:false applicationState:appState];
_stateDelegate = nil;
}
}];
//if the message wasn't sent, then this ends now
if(!messageSent)
{
if(nil != _stateDelegate)
{
//just report that the main application is inactive
[_stateDelegate onOperationComplete:self timeout:false applicationState:UIApplicationStateInactive];
}
_stateDelegate = nil;
}
-(void)onTimeout
{
timeoutTimer = nil;
if(nil != _stateDelegate)
{
[_stateDelegate onOperationComplete:self timeout:true applicationState:UIApplicationStateInactive];
}
_stateDelegate = nil;
}
In a nutshell, if the timer fires before I hear back from the main app I will basically assume that the main app has been put to sleep. Keep in mind that all pending calls will succeed at some point (e.g. app state is restored to active) and, thus, you will need to handle this scenario (if necessary).

Related

NSSharingService performWithItems Hangs

I had a working share routine and now it is broken. Hadn't checked it, or modified it, for some time and now find that it is inoperable. When I call
[sharingService performWithItems:[NSArray arrayWithObject:itemProvider]];
I get a share sheet displayed. It shows the current members of the share. The form is inoperable and will not accept any input or taps. I cannot add, remove or stop sharing altogether. When I close the form, my app is hung up and will not respond or take focus. I have to kill the app and reopen to get it working again.
This used to work fine, within the last few months. I hadn't changed anything so I am very surprised by new problem.
I am adding my code for creating share here:
NSString *shareOption = [[NSUserDefaults standardUserDefaults] objectForKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
if ([shareOption isEqualToString:TTICloudKitShareOwnerService]) {
CDEZipCloudFileSystem *zipFile = (CDEZipCloudFileSystem *)_cloudFileSystem;
CDECloudKitFileSystem *fileSystem = (CDECloudKitFileSystem *)zipFile.cloudFileSystem;
NSItemProvider *itemProvider = [[NSItemProvider alloc] init];
[itemProvider registerCloudKitShare:fileSystem.share container:fileSystem.container];
NSSharingService *sharingService = [NSSharingService sharingServiceNamed:NSSharingServiceNameCloudSharing];
sharingService.subject = #"Share Workforce Data";
sharingService.delegate = self;
if ([sharingService canPerformWithItems:[NSArray arrayWithObject:itemProvider]]) {
[sharingService performWithItems:[NSArray arrayWithObject:itemProvider]];
// This is the point at which the Apple UI is presented but inoperable.
// No changes can be made to the share.
// The only way to dismiss the dialog is to quit or press escape.
// Upon dismissal the app is either crashed or hung up.
// Quitting the app and restart is only option to use the app again.
// If not run from Xcode, requires force quit.
}
} else {
NSLog(#"Is Shared Ensemble");
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:#"Stop Share"];
[alert addButtonWithTitle:#"Cancel"];
[alert setMessageText:#"Shared Data Options"];
[alert setInformativeText:#"You are participating in a shared file. Stop sharing will remove your participation and reset your data. You will no longer participate or have access to the shared information."];
[alert setAlertStyle:NSAlertStyleWarning];
if ([alert runModal] == NSAlertFirstButtonReturn) {
[alert setInformativeText:#"Are you sure? You will no longer have access to shared data. You will need the owner of the share to resend an invitation to join the share."];
if ([alert runModal] == NSAlertFirstButtonReturn) {
// This actually does not remove user from sharing as intended.
// I am sure that is my own implementation incomplete though.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setNilValueForKey:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT];
[defaults setObject:TTICloudKitShareOwnerService forKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
[defaults synchronize];
[self disconnectFromSyncServiceWithCompletion:^{
// TODO: Need to wipe the existing Core Data info here. Leave them with no access to shared data.
// Also need to remove self from the share?
[self reset];
[self setupEnsemble];
}];
}
}
}
Creating share and sending worked flawlessly and I'd been developing app and testing live. Currently my test is shared with two other users and still works. In fact I can't seem to find a way to stop sharing with those users or in any way alter the current share at all.
This is the NSCloudSharingServiceDelegate code:
-(NSCloudKitSharingServiceOptions)optionsForSharingService:(NSSharingService *)cloudKitSharingService shareProvider:(NSItemProvider *)provider
{
return NSCloudKitSharingServiceAllowPrivate | NSCloudKitSharingServiceAllowReadWrite;
}
-(void)sharingService:(NSSharingService *)sharingService willShareItems:(NSArray *)items
{
DLog(#"Will Share Called with items:%#",items);
}
-(void)sharingService:(NSSharingService *)sharingService didShareItems:(NSArray *)items
{
DLog(#"Did share called");
}
-(void)sharingService:(NSSharingService *)sharingService didFailToShareItems:(NSArray *)items error:(NSError *)error
{
DLog(#"Sharing service failed to share items, %#-", error);
if (error.code == NSUserCancelledError) return;
DLog(#"Failed to share, error- %#", error.userInfo);
[self disconnectFromSyncServiceWithCompletion:^{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT forKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
[defaults setNilValueForKey:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT];
[defaults synchronize];
}];
}
It is apparent that I am one of the very few who find this to be important as I have scoured the webs and found almost nobody discussing it. Apple documentation is just about nil.
This is a screenshot of the Apple UI which is not working:
I am posting this as an answer, is more like a work around that I have managed to get working. Still no answer as to why the Apple UI does not respond.
See code for inviting participants without the Apple UI.
-(void)addParticipantWithEmail:(NSString *)email toShare:(CKShare *)share inContainer:(CKContainer *)container
{
[container discoverUserIdentityWithEmailAddress:(email) completionHandler:^(CKUserIdentity * _Nullable userInfo, NSError * _Nullable error) {
if (!userInfo || error) {
NSLog(#"Participant was not found for email %#", email);
if (error) {
NSLog(#"Error: %#", error.userInfo);
} else {
NSLog(#"No error was provided");
}
// abort
return;
}
CKFetchShareMetadataOperation *fetchMetaDataOperation = [[CKFetchShareMetadataOperation alloc] initWithShareURLs:[NSArray arrayWithObject:share.URL]];
fetchMetaDataOperation.shouldFetchRootRecord = YES;
[fetchMetaDataOperation setPerShareMetadataBlock:^(NSURL * _Nonnull shareURL, CKShareMetadata * _Nullable shareMetadata, NSError * _Nullable error) {
CKRecord *root = shareMetadata.rootRecord;
if (!root) {
NSLog(#"There was an error retrieving the root record- %#", error);
} else {
NSLog(#"Root is %#", root);
NSLog(#"/n");
}
CKUserIdentityLookupInfo *info = userInfo.lookupInfo;
CKFetchShareParticipantsOperation *fetchOperation = [[CKFetchShareParticipantsOperation alloc] initWithUserIdentityLookupInfos:[NSArray arrayWithObject:info]];
[fetchOperation setShareParticipantFetchedBlock:^(CKShareParticipant * _Nonnull participant) {
participant.permission = CKShareParticipantPermissionReadWrite;
[share addParticipant:participant];
CKModifyRecordsOperation *modifyOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:[NSArray arrayWithObjects:root, share, nil] recordIDsToDelete:nil];
modifyOperation.savePolicy = CKRecordSaveIfServerRecordUnchanged;
[modifyOperation setPerRecordCompletionBlock:^(CKRecord * _Nonnull record, NSError * _Nullable error) {
if (error) {
DLog(#"Error modifying record %#. UserInfo: %#", record, error.userInfo);
} else {
DLog(#"No Error Reported in Modify Operation");
}
}];
[container.privateCloudDatabase addOperation:modifyOperation];
}];
[fetchOperation setFetchShareParticipantsCompletionBlock:^(NSError * _Nullable operationError) {
if (operationError) {
NSLog(#"There was en error in the fetch operation- %#", operationError.userInfo);
// Error may be a network issue, should implement a retry and possibly a limit to how many times to run it
}
}];
[container addOperation:fetchOperation];
}];
[container addOperation:fetchMetaDataOperation];
}];
}
It seems now, if I pass an email address to this function they are successfully invited to share, provided the user is in my contacts and has allowed discoverability.
I send the user the link to the share manually via iMessage at this point. Copied the URL from the console. My intent is to provide my own forms to handle that now.
On receiving link, I use Ensembles method:
CDECloudKitFileSystem acceptInvitationToShareWithMetadata:metadata completion:^(NSError *error)
This code didn't seem to work, accepting invites was failing initially. Without having changed anything, the accepting shares started to work. I am not sure why the initial fails.

Callbacks for USB HID API in OsX are never called for game pad or anything else

I am trying to detect and use a gamepad attached to my Mac in my game.
I am using the IOKit for this but without success.
The gamepad is recognized as I downloaded a Mac app that views all gamepads attached.
It is a Logitech F310 and I have set it to DirectInput.
I am calling setupInput inside awakeFromNib but none of the callbacks are ever called.
There is no error or exception thrown.
I have no idea why the callbacks are not called.
What am I doing wrong?
I am thinking maybe it's an issue with the run loop. I tried both CFRunLoopGetMain and CFRunLoopGetCurrent.
Not sure what else to do.
Edit: As suggested by someone I tried to put the same GamePad code in a new project and it worked.
There is some difference in the structure of the classes but I couldn't make it work in my app even if I tried to replicate the classes.
My app use AppDelegate and NSOpenGLView. Strangely enough it doesn't have an NSViewController.
The minimal app use NSOpenGLView but also NSResponder and when I put the gamepad code inside the(custom) NSResponder class it "works"(detects the gamepad and responds to input).
I tried to add that custom class to my app but it "didn't work".
What am I missing?
void gamepadWasAdded(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
NSLog(#"Gamepad was plugged in");
}
void gamepadWasRemoved(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
NSLog(#"Gamepad was unplugged");
}
void gamepadAction(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
NSLog(#"Gamepad talked!");
IOHIDElementRef element = IOHIDValueGetElement(value);
NSLog(#"Element: %#", element);
int elementValue = IOHIDValueGetIntegerValue(value);
NSLog(#"Element value: %i", elementValue);
}
-(void) setupInput {
//get a HID manager reference
hidManager = IOHIDManagerCreate(kCFAllocatorDefault,
kIOHIDOptionsTypeNone);
//define the device to search for, via usage page and usage key
NSMutableDictionary* criterion = [[NSMutableDictionary alloc] init];
[criterion setObject: [NSNumber numberWithInt: kHIDPage_GenericDesktop]
forKey: (NSString*)CFSTR(kIOHIDDeviceUsagePageKey)];
[criterion setObject: [NSNumber numberWithInt: kHIDUsage_GD_Joystick]
forKey: (NSString*)CFSTR(kIOHIDDeviceUsageKey)];
//search for the device
IOHIDManagerSetDeviceMatching(hidManager,
(__bridge CFDictionaryRef)criterion);
//register our callback functions
IOHIDManagerRegisterDeviceMatchingCallback(hidManager, gamepadWasAdded,
(__bridge void*)self);
IOHIDManagerRegisterDeviceRemovalCallback(hidManager, gamepadWasRemoved,
(__bridge void*)self);
IOHIDManagerRegisterInputValueCallback(hidManager, gamepadAction,
(__bridge void*)self);
//scedule our HIDManager with the current run loop, so that we
//are able to recieve events from the hardware.
IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetMain(),
kCFRunLoopDefaultMode);
//open the HID manager, so that it can start routing events
//to our callbacks.
IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
}
// Put our timer in -awakeFromNib, so it can start up right from the beginning
-(void)awakeFromNib
{
renderTimer = [NSTimer timerWithTimeInterval:0.001 //a 1ms time interval
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:renderTimer
forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:renderTimer
forMode:NSEventTrackingRunLoopMode]; //Ensure timer fires during resize
[self setupInput];
}
Ok, I have found the issue.
The problem was that my Mac app was in sandbox and I didn't check mark the Hardware/USB check box.
With this option checked it works with the original code.

iBeacon didEnterRegion not firing when in background and locked

I have an iBeacon configured as:
NSUUID *proximityUUID = [[NSUUID alloc] initWithUUIDString:kUUID];
self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:proximityUUID identifier:kIdentifier];
self.beaconRegion.notifyEntryStateOnDisplay = NO;
self.beaconRegion.notifyOnEntry = YES;
self.beaconRegion.notifyOnExit = YES;
When my app is closed and the device is locked, didEnterRegion is never fired:
locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
[self sendLocalNotificationForBeaconRegionHello:(CLBeaconRegion *)region];
}
- (void)sendLocalNotificationForBeaconRegionHello:(CLBeaconRegion *)region
{
UILocalNotification *notification = [UILocalNotification new];
notification.alertBody = [NSString stringWithFormat:#"Welcome - %#", region.identifier];
notification.alertAction = NSLocalizedString(#"View", nil);
notification.soundName = UILocalNotificationDefaultSoundName;
notification.fireDate = nil;
notification.hasAction = false;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
The didExitRegion does get called even if the app is closed and the phone is locked. I only get a notification when entering a region when unlocking the phone.
Any ideas what might be the problem?
Thanks
I suspect the method is getting called but the notification swallowed for some reason (although the code looks OK). Try adding a method like below to help figure out what is going on:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if(state == CLRegionStateInside) {
NSLog(#"locationManager didDetermineState INSIDE for %#", region.identifier);
}
else if(state == CLRegionStateOutside) {
NSLog(#"locationManager didDetermineState OUTSIDE for %#", region.identifier);
}
else {
NSLog(#"locationManager didDetermineState OTHER for %#", region.identifier);
}
}
It would also be useful to see your code in didExitRegion for comparison, and hear details about how you are testing enter/exit conditions (including wait times.)

Run loop doesn't proceed events

I'm testing with run loops in standard (created by XCode) App. My App has 2 buttons:
Start Loop - starts runloop in some mode (see code below);
Stop Loop - change self.stop flag to stop runloop.
`
- (IBAction)stopLoop:(id)sender
{
self.stop = YES;
}
- (IBAction)startLoop:(id)sender
{
self.stop = NO;
do
{
[[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
if (self.stop)
{
break;
}
} while (YES);
}
`
where:
1. runLoopMode is one of the predefined modes (I try each, default, event tracking, modal, connection).
2. runLoopLimitDate [NSDate distantFuture], or [NSDate distantPast], or close feature.
3. self.stop flag is installed in other method, which called by button.
That's all, my App hasn't any other code.
AFAIU, runloop mode is a set of event sources. So, if I run runloop in some mode, runloop will be proceed those event sources, whose are associated with this mode.
By default Cocoa runs runloop in default mode and all events are proceeds greatly. But when user press startLoop button, my App is freezing: .
startLoop method is never break this infinity cycle. Application doesn't send any event to me, therefore UI freezing and user can't press stopLoop button. The same problem if I run Core Foundation counterparts.
But, when I try to receive events through NSApplication (of NSWindow) method nextEventMatchingMask:untilDate:inMode:dequeue: and pass the same mode, I receive UI events.
- (IBAction)startLoop:(id)sender
{
self.stop = NO;
do
{
NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES];
if (event == nil)
{
break;
}
[NSApp sendEvent:event];
if (self.stop)
{
break;
}
} while (YES);
}
There are question: "Why if I run default run loop mode, or some other, in this way, I can't receive events?"
Thanks for your advice.
You are assuming that running the NSRunLoop in the NSDefaultRunMode processes user input events such as key presses or mouse clicks. I don't think that's the case.
NSApplication fetches events from the event with nextEventMatchingMask:untilDate:inMode:dequeue:. By running the run loop like that your not really fetching any event of the event queue.
Cloud you try something like this:
- (IBAction)startLoop:(id)sender
{
do
{
NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask];
[NSApp sendEvent:event];
if (self.stop)
{
break;
}
} while (YES);
}
(Haven't tested this).
What happens if you substitute the following code in your app in place of the -startLoop: method?
- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
NSLog (#"didRun? %#, runMode: %#, dateNow: %#, limitDate: %#",
didRun ? #"YES" : #"NO",
runLoopMode,
[NSDate date],
limitDate);
}
- (IBAction)startLoop:(id)sender
{
BOOL didRun = NO;
do
{
didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
[self debugLogRunLoopInfo:didRun];
if (self.stop)
{
break;
}
} while (YES);
}
The -[NSRunLoop runUntilDate:] method spins the runloop in NSDefaultRunLoopMode. Since you're wanting to experiment with runloop modes, you could try the code below.
I've implemented a -myRunLoopUntilDate:runMode: method that does what runUntilDate: is documented to do, but allows you to specify a runloop mode.
All of this is compiled in my text editor (i.e. not compiled at all), so caveat emptor.
- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
NSLog (#"didRun? %#, runMode: %#, dateNow: %#, limitDate: %#",
didRun ? #"YES" : #"NO",
runLoopMode,
[NSDate date],
limitDate);
}
- (void) myRunLoopUntilDate:(NSDate *)limitDate runMode:(NSString *)runLoopMode
{
BOOL didRun = NO;
do {
didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:limitDate];
[self debugLogRunLoopInfo:didRun];
} while (didRun && ([limitDate timeIntervalSinceNow] > 0));
}
- (IBAction)startLoop:(id)sender
{
BOOL didRun = NO;
do
{
[self myRunLoopUntilDate:runLimitDate runMode:runLoopMode];
if (self.stop)
{
break;
}
} while (YES);
}

Cocoa: Displaying an error after NSApp beginSheet results the main window hiding

I have broken this down into a very small project. Using the following code in the application delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
TestingWindowController * testingWindowController = [[TestingWindowController alloc] initWithWindowNibName: #"TestingWindowController"];
// Begin our sheet
[NSApp beginSheet: testingWindowController.window
modalForWindow: self.window
modalDelegate: self
didEndSelector: #selector(windowDidEnd:returnCode:contextInfo:)
contextInfo: NULL];
}
- (void)windowDidEnd:(id)alert returnCode:(NSInteger)returnCode contextInfo:(id) contextInfo
{
// If the user did not accept, then we really don't care what else they did!
if (returnCode != NSOKButton) return;
// We have had an error. Display it.
[[NSApplication sharedApplication] presentError: nil
modalForWindow: self.window
delegate: nil
didPresentSelector: nil
contextInfo: NULL];
}
And the following action tied to button on the windows nib. (Note that the nib's window is also set to not be visible on launch).
- (IBAction) onClose: (id) sender
{
[[NSApplication sharedApplication] endSheet: self.window
returnCode: NSOKButton];
[self.window orderOut: nil];
} // End of onClose
What ends up happening is, once I the onClose runs, all of the windows disappear and I am left with nothing but the error dialog (the main window has disappeared).
Is there something wrong with my code? Why does my main window go away?
NOTE: I know that I am not passing an error to the presentError method. I purposely left this null as I only had a short time to write the sample code. Passing an actual error results in the same behaviour.
Sample project is available here.
Looks like you are still using the old api, try the new one
(deselect Always visible at launch for the UserLoginWindowController window)
- (IBAction)userButtonPressed:(id)sender {
UserLoginWindowController * wc = [UserLoginWindowController new];
// we keep a reference, so the WC doesn't deallocate
self.modalWindowController = wc;
[[self window] beginSheet:[wc window] completionHandler:^(NSModalResponse returnCode) {
self.modalWindowController = nil;
}];
}
in the UserLoginWindowController
- (IBAction)cancelButtonPressed:(id)sender {
[[[self window] sheetParent] endSheet:[self window] returnCode:NSModalResponseCancel];
}
You are using 2 methods to open your window, beginSheet:....., and runModalForWindow:. You only need one of those. If you want a sheet attached to your window, use the first method, if you want a stand alone window, use the second. Likewise, in your onClose method, you should use endSheet:returnCode: if you're closing a sheet (the argument for that method should be testingWindowController.window not self.window) , and stopModalWithCode: if you're closing a modal window, you shouldn't have both.

Resources