/SwiftLocation

Easy & Efficient Location Tracker for iOS

Primary LanguageSwiftMIT LicenseMIT

SwiftLocation

Carthage compatible CI Status Version License Platform

Easy & Efficient Location Tracking for iOS
🛰
Made with ♥ for Swift

★★ Star our github repository to help us! ★★

Created by Daniele Margutti (@danielemargutti)

What's SwiftLocation

SwiftLocation is a lightweight library to work with location tracking in iOS. Stop struggling with CoreLocation services settings and delegate, try now a new simple and effective way to play with location.

It provides a block based asynchronous API to request current location, either once (oneshot) or continously (subscription). It internally manages multiple simultaneous location and heading requests and efficently manage battery usage of the host device based upon running requests.

Main Features

Feature Description
Efficient Power Manager SwiftLocation automatically manage power consumption based upon currently running requests. It turns off hardware when not used, automatically.
Location Monitoring Easily monitor for your with desired accuracy and frequency (continous monitoring, background monitoring, monitor by distance intervals, interesting places or significant locations).
Device Heading Subscribe and receive continous device's heading updates
Reverse Geocoder Get location from address string or coordinates using three different services: Apple (built-in), Google (require API Key) and OpenStreetMap.
Autocomplete Places Implement your places autocomplete search with just one call, including place's details (it uses Google API)
IP Address Location Fetch current location without user authorization using device's IP address (4 services supported: freegeoip.net, api.petabyet.com, smart-ip.net, ip-api.com)
Background Location Monitoring Easily monitor location with significant location in background.
Background Monitor with Region Monitoring No yet supported

Other Libraries you may like

I'm also working on several other projects you may like. Take a look below:

Library Description
SwiftDate The best way to manage date/timezones in Swift
Hydra Write better async code: async/await & promises
Flow A new declarative approach to table managment. Forget datasource & delegates.
SwiftRichString Elegant & Painless NSAttributedString in Swift
SwiftLocation Efficient location manager
SwiftMsgPack Fast/efficient msgPack encoder/decoder

Current Version

Latest version of SwiftLocation is: 3.2.0 for Swift 4.

Changelog

Changelog is available on CHANGELOG.md file.

Documentation

Table of Contents:

Other:

SwiftLocation automatically handles obtaining permission to access location services of the host machine when you issue a location request and user has not granted your app permissions yet.

iOS 9 and iOS 10

Starting with iOS 8, you must provide a description for how your app uses location services by setting a string for the key NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your app's Info.plist file.

SwiftLocation determines which level of permissions to request based on which description key is present. You should only request the minimum permission level that your app requires, therefore it is recommended that you use the "When In Use" level unless you require more access. If you provide values for both description keys, the more permissive "Always" level is requested.

Manual Request

Sometimes you want to get the authorization manually. In this case you need to call Locator.requestAuthorizationIfNeeded by passing the auth level (always or whenInUse).

Example:

Locator.requestAuthorizationIfNeeded(.always)

iOS 11+

Starting with iOS 11, you must provide a description for how your app uses location services by setting a string for the key NSLocationAlwaysAndWhenInUseUsageDescription as well as a key for NSLocationWhenInUseUsageDescription in your app's Info.plist file.

You can also observe for changes in authorization status by subscribing auth changes events:

let token = Locator.events.listen { newStatus in
	print("Authorization status changed to \(newStatus)")
}
// In a second time you may decide to remove it
Locator.events.remove(token)
// Or remove all listeners
Locator.events.removeAll()

To get the device's current location, use the method Locator.currentPosition. This function require two parameters:

  • accuracy: The accuracy level desired (refers to the accuracy and recency of the location).
  • timeout: The amount of time to wait for a location with the desired accuracy before completing

Accuracy levels are:

Accuracy Description
city (lowest accuracy) 5000 meters or better, received within the last 10 minutes
neighborhood 1000 meters or better, received within the last 5 minutes
block 100 meters or better, received within the last 1 minute
house 15 meters or better, received within the last 15 seconds
room (highest accuracy) 5 meters or better, received within the last 5 seconds

