NSApp Sheets question in cocoa - cocoa

Here's what I am trying to do. I need to prompt the user for a password prompt and until he enters the password and hits, say the Enter button on the sheet, I want to prevent the code being parsed in the background.
Here's the code to run the sheet and when the user enters the password and hits Enter, endSpeedSheet is run. I am calling all of this from my Main() function.
What I am noticing is that the when the main function runs, the sheet shows up, the user is prompted for a password. But in the background, I already see " Code gets here" has been run. This means the code has already run in the background. What I need is the code to wait at the password prompt and then use this password after the Sheet has been dismissed. Any idea's on what I am missing here ? ( And thanks in advance :))
- (IBAction) showSpeedSheet:(id)sender
{
[NSApp beginSheet:speedSheet
modalForWindow:(NSWindow *)window
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
}
-(IBAction)endSpeedSheet:(id)sender
{
joinPassword = [joinPasswordLabel stringValue];
[NSApp endSheet:speedSheet];
[speedSheet orderOut:sender];
}
-(IBAction)main:(id)sender
{
[self showSpeedSheet:(id)sender];
// More Code here
NSLog(#" Code gets here");
}

The answer is simple: don't put the code that needs to run after the sheet completes directly after you call -showSpeedSheet:. Sheets are asynchronous, so you must factor your code so that it is only called once the sheet completes.
That's what the didEndSelector parameter in the ‑beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo: method is for. You pass in a selector to be called when the sheet is closed. In the modal delegate object, you implement this selector and do whatever processing needs to be done once the sheet completes.
- (IBAction) showSpeedSheet:(id)sender
{
[NSApp beginSheet:speedSheet
modalForWindow:self.window
modalDelegate:self
didEndSelector:#selector(speedSheetDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
- (IBAction)endSpeedSheet:(id)sender
{
self.joinPassword = [joinPasswordLabel stringValue];
[NSApp endSheet:speedSheet];
[speedSheet orderOut:sender];
}
- (void)speedSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
if(sheet == speedSheet)
{
//the sheet has ended, so do something
}
}

Related

How to wait for completion of sheet after invoking beginSheet:completionHandler:

My code results in the sheet appearing and functioning properly but the invoker receives control back (as per Apple definition) before the sheet ends itself with endSheet.
How can I get the invoker to wait for the return from the end of the sheet processing, so the resultValue is updated.
Invoker:
[self.window beginSheet: sheetController.window
completionHandler:^(NSModalResponse returnCode) {
resultValue = returnCode;
}
];
...
Sheet:
...
[self.window.sheetParent endSheet:self.window returnCode:false];
I thought the new beginSheet: was meant to do the whole linkage, but that isn't the case. All it does is put up the sheet window. The passage and return of control remains as it was. So the following code works:
Invoker:
[self.window beginSheet: sheetController.window
completionHandler:nil
];
returnCodeValue = [NSApp runModalForWindow:sheetController.window];
// Sheet is active until stopModalWithCode issued.
[NSApp endSheet: sheetController.window];
[sheetController.window orderOut:self];
Sheet:
[NSApp stopModalWithCode:whatever];
Looks like you're most of the way there. The return code should be type NSModalResponse (ObjC) or NSApplication.ModalResponse (Swift). I've also found that you need to uncheck 'Visible at Launch'. Otherwise it won't launch as modal.
There was an NSAlert category from way back that used the older (deprecated) methods. I’m not all that great with Objective-C (and worse with Swift), but I’ve been using an updated equivalent for a while in one of my RubyMotion projects. Hopefully I’ve come close with reversing the code conversions, but basically it fires up a modal event loop with runModalForWindow: after calling beginSheetModalForWindow;, and the completionHandler exits with stopModalWithCode:
// NSAlert+SynchronousSheet.h
#import <Cocoa/Cocoa.h>
/* A category to allow NSAlerts to be run synchronously as sheets. */
#interface NSAlert (SynchronousSheet)
/* Runs the receiver modally as a sheet attached to the specified window.
Returns a value positionally identifying the button clicked */
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow;
/* Same as above, but runs the receiver modally as a sheet attached to the
main window. */
-(NSInteger) runModalSheet;
#end
// NSAlert+SynchronousSheet.m
#import "NSAlert+SynchronousSheet.h"
#implementation NSAlert (SynchronousSheet)
-(NSInteger) runModalSheetForWindow:(NSWindow *)theWindow {
// Bring up the sheet and wait until it completes.
[self beginSheetModalForWindow:theWindow
completionHandler:^(NSModalResponse returnCode) {
// Get the button pressed - see NSAlert's Button Return Values.
[NSApp stopModalWithCode:returnCode];
}];
[NSApp runModalForWindow:self.window] // fire up the event loop
}
-(NSInteger) runModalSheet {
return [self runModalSheetForWindow:[NSApp mainWindow]];
}
#end
The application will wait for the alert to finish before continuing, with the modal response passed in the result. I don’t know about blocking a couple of thousand lines of code, but I found it useful when chaining two or three sheets, such as an alert before an open/save panel.

force NSDocument to save after creation

In its documents, my application uses a lot of assets that are relative to the document path. So the document must be saved before assets can be added. How can I force-call a [NSDocument saveDocumentAs] ?
I managed to do parts of it : by creating my own document controller, and inside openUntitledDocumentAndDisplay: force a call like this :
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError
{
NSDocument * res = [super openUntitledDocumentAndDisplay:displayDocument error:outError];
[res saveDocumentAs:self];
return res;
}
This forces the save dialog to appear, but unfortunately I can not check whether the user pressed cancel : the saveDocumentAs call is asynchronous and continues immediately !
Is there a way to fix this ?
I had a similar problem. By using:
saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo
you can defer your processing (or not) until after the document save dialogue has completed. This means you can find out whether the user cancelled or not. You split your processing in two, do whatever preparation you need and put the rest (that depends upon a successful save) into another method. If you use something like:
[self saveDocumentWithDelegate:self didSaveSelector:#selector(actuallyDoIt: didSave: contextInfo:) contextInfo:nil];
The document will be saved but, critically, if it has not been saved before, the Save dialogue will appear so the user can input a file name. Once he/she has done that, or cancelled, your method actuallyDoIt: (or whatever) is invoked. The didSave: parameter tells you whether the save actually happened (essentially, did the user cancel) so you can either continue or offer an alert explaining politely to the user that nothing's going to happen until they save.
I have a similar thing in my application, in my case if the user tries to do something, I pull up a prompt to say 'This requires you to save the document first' with buttons to cancel or save.
If you want to absolutely force it, then instead of using saveDocumentAs, just display your own NSSavePanel. Run it modally, check the result, save the document with the result, and if this doesn;t go smoothly, call it again. You can check if the document is saved by looking for a valid value for it's file path.
NSSavePanel can run modally. Here is how it can/should look like.
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError *__autoreleasing *)outError
{
Document *document;
NSSavePanel *panel = [NSSavePanel savePanel];
panel.prompt = #"Create";
NSInteger modalCode = [panel runModal];
if (modalCode == NSModalResponseOK) {
NSURL *URL = [panel URL];
NSError *docError;
document = [[Document alloc] initWithType:nil & docError];
[document saveToURL:URL ofType:nil forSaveOperation:NSSaveOperation completionHandler:^(NSError *error){
if(error) {
return nil;
}
[self addDocument:document];
[document makeWindowControllers];
if (displayDocument) {
[document showWindows];
}
}];
}
return document;
}
To sum up for reference:
Create custom nsdocumentsubclass in XIB (no XIB -> app did finish launching)
override openUntitledDocumentAndDisplay
(NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError;

UIWebview Logout

Can anyone tell me how to logout UIWebview when Application entered Background?
I need to call a js function such as offline() through UIWebview.
when i call this function with
[mywebview stringByEvaluatingJavaScriptFromString:#"offline()"];
this method is not working.
but this is working when application entered foreground back.
how could I get it work?
It's possible that the javascript does not execute in time before the app goes into the background. You can request more time by running a backgroundTask in you appdelegate's applicationWillResignActive method:
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid){
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];
Also make sure to end the background task once you know your javascript it complete (because its javascript within a webview, the only way might just be to call it after N number of seconds):
// After N sections execute this:
if (bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}

Multiwindows problem, cocoa

I have a simple application, not document-based. I want to have a login window that allows people to login or add a user, and when they logged in successfully I want it to load the main page. If from the main page you click log out, it should destroy the main page and take you back to login page.
sounds like a simple plan, but for some reason I have a problem.
The way I have it right now, I check if the customer logged in or not in the main file AppDelegate and load different window controller. When customer logs in, I send a notification back to AppDelegate from Login Conntroller and load another window controller for main window.
Something like this:
if([[settings get:#"isLoggedIn"] isEqualToString:#"Yes"])
{
MainController *tmpMainController = [[MainController alloc] initWithWindowNibName:#"MainWindow"];
self.mainController = tmpMainController;
NSWindow *mainWindow = [tmpMainController window];
[mainWindow makeKeyAndOrderFront:self];
[tmpMainController release];
} else {
LoginController *tmpViewController = [[LoginController alloc] initWithWindowNibName:#"LoginWindow"];
self.loginController = tmpViewController;
loginWindow = [tmpViewController window];
[loginWindow makeKeyAndOrderFront:self];
[tmpViewController release];
}
Everything works fine, it displays the correct window. But the weird part happens when I log out from the main page, log in again and log out again. If I do it several times, instead of showing me 1 login window, it draws 2. If I continue the login process, on the second try I get 2 main windows. If I log out again, I see 4 cascade login windows, then I see 5 or 7 main windows. After all windows gets loaded all extra windows start getting destroyed one-by-one. It looks like when new window gets created it draws all old windows, then the new one and then destroys all old ones. I don't know why it happens. Would like some help.
Here is the code from my main controller when customer clicks log out:
-(IBAction)logOutClick:(id) sender
{
[settings set:#"isLoggedIn" value:#"No"];
[[self window] orderOut:self];
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSUserLoggedOutNotification" object: self userInfo: nil];
}
the same thing for login controller:
if ([users verifyUser]) {
[settings set:#"isLoggedIn" value:#"Yes"];
[loginView removeFromSuperview];
[[self window] orderOut:self];
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSUserLoggedInNotification" object: self userInfo: nil];
}
I have "Released when closed" checked off for both windows.
I added new nsnotification center observer every time I log out.
That was the problem.

Execute an action from a text field ONLY when enter is pressed [duplicate]

I have two NSTextFields: textFieldUserID and textFieldPassword.
For textFieldPassword, I have a delegate as follows:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
This delegate gets called when textFieldPassword has focus and I hit the enter key. This is exactly what I want.
My problem is that controlTextDidEndEditing also gets called when textFieldPassword has focus and I move the focus to textFieldUserID (via mouse or tab key). This is NOT what I want.
I tried using controlTextDidChange notification (which is getting called once per key press) but I was unable to figure out how to detect enter key ( [textFieldPassword stringValue] does not include the enter key). Can someone please help me figure this one out?
I also tried to detect if textFieldUserID was a firstResponder, but it did not work for me. Here is the code I tried out:
if ( [[[self window] firstResponder] isKindOfClass:[NSTextView class]] &&
[[self window] fieldEditor:NO forObject:nil] != nil ) {
NSTextField *field = [[[self window] firstResponder] delegate];
if (field == textFieldUserID) {
// do something based upon first-responder status
NSLog(#"is true");
}
}
I sure could use some help here!
If I understood you correctly, you could set an action for the password text field and tell the field to send its action only when the user types Return. Firstly, declare and implement an action in the class responsible for the behaviour when the user types Return on the password field. For example:
#interface SomeClass …
- (IBAction)returnOnPasswordField:(id)sender;
#end
#implementation SomeClass
- (IBAction)returnOnPasswordField:(id)sender {
// do something
}
#end
Making the text field send its action on Return only, and linking the action to a given IBAction and target, can be done either in Interface Builder or programatically.
In Interface Builder, use the Attributes Inspector, choose Action: Sent on Enter Only, and then link the text field action to an IBAction in the object that implements it, potentially the File’s Owner or the First Responder.
If you’d rather do it programatically, then:
// Make the text field send its action only when Return is pressed
[passwordTextFieldCell setSendsActionOnEndEditing:NO];
// The action selector according to the action defined in SomeClass
[passwordTextFieldCell setAction:#selector(returnOnPasswordField:)];
// someObject is the object that implements the action
[passwordTextFieldCell setTarget:someObject];
[passwordTextFieldCell setTarget:self];
[passwordTextFieldCell setAction:#selector(someAction:)];
- (void) someAction{
//handle
}

Resources