Open account preferences panel in front of actual modal window in cocoa - objective-c

I have a Mac app with a preferences window. The preferences window is opened modally
-(IBAction)displayPreferencesWindow:(id)sender{
if (!pc) {
pc = [[PreferencesController alloc] initWithWindowNibName:#"PreferencesController"];
pc.delegate = self;
}
NSWindow *pcWindow = [pc window];
[NSApp runModalForWindow: pcWindow];
[NSApp endSheet: pcWindow];
[pcWindow orderOut: self];
}
In the preferences Window I have a button that opens the account preferences panel
- (IBAction)openSystemPrefs:(id)sender {
[[NSWorkspace sharedWorkspace] openFile:#"/System/Library/PreferencePanes/Accounts.prefPane"];
}
The problem is that the account preferences panel does not open in front of the actual Window. How can I achieve this?

This is kind of weird, and goes against the comments in the header, but try using this instead:
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
From the comments there:
Open a file at some path. If you use the variant without the
withApplication: parameter, or if you pass nil for this parameter, the
default application is used. The appName parameter may be a full path
to an application, or just the application's name, with or without the
.app extension. If you pass YES for andDeactivate:, or call a variant
without this parameter, the calling app is deactivated before the new
app is launched, so that the new app may come to the foreground unless
the user switches to another application in the interim. Passing YES
for andDeactivate: is generally recommended.
So it sounds like your app should be deactivating (since you're calling a variant without the andDeactivate: parameter) but I would try explicitly using the variant with that parameter, just to be sure.

Empirically, it appears that the launched application will not activate if the launching application is presenting modal UI, at least not when using the NSWorkspace API. I was able to hack something up using AppleScript that appears to achieve the desired results:
- (IBAction)doStuff:(id)sender
{
[[NSWorkspace sharedWorkspace] openFile:#"/System/Library/PreferencePanes/Accounts.prefPane"];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSString* s = #"tell application \"System Preferences\" to activate";
NSAppleScript* as = [[[NSAppleScript alloc] initWithSource: s] autorelease];
NSDictionary* error = nil;
if ([as compileAndReturnError: &error])
{
(void)[as executeAndReturnError: &error];
}
});
}
I dispatched it to the background queue because it takes a few hundred milliseconds to compile and run the AppleScript and it's a little conspicuous if done synchronously (the button stays highlighted longer than you expect).
If you were feeling really masochistic, you could probably get the script compiling phase out of it (i.e. quicker) by conjuring up the equivalent AppleEvents and sending them directly, but this appeared to achieve the desired effect, even while presenting modal UI in the launching application.

Related

iCloud enabled - Stop the open file displaying on application launch?

I've just added iCloud support to an app that I am working on. Its working great, except that when I open the application without a document in focus the iCloud open file dialog appears and I don't want it to!
In my app delegate I have:
- (BOOL) applicationShouldOpenUntitledFile:(NSApplication *)sender
{
[mainWindowController.window makeKeyAndOrderFront:self];
return NO;
}
Which I use to show my own custom window. However now, both the iCloud open file dialog and my own dialog are displayed. Any ideas on how I can get rid of the iCloud dialog?
https://developer.apple.com/library/prerelease/content/releasenotes/AppKit/RN-AppKitOlderNotes/index.html
NSDocument Support for iCloud
In 10.8, NSDocument-based applications with a ubiquity-container-identifiers entitlement gain new functionality and UI to facilitate iCloud document management.
When iCloud is enabled and an application is first launched or re-activated and no windows are visible or being restored, instead of creating a new Untitled document, NSDocumentController will display a non-modal open panel showing the user's iCloud library.
...
Applications that do not wish to use these features for any or all of their NSDocument subclasses can override +[NSDocument usesUbiquitousStorage] and return NO. If all of the application's declared NSDocument subclasses return NO from this method, then NSDocumentController will never show the new non-modal open panel.
So if you can give up using the features listed in this release note, return NO at +[NSDocument usesUbiquitousStorage].
I confirmed you can still open/save your file into iCloud storage from the normal dialog.
Putting below codes in your App Delegate lets you bypass that iCloud pop up New Document screen. Tested for High Sierra.
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
// Schedule "Checking whether document exists." into next UI Loop.
// Because document is not restored yet.
// So we don't know what do we have to create new one.
// Opened document can be identified here. (double click document file)
NSInvocationOperation* op = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(openNewDocumentIfNeeded) object:nil];
[[NSOperationQueue mainQueue] addOperation: op];
}
-(void)openNewDocumentIfNeeded
{
NSUInteger documentCount = [[[NSDocumentController sharedDocumentController] documents]count];
// Open an untitled document what if there is no document. (restored, opened).
if(documentCount == 0){
[[NSDocumentController sharedDocumentController]openUntitledDocumentAndDisplay:YES error: nil];
}
}
- (BOOL) applicationShouldOpenUntitledFile:(NSApplication *)sender
{
[mainWindowController.window makeKeyAndOrderFront:self];
return NO;
}
This part is correct. I've just tested it.
Just make sure your that this class is really your app delegate.
Make a new class called prefixAppDelegate
In your MainMenu.xib, drag a new object to the side and set it's custom class to the app delegate class
Right click Application and drag from Delegate down to your app delegate object.
Now just paste the code above into your app delegate class
If that still doesn't help, try logging something in applicationShouldOpenUntitledFile:.
Also, I recommend not to set [mainWindowController.window makeKeyAndOrderFront:self]; in this method. You should rather use the app delegate method applicationDidFinishLaunching: method.
My observation and fix:
[applicationShouldOpenUntitledFile:] won't be executed except you remove Key NSDocumentClass from *-info.plist. But this is harmful if your app is document based application, it won't open the document type you linked.
My fix is open my customised window directly in -(void)applicationWillFinishLaunching:(NSNotification *)notification method (Application delegate)
ETDocumentWindowController *windowController = (ETDocumentWindowController*)get your own window controller here...;
[windowController.window makeKeyAndOrderFront:nil];
I thought I would share my solution to this issue as I see others still looking for an answer. Its not a great solution but it does the trick.
Subclass NSDocumentController and add the following:
+ (void) setCanOpenUntitledDocument: (BOOL) _canOpenUntitledDocument
{
canOpenUntitledDocument = _canOpenUntitledDocument;
} // End of setCanOpenUntitledDocument:
- (void) openDocument: (id) sender
{
// With iCloud enabled, the app keeps trying to run openDocument: on first launch (before apphasfinishedlaunching gets set.
// This method lets us check and see if the app has finished launching or not. If we try to open a document before
// its finished, then don't let it.
if(!canOpenUntitledDocument)
{
return;
} // End of appHasFinishedLaunching not set
[super openDocument: sender];
} // End of openDocument:
Add the following to your app delegate:
- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{
// Finished launching. Let us open untitled documents.
[SQLProDocumentController setCanOpenUntitledDocument: true];
...
}
And the reasoning -- By setting a breakpoint in openDocument I've found that its called before applicationDidFinishLaunching, applicationShouldOpenUntitledFile or applicationShouldHandleReopen:hasVisibleWindows: get called, meaning adding those methods is useless. Again, it's not great code but it works and does the trick. (None of the other solutions have worked for me).
I ran into a similar problem -- it turned out that in my case, I had to remove the NSDocumentClass key and value from my Info.plist in the CFBundleDocumentTypes array. Only then would the applicationShouldOpenUntitledFile: method get called and thus allow me to prevent the iCloud/Document window from opening.