The timeout parameter specifies how long you are willing to wait for a location with the accuracy you requested. The timeout guarantees that your block will execute within this period of time, either with a location of at least the accuracy you requested (succeded), or with whatever location could be determined before the timeout interval was up (timedout).

Timeout can be specified as:

  • after(_: TimeInterval): timeout occours after specified interval regardeless the needs of authorizations from the user.
  • delayed(_: TimeInterval): delay the start of the timeout countdown until the user has responded to the system location services permissions prompt (if the user hasn't allowed or denied the app access yet).

This is an example of the call:

Locator.currentPosition(accuracy: .city).onSuccess { location in
	print("Location found: \(location)")
}.onFailure { err, last in
	print("Failed to get location: \(err)")
}

If you don't want to require user authorization and you don't need of an accurate location you can use Locator.currentPosition(usingIP:onSuccess:onFail) function. It uses host's device IP address to retrive the nearest location of the device (remember it may be inaccurate). Location is retrived in one shot mode.

Currently four different services are supported:

Example:

Locator.currentPosition(usingIP: .smartIP, onSuccess: { loc in
	print("Find location \(loc)")
}) { err, _ in
	print("\(err)")
}

To subscribe to continuous location updates, use the method Locator.subscribePosition. The block will execute indefinitely (even across errors, until canceled), once for every new updated location regardless of its accuracy.

Example:

Locator.subscribePosition(accuracy: .city).onUpdate { loc in
	print("New location received: \(loc)")
}.onFail { err, last in
	print("Failed with error: \(err)")
}

To subscribe to significant location changes, use the method Locator.subscribeSignificantLocations. This instructs location services to begin monitoring for significant location changes, which is very power efficient. The block will execute indefinitely (until canceled), once for every new updated location regardless of its accuracy.

Note: If there are other simultaneously active location requests or subscriptions, the block will execute for every location update (not just for significant location changes).

Locator.subscribeSignificantLocations(onUpdate: { newLocation in
	print("New location \(newLocation)")
}) { (err, lastLocation) -> (Void) in
	print("Failed with err: \(err)")
}

If your app has acquired the always location services authorization and your app is terminated with at least one active significant location change subscription (see above), your app may be launched in the background when the system detects a significant location change.

Please note: when the app terminates, all of your active location requests and subscriptions with SwiftLocation are canceled automatically. Therefore, when the app launches due to a significant location change, you should immediately use SwiftLocation to set up a new subscription for significant location changes in order to receive the location information.

A good point to do it is the application's AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
	/// If you start monitoring significant location changes and your app is subsequently terminated,
	/// the system automatically relaunches the app into the background if a new event arrives.
	// Upon relaunch, you must still subscribe to significant location changes to continue receiving location events.
	if let _ = launchOptions?[UIApplicationLaunchOptionsKey.location] {
		Locator.subscribeSignificantLocations(onUpdate: { newLocation in
			// This block will be executed with the details of the significant location change that triggered the background app launch,
			// and will continue to execute for any future significant location change events as well (unless canceled).
		}, onFail: { (err, lastLocation) in
			// Something bad has occurred
		})
	}
	// the rest of the init...
	return true
}

Each request you have created via Locator function return a Request object. You can keep it to manage the lifecycle of the request.

Using Locator functions:

  • stopRequest() to stop a request (both one shot or recurring). It won't execute the block. It's valid both for heading and location requests.
  • completeLocationRequest() force the request to complete early, like a manual timeout. It will execute the block (valid only for location requests).
  • completeAllLocationRequests() Immediately completes all active location requests and execute associated blocks.

To subscribe to continuous heading updates, use the method Locator.subscribeHeadingUpdates function. It requires the following parameters:

  • accuracy: minimum accuracy (expressed in degrees) you want to receive. nil to receive all events.
  • minInterval: minimum interval between each request. nil to receive all events regardless the interval.

