Swift: Can I get information for an application which is not running?
First, get the application's bundle using
let bundleURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: someBundleID)!
let bundle = Bundle(url: bundleURL)!
Once you have the bundle, you can get most of the things that you can get from a NSRunningApplication
:
bundleIdentifier
you already know.bundleURL
you just got with the code above.executableArchitecture
is nowbundle.executableArchitectures
, with a plural. Because it's not running, you get all the architectures that it can be run on, rather than the specific architecture that it is running on.executableURL
you can get withbundle.executableURL
, no problems there.launchDate
,isFinishedLaunching
,processIdentifier
,ownsMenuBar
are nonsensical to get, since the application is not running.
Now we are left with icon
and localizedName
. The methods I propose for getting these are less reliable. To get icon
, do
NSWorkspace.shared.icon(forFile: bundleURL.path)
I'm sure this produces the same result as NSRunningApplication.icon
at least 99% of the time, but I'm not sure if there are edge cases...
To get localizedName
is more unreliable. There are 2 ways, neither of which are good. Choose the one that fits your purpose the best
FileManager.default.displayName(atPath: bundleURL.path)
This gives you the localised name, but if the user renames the application, it will return the new name, rather than the bundle nameGetting
CFBundleDisplayName
from the bundle'slocalizedInfoDictionary
, falling back onCFBundleName
andinfoDictionary
. This for some reason doesn't give the localised name, even though I saidlocalizedInfoDictionary
.let infoDict = (bundle.localizedInfoDictionary ?? bundle.infoDictionary)
let localizedName = infoDict?["CFBundleDisplayName"] ?? infoDict?["CFBundleName"]
Debugging while app is not running
You can launch the app in Xcode but set the scheme to wait for an external launch trigger. In this mode Xcode will watch and wait for something else to open the app.
To do this, edit the scheme, select the info
section and then select Wait for executable to be launched
.
How to prevent my app from running in the background on the iPhone
See opting out of background execution in the iphone application programming guide:
"If you do not want your application to remain in the background when it is quit, you can explicitly opt out of the background execution model by adding the UIApplicationExitsOnSuspend key to your application’s Info.plist file and setting its value to YES.
When an application opts out, it cycles between the not running, inactive, and active states and never enters the background or suspended states.
When the user taps the Home button to quit the application, the applicationWillTerminate: method of the application delegate is called and the application has approximately five seconds to clean up and exit before it is terminated and moved back to the not running state."
Receiving Location even when app is not running in Swift
It may or may not be a good solution but if I were you I would have used both startMonitoringSignificantLocationChanges
and regionMonitoring
.
Here is the sample I made which worked well with iOS 13.
Lets take regionMonitoring
first. We have certainly no problems when the app is in foreground state and we can use the CLLocationManager's didUpdate
delegate to get the location and send it to the server.
Keep latest current location in AppDelegate's property, lets say:
var lastLocation:CLLocation?
//And a location manager
var locationManager = CLLocationManager()
We have two UIApplicationDelegates
func applicationDidEnterBackground(_ application: UIApplication) {
//Create a region
}
func applicationWillTerminate(_ application: UIApplication) {
//Create a region
}
So whenever the user kills the app or makes the app go to background, we can certainly create a region around the latest current location fetched. Here is an example to create a region.
func createRegion(location:CLLocation?) {
if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
let coordinate = CLLocationCoordinate2DMake((location?.coordinate.latitude)!, (location?.coordinate.longitude)!)
let regionRadius = 50.0
let region = CLCircularRegion(center: CLLocationCoordinate2D(
latitude: coordinate.latitude,
longitude: coordinate.longitude),
radius: regionRadius,
identifier: "aabb")
region.notifyOnExit = true
region.notifyOnEntry = true
//Send your fetched location to server
//Stop your location manager for updating location and start regionMonitoring
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoring(for: region)
}
else {
print("System can't track regions")
}
}
Make use of RegionDelegates
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("Entered Region")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("Exited Region")
locationManager?.stopMonitoring(for: region)
//Start location manager and fetch current location
locationManager?.startUpdatingLocation()
}
Grab the location from didUpdate
method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if UIApplication.shared.applicationState == .active {
} else {
//App is in BG/ Killed or suspended state
//send location to server
// create a New Region with current fetched location
let location = locations.last
lastLocation = location
//Make region and again the same cycle continues.
self.createRegion(location: lastLocation)
}
}
Here I have made a 50m region radius circle. I have tested this and it is called generally after crossing 100m from your center point.
Now the second approach can me using significantLocationChanges
On making the app go background or terminated, we can just stop location manager for further updating locations and can call the startMonitoringSignificantLocationChanges
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoringSignificantLocationChanges()
When the app is killed, the location is grabbed from didFinishLaunching
method's launchOptions?[UIApplicationLaunchOptionsKey.location]
if launchOptions?[UIApplicationLaunchOptionsKey.location] != nil {
//You have a location when app is in killed/ not running state
}
Make sure to keep BackgroundModes On for Location Updates
Also make sure to ask for locationManager?.requestAlwaysAuthorization()
by using the key
<key>NSLocationAlwaysUsageDescription</key>
<string>Allow location</string>
in your Info.plist
There can be a third solution by taking 2 LocationManagers simultaneously.
- For region
- Significant Location Changes
As using significantLocationChanges
Apps can expect a notification as soon as the device moves 500 meters
or more from its previous notification. It should not expect
notifications more frequently than once every five minutes. If the
device is able to retrieve data from the network, the location manager
is much more likely to deliver notifications in a timely manner.
as per the give Apple Doc
So it totally depends on your requirements as the location fetching depends on many factors like the number of apps opened, battery power, signal strength etc when the app is not running.
Also keep in mind to always setup a region with good accuracy.
I know that this will not solve your problem completely but you will get an idea to move forward as per your requirements.
How can I detect when the swift application is foreground from the background?
You probably need willEnterForegroundNotification.
Posted shortly before an app leaves the background state on its way to
becoming the active app.
Keep in mind thow that this notification posted olso after application(_:didFinishLaunchingWithOptions:)
when opening the application for the first time.
When application opens for the first time the applicationState
is inactive and when the application comes from the background the applicationState
is background.
So, your code will be something like this:
token = NotificationCenter.default.addObserver(
forName: UIApplication.willEnterForegroundNotification,
object: nil,
queue: .main
) { (notification: Notification) in
if UIApplication.shared.applicationState == .background {
// Came from the background
}
}
Related Topics
Is There Difference Between Using a Constructor and Using .Init
Comparing Objects in an Array Extension Causing Error in Swift
Collection View Layout Bug When Selectitem (Swift 5)
Learning Swift: Expressions Are Not Allowed at the Top Level
Ios8: Auto-Layout and Gradient
Decode/Encode Dictionary Keyed by Date
Unrecognized Selector Sent to Instance When No Related Entities Found in Core Data
How to Comprehend the "First-Class Function" in Swift
What Does an Exclamation Mark in a Property in Swift Language
Why Do We Need to Specify Init Method
Swift: Ambiguous Reference to Member 'Map'
Nsurl from String Gets Truncated, Dots on the Uivideoeditorcontroller's Video Path Swift 2 iOS 8
Swiftui: Unexpected Animation When Using a Non @State Var
How to Use a Specific Gmt for a Function Which Will Be Recognised by Other Time Zones