Modal NSAlert within block not displayed

In my document-based application, I have overridden the method openDocument: in my subclass of NSDocumentcontroller so that I can display my own openPanel. I pass the chosen URLs to the method openDocumentWithContentsOfURL:display:completionHandler:. I use this code for the call:
[self openDocumentWithContentsOfURL:[chosenFiles objectAtIndex:i] display:YES completionHandler:^(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error) {
if (document == nil)
{
NSAlert* alert = [NSAlert alertWithError:error];
[alert runModal];
}
}];
So I want to display the passed error if nil gets returned as a reference to the document. The problem is, that the program just "freezes" after I press the "Open" button in the open panel. Then I need to manually stop the program with the "stop" button in Xcode. No spinning beach ball appears, though. If I comment the line "[alert runModal]", the program does not freeze any more, but no alert is displayed, of course.
Now the strange thing: The code works sometimes. If I switch from Xcode to my browser and back and I run the program again, it sometimes works flawlessly and the error is displayed. After some time it stops working again. It is unpredictable, but most of the time it doesn't work.
All this sounds like a race-condition to me. It certainly has something to do with the block? But what do I do wrong?
Converting my comment to an answer:
runModel on the main thread.
[alert performSelectorOnMainThread:#selector(runModal) withObject:nil waitUntilDone:NO];
I think runModel needs to be called on the main thread because it's part of the AppKit framework, and it's estentially triggering UI graphics. I believe all calls to the AppKit framework or to any method that manipulates graphics needs to be on the main thread.

customize an action of iphone home button to submit score in gamecenter

i have a button in my app a button that submit score to gamecenter and works.
this is the code:
-(void)subScore{
GKScore *scoreRepoter = [[[GKScore alloc] initWithCategory:#"123456"] autorelease];
scoreRepoter.value=100;
[scoreRepoter reportScoreWithCompletionHandler:^(NSError *error) {
if (error!=nil) {
NSLog(#"errr submitting");
}else
NSLog(#"ok!");
}];
now i'd like to submit score before app is closed with home button.
i thought to customize an action of home button (if it is possible)
or perhaps i make the same line of code in viewDidUload...or something like that...
will i be sure that that action will be performed before unloading the app?
i should make that code in dealloc method?
thanks
You can't customize behaviour of Home button directly, but iOS provides some methods in your application's delegate, by which you can control lifecycle of the application.
Method called right before the application goes to background is applicationWillResignActive: in your application's delegate (usually this method is located in AppDelegate.m file).
I think you can get needed effect by calling your method like that:
- (void)applicationWillResignActive:(UIApplication *)application {
[mygame subScore];
}
Also please note that iOS has time limit of execution for this method: you must do all saving-the-game work in less that five seconds or your application will be killed.

How to get Main Window (App Delegate) from other class (subclass of NSViewController)?

I'm trying to change my windows content, from other class , that is subclass of NSViewController.I'm trying code below, but it doesn't do anything.
[NSApplication sharedApplication]mainWindow]setContentView:[self view]]; //code in NSViewController
[NSApplication sharedApplication]mainWindow] // returns null
I tried to add
[window makeMainWindow];
in App Delegate class, but it won't help.
Did I miss something?
P.S. Also I'm using code below to call any delegate function in my class,
[(appDelegate *) [[NSApplication sharedApplication]delegate]MyMethod];
but I wonder is there something better, wihtout importing delegate class. Something like this
[[NSApplication sharedApplication]delegate]MyMethod];
(it gives warning)
For the mainWindow method the docs say:
This method might return nil if the application’s nib file hasn’t finished loading, if the receiver is not active, or if the application is hidden.
I just created a quick test application and I placed the following code:
NSLog(#"%#", [[NSApplication sharedApplication] mainWindow]);
into my applicationDidFinishLaunching:aNotification method, and into an action method which I connected to a button in the main window of my application.
On startup, the mainWindow was nil, but when I click the button (after everything is up and running and displayed), the mainWindow was no longer nil.
NSApplication provides other methods which you may be useful to you:
- windows - an array of all the windows;
– keyWindow - gives the window that is receiving keyboard input (or nil);
– windowWithWindowNumber: - returns a window corresponding to the window number - if you know the number of the window whose contents you wish to replace you could use this;
– makeWindowsPerform:inOrder: - sends a message to each window - you could use this to test each window to see if it's the one you are interested in.
With regard to calling methods on the delegate, what you say gives a warning works fine for me. For example, this works with no warnings:
NSLog(#"%#", [[[NSApplication sharedApplication]delegate] description]);
What exactly is the warning you receive? Are you trying to call a method that doesn't exist?
Fighting with MacOS just figured this out.
Apple's quote:
mainWindow
Property
The app’s main window. (read-only)
Discussion
The value in this property is nil when the app’s storyboard or nib file has not yet finished loading. It might also be nil when the app is inactive or hidden.
If you have only one window in your application (which is the most used case) use next code:
NSWindow *mainWindow = [[[NSApplication sharedApplication] windows] objectAtIndex:0];
Promise it won't be nil, if application has windows.
Swift flavored approaches for getting the main window (if present)
Application Main Window
guard let window = NSApplication.shared.mainWindow,
else {
// handle no main window present
}
// ... access window here
Application Window Array
let windowArray: [NSWindow] = NSApplication.shared.windows
guard windowArray.count > 0 else {
// hand case where no windows are present
}
let window = windowArray[0]
// ... access window here
If the window property isn't set yet, try delaying things until the app has finished loading, like so:
[myObject performSelector:#selector(theSelector) withObject:nil afterDelay:0.1];

Opening a new window and waiting for it to close

I have a Mac OS X app written in objetive-c Cocoa. You can see most of the code in this previous question. Essentially you click a button on the main window (the app delegate) and it opens another window where the user can enter information.
In the following code (that gets called when the user press the button in the app's main window)
- (IBAction)OnLaunch:(id)sender {
MyClass *controllerWindow = [[MyClass alloc] initWithWindowNibName:#"pop"];
[controllerWindow showWindow:self];
NSLog(#"this is a log line");
}
The NSLog line gets printer immediately after I called showWindow. Is there any way to wait until controllerWindow is closed to continue with the NSlog?
The reason for this is that the user set's a value on the new window I opened and I need to collect that value on the same OnLaunch so I need to wait.
I know that modal windows are bad form in Mac, but I have no control over this feature.
I've tried with
[NSApp runModalForWindow:[controllerWindow window]];
and then setting the popup window to
[[NSApplication sharedApplication] runModalForWindow:popupwin];
and it works but then the focus never gets passed to the main window anymore
Thanks!
If you want the window to be modal for your application, use a sheet: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Sheets/Tasks/UsingCustomSheets.html
However, there is no way to suspend execution of a method while the sheet is displayed, this would be tantamount to blocking the current run loop. You would have to break you code into the begin and end methods as described in the linked documentation.
Here are the steps you need to follow:
In TestAppAppDelegate create an NSWindow outlet to hold your sheet and an action to dismiss the sheet
Create a nib with an NSWindow as the root object. I think you already have this in "pop". Set the Visible at Launch option to NO (this is very important)
Set the file's owner of this nib to TestAppAppDelegate and connect the window to your new outlet, and the close button to your new action
In your method to launch the sheet (OnLaunch), use the following code:
(ignore this it's to make the code format properly!)
if(!self.sheet)
[NSBundle loadNibNamed:#"Sheet" owner:self];
[NSApp beginSheet:self.sheet
modalForWindow:self.window
modalDelegate:self
didEndSelector:#selector(didEndSheet:returnCode:contextInfo:)
contextInfo:nil];
Your close button action should be [NSApp endSheet:self.sheet];
Your didEndSheet: method should be [self.sheet orderOut:self];
You can use UIVIew method animateWithDuration:delay:options:animations:completion: to accomplish this.
You said you want the next line to execute once the window is closed, rather than after it is opened. In any case, you may end the OnLaunch method this way:
- (IBAction)OnLaunch:(id)sender {
MyClass *controllerWindow = [[MyClass alloc] initWithWindowNibName:#"pop"];
[controllerWindow animateWithDuration:someDelay:options: someUIAnimationOption
animations:^{
[controllerWindow showWindow:self]; // now you can animate it in the showWindow method
}
completion:^{
[self windowDidFinishShowing]; // or [self windowDidFinishDisappearing]
}
}
- (void) windowDidFinishShowing {
NSLog(#"this is a log line");
}