NSApplication keyWindow is nil during applicationDidFinishLaunching - swift

Starting with a blank OS X application project, I add the following code to applicationDidFinishLaunching.
func applicationDidFinishLaunching(aNotification: NSNotification) {
let app = NSApplication.sharedApplication()
guard let window = app.keyWindow else {
fatalError("No keyWindow\n")
}
print(window)
}
At launch I hit the error case because my local window variable is nil. Yet when I show the contents of the app variable, I see a valid value for _keyWindow. Also notice that the blank GUI Window is being displayed on the screen next to the stack dump.
Why does the keyWindow: NSWindow? property return nil in this case?
Thanks

NSApplication's keyWindow property will be nil whenever the app is not active, since no window is focused for keyboard events. You can't rely on it being active when it finished launching because the user may have activated some other app between when they launched your and when it finished launching, and Cocoa is designed to not steal focus.
To some extent, you may be seeing that happen more when launching from Xcode because app activation is a bit strange in that case. But, still, you must not write your applicationDidFinishLaunching() method to assume that your app is active.
What you're seeing in terms of the app's _keyWindow instance variable is, of course, an implementation detail. One can't be certain about what it signifies and you definitely should not rely on it. However, I believe it's basically the app's "latent" key window. It's the window that will be made key again when the app is activated (unless it is activated by clicking on another of its windows).

Related

SwiftUI - How to know if a new app window has been opened after clicking the X button (Mac)?

I know there are functions in the AppDelegate like applicationDidFinishLaunching, but I haven't found a way to know when the app has been reopened after closing it (without quitting it) using the X button.
I'm trying to know which function (if any) is going to help me with that.
I'm trying to corroborate which one works by printing something, for example:
In applicationDidFinishLaunching I use print("App Launched").
In applicationShouldTerminateAfterLastWindowClosed I use print("Last Window Closed")
So I need one that theoretically prints App Reopened.
I've tried so many, including:
awakeFromNib
applicationDidBecomeActive
applicationDidUnhide
But none of them have work for me.
Thanks in advance.
I think the callback you're looking for is NSApplicaitonDelegate.applicationShouldHandleReopen(_:hasVisibleWindows:)
These events are sent whenever the Finder reactivates an already running application because someone double-clicked it again or used the dock to activate it.

How to detect another instance of a Swift app is already running

I am trying to detect in my Swift app if an instance is already running, and if so, refrain the user from launching another instance. I am using NSRunningApplication class to detect such behavior but am having trouble getting it to work properly. I have read through some of the older posts and know that flock() is a lower-level way to detect so but I would prefer to "do it the Swift way" if possible. Here is a snippet of the code:
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Check if app has already been launched. If so, alert user and terminate the new session
let bundleID = Bundle.main.bundleIdentifier!
if NSRunningApplication.runningApplications(withBundleIdentifier: bundleID).count > 1 {
// warn and terminate
NSApp.terminate(nil)
}
// keep going and finish launching the app
...
}
It appears that the count returned is always of value 1 regardless of how many sessions I am trying to open. Any idea what goes wrong, or am I not calling this check in the right place?
Additionally, my app also allows the user to double-click on a file with my defined file extension to launch the app. I assume that if I can get the above working then it should also work for this scenario as well?
UPDATE:
After reviewing the launch behavior of my app using Activity Monitor, I think I have determined the source of the observed behavior.
Full disclosure, my app creates a new process to launch a console app, and will wait until this process completes and terminates. Updated code snippets below:
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Check if app has already been launched. If so, alert user and terminate the new session
let bundleID = Bundle.main.bundleIdentifier!
let num_instances = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID).count
// Debug dialog box to see instances count
let alert = NSAlert()
alert.messageText = "msg"
alert.informativeText = "num instances running: \(num_instances)"
alert.alertStyle = NSAlert.Style.critical
alert.addButton(withTitle: "OK")
alert.runModal()
if num_instances > 1 {
// warn and terminate
NSApp.terminate(nil)
}
// keep going and finish launching the app
let task = Process()
task.launchPath = "myConsoleApp"
task.launch()
task.waitUntilExit()
let status = task.terminationStatus
if status == 0 {
// Done with the console app, exit
...
} else {
// something's wrong; report to user
...
}
}
Now, when the app is first launched, there are two processes created initially: mySwiftApp and myConsoleApp. However, with the presence of the debug dialog box, the mySwiftApp process is terminated after myConsoleApp has launched. As a result, the instance count for mySwiftApp is now 0. Therefore, subsequent launch of mySwiftApp will be successful and now there are two myConsoleApp processes running on the system. It is not clear to me why the presence of the alert box will terminate the mySwiftApp process but I suspect that it sends a termination signal to my app and changes its termination status.
If the debug dialog box is not there, due to the waitUnitlExit() setup, any attempt to launch the second instance will not be seen and essentially got dropped to the floor. Although this is the "desired" behavior since the second instance cannot be launched, it will be nice to have a way to notify the user.
Any idea what's the best way to do so?

