Swift: How to Get Information for an Application Which Is Not Running

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 now bundle.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 with bundle.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 name

  • Getting CFBundleDisplayName from the bundle's localizedInfoDictionary, falling back on CFBundleName and infoDictionary. This for some reason doesn't give the localised name, even though I said localizedInfoDictionary.

    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
Sample Image

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.

  1. For region
  2. 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



Leave a reply



Submit