The block will execute indefinitely (until canceled), once for every new updated heading regardless of its accuracy. Note that if heading requests are removed or canceled, the manager will automatically stop updating the device heading in order to preserve battery life.

If an error occurs, the block will execute with a status other than succeded (error callback), and the subscription will only be automatically canceled if the device doesn't have heading support (i.e. for error unavailable).

Example:

Locator.subscribeHeadingUpdates(accuracy: 2, onUpdate: { newHeading in
	print("New heading \(newHeading)")
}) { err in
	print("Failed with error: \(err)")
}

SwiftLocation supports reverse geocoding for:

  • From Address String to Location: convert a readable address string to a valid CLLocation object with the associated coordinates
  • From Coordinates to Place: convert a coordinate expressed place to one or more Place object (with CLPlacemarks associated when using Apple service)

Currently the following services are supported for reverse geocoding:

  • Apple Built-In Service: Using built-in iOS services (CLGeocoder and CLPlacemark)
  • Google API: Using Google Maps Services. It requires API Key you can obtain for free here
  • OpenStreetMap: Using OpenStreetMap (nominatim)

Note If you are using Google API service be sure to set the API by calling Locator.api.googleAPIKey = "<API KEY VALUE>" before doing any request.

From Address String to Location

This function get a readable address and convert it in an array of Place objects. Place is and object created to group common properties (city,country,road,postalcode and so on) between all supported services. If you need of raw data of an object you can get the rawDictionary property. If you are using Apple services you can get placemark to retrive the associated CLPlacemark instance.

Example

Locator.location(fromAddress: "1 Infinite Loop", using: .openStreetMap).onSuccess { places in
	print(places)
}.onFailure { err in
	print("err")
}

From Location to Places

This function get the location via CLLocationCoordinate2D and return a list of found Place objects.

Example:

Locator.api.googleAPIKey = ...
let coordinates = CLLocationCoordinate2DMake(41.890395, 12.493083)
Locator.location(fromCoordinates: coordinates, using: .google, onSuccess: { places in
	print(places)
}) { err in
	print(err)
}

SwiftLocation allows to use the Google's Places APIs to get a list of candidate places for a given input string. It returns a list of PlaceMatch object with the main informations about the candidate place. You can retrive details about a place by calling place.details(onSuccess: { placeInfo in .... }).

Example:

Locator.autocompletePlaces(with: "123 main street", onSuccess: { candidates in
	print("Found \(candidates.count) candidates for this search")
	// get the detail of the first candidate - return a Place object.
	candidates.first?.detail(onSuccess: { placeDetail in
		print("Found detail about this place!")
	})
}) { err in
	print(err)
}

You can specify the language in which the Google Places APIs should return the results by passing a language argument.

Example:

Locator.autocompletePlaces(with: "123 main street", language: .french, onSuccess: { candidates in
	print("Found \(candidates.count) candidates for this search")
}) { err in
	print(err)
}

Please open an issue here on GitHub if you have a problem, suggestion, or other comment. Pull requests are welcome and encouraged.

Current supported version of SwiftLocation require:

  • Minimum OS: iOS 9, macOS 10.10 or watchOS 3.0
  • Swift: Swift 4

CocoaPods

Using CocoaPods

  1. Add the pod SwiftLocation to your Podfile.
pod 'SwiftLocation', '~> 3.2.0'

Run pod install from Terminal, then open your app's .xcworkspace file to launch Xcode.

Using Carthage

  1. Add the malcommac/SwiftLocation project to your Cartfile.
github "malcommac/SwiftLocation"
github "SwiftyJSON/SwiftyJSON"

You need to add SwiftyJSON as dependency because Carthage currently does not support this feature in Cartfile (see this issue on GitHub's project page)

  1. Run carthage update, then follow the additional steps required to add the iOS and/or Mac frameworks into your project.
  2. Import the SwiftLocation framework/module via import SwiftLocation