Is there a way to remove the Push Notification Pop Up confirmation message in Fastlane screenshots?

I'm creating snapshots using Fastlane. As my application uses "Push Notifications", when the app is launched it always displays to the user the pop up that requests the authorization to send this kind of messages. There is a method that is called in the AppDelegate UIApplication.shared.registerForRemoteNotifications(), this method is the one that "shows" the pop up to the user.
I have tried something like:
#if !DEBUG
UIApplication.shared.registerForRemoteNotifications()
#endif
#if TARGET_IPHONE_SIMULATOR
UIApplication.shared.registerForRemoteNotifications()
#endif
I also tried to set a Global variable but it hasn't been possible to find a place where to set this variable, because it never works
I always get the same behavior.
I would expect that the first time I run the test in the simulator, it does not display the message.
Thanks.
I found an easy way to avoid this screenshot.
Before the screenshot is taken, I simply press the button "Allow"
let systemAlerts = XCUIApplication(bundleIdentifier: "com.apple.springboard").alerts
if systemAlerts.buttons["Allow"].exists {
systemAlerts.buttons["Allow"].tap()
}
Simple an easy :)

How to exit app and return to home screen in iOS 8 using Swift programming

I'm trying to programmatically return to the home screen in an iOS8 App using Swift. I want to continue the application running in the background though. Any ideas on how to do this?
Thanks in advance for the help.
When an app is launched, the system calls the UIApplicationMain function; among its other tasks, this function creates a singleton UIApplication object. Thereafter you access the object by calling the sharedApplication class method.
To exit gracefully (the iOS developer library explicitly warns you not to use exit(0) because this is logged as a crash ) you can use:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
For example, my app exits when the user shakes the device. So, in ViewController.swift:
override func motionEnded(motion: UIEventSubtype,
withEvent event: UIEvent?) {
if motion == .MotionShake{
//Comment: to terminate app, do not use exit(0) bc that is logged as a crash.
UIControl().sendAction(Selector("suspend"), to: UIApplication.sharedApplication(), forEvent: nil)
}}
Swift 4:
UIControl().sendAction(#selector(NSXPCConnection.suspend),
to: UIApplication.shared, for: nil)
Edit: It's worth mentioning that in iOS 12 there's a bug that will prevent network connectivity if the app is brought back from background after sending the suspend action.
For that you should use following code
import Darwin
exit(0)
To force your app into the background, you can legally launch another app, such as Safari, via a URL, into the foreground.
See: how to open an URL in Swift3
UIApplication.shared.open() (and the older openURL) are a documented public APIs.
If you set the exits-on-suspend plist key, opening another app via URL will also kill your app. The use of this key is a documented legal use of app plist keys, available for any app to "legally" use.
Also, if your app, for some impolite reason, continues to allocate and dirty large amounts of memory in the background, without responding to memory warnings, the OS will very likely kill it.
How about setting of info.plist?
You can set "Application does not run in background" true in info.plist with editor.
Or Add this lines with code editor.
<key>UIApplicationExitsOnSuspend</key>
<true/>
There is no way to "programmatically return to the home screen" except for crashing, exiting your program or calling unofficial API. Neither is welcome by Apple. Plus the Human Interface Guidelines are also covering this.

iPhone: Stopping a Program from Running in the Background

Right now, I have an application with a single map view. What should I do to stop the app from retrieving new location updates once I hit the home button. My goal is to make that arrow next to the battery symbol disappear when on the main menu page. The app will launch again when the user brings it back to the foreground. This is what I have so far, but it's not working.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
exit(0);
}
What you have should work (in fact, I have used it once for debugging purposes).
However, you should not do that, despite the fact that it works. Simply set the UIApplicationExitsOnSuspend key in the Info.plist file of your application to true.