SwiftLocation 5.0.0

SwiftLocation 5.0.0

TestsTested βœ—
LangLanguage SwiftSwift
License NOASSERTION
ReleasedLast Release Nov 2020
SPMSupports SPM βœ“

Maintained by Daniele Margutti.



SwiftLocation: Location Manager Made Easy

Location Manager Made Easy

SwiftLocation is a lightweight Swift Library which provides an easy way to work with location related functionalities. No more pain with delegates or callbacks but a simple request based architecture with multiple subscriptions and automatic tuning of the core location manager's settings.

Feature Description
πŸ›° Location via GPS Monitor single, continous or significant location updates with fully configurable APIs.
πŸ‘©β€πŸ’» Location via IP Approximate location data without user authorization.
IPApi βˆ™ IPData βˆ™ IPGeolocation βˆ™ IPInfo βˆ™ IPify βˆ™ IPStack.
πŸ›΄ Geofencing Supports monitoring of circular regions and custom polygons.
🌍 Gecoder
Reverse Geocoder
Get address from coordinates and viceversa.
Apple βˆ™ Google βˆ™ Here βˆ™ MapBox βˆ™ OpenStreet.
🏠 Autocomplete Suggestions & details for addresses, places or POIs.
Apple βˆ™ Google βˆ™ Here.
πŸ“ Beacon Beacons ranging, monitoring.
πŸ“‘ Beacon Broadcaster Broadcast beacon from the app (only foreground).
🏞 Visits Significant visited location monitoring.

🀾 Play Now!

SwiftLocation comes with a playground application you can use to learn how to use the lib or just play with each supported feature!

drawing

❀️ Your Support

Hi fellow developer!
You know, maintaing and developing tools consumes resources and time. While I enjoy making them your support is foundamental to allow me continue its development.

If you are using SwiftLocation or any other of my creations please consider the following options:

Introduction

It's pretty easy to use; all the features are accessible through the SwiftLocation object.

Each feature create and add to a pool manager a new request (conforms to RequestProtocol protocol) object you can use to get the results or fine tuning its configuration.

Result is a Result<Data, LocationError> object (where Data is a struct/class which depends by the request).
You can manage as you want but SwiftLocation also exposes two optional properties:

  • .error to get the error produced (LocationError?)
  • .data to get the result produced (<RequestInstance>.ProducedData?)

All requests are retained automatically so you don't need to keep them alive until they're running.

You can add one or more subscriptions to get the produced data by calling then() operator on request (optionally you can specify the DispatchQueue where result is returned, by default is .main).

To cancel/stop a running request (ie. an continous location update) call cancelRequest() on it: it will remove the request from the queue and adjust core location settings based upon remaining requests if needed.

To temporary pause a request while still in queue use .isEnabled property.

Discover Features

Getting user location via GPS

Just one line of code:

SwiftLocation.gpsLocation().then {
    print("Location is \($0.location)")
}

If you need more customization you can fine tune your request configuring each of the available parameters. This is a more complex example:

SwiftLocation.gpsLocationWith {
    // configure everything about your request
    $0.subscription = .continous // continous updated until you stop it
    $0.accuracy = .house 
    $0.minDistance = 300 // updated every 300mts or more
    $0.minInterval = 30 // updated each 30 seconds or more
    $0.activityType = .automotiveNavigation
    $0.timeout = .delayed(5) // 5 seconds of timeout after auth granted
}.then { result in // you can attach one or more subscriptions via `then`.
    switch result {
    case .success(let newData):
        print("New location: \(newData)")
    case .failure(let error):
        print("An error has occurred: \(error.localizedDescription)")
    }
}

.significant subscription require always authorization and works even in background.
If the app is not running and it's resumed by the system gps request is restored for you by SwiftLocation, so you can bind your own subscriptions.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // The best place to resume your requests is the app launch.
    SwiftLocation.onRestoreGPS = { requests in
        for request in requests {
            request.then {
                // do anything or bind to your own classes
            }
        }
    }
    ...
}

Getting the last known location

SwiftLocation store automatically the last known location, even between re-launches of the app:

let location = SwiftLocation.lastKnownGPSLocation

Geocoder & Reverse Geocoder

Geocoding is the process of converting addresses (like a street address) into geographic coordinates which you can use to place markers on a map, or position the map.
Reverse geocoding is the process of back (reverse) coding of a point location to a readable address or place name.

Let me show how you can use Apple service to get the coordinates of a famous place:

let service = Geocoder.Apple(address: "1 infinite loop cupertino")
SwiftLocation.geocodeWith(service).then { result in
    // You will get an array of suggested [GeoLocation] objects with data and coordinates
    print(result.data)
}

Reverse geocoding still straighforward. This is an example using google:

let service = Geocoder.Google(lat: 37.331656, lng: -122.0301426, APIKey: "<your api key>")
SwiftLocation.geocodeWith(service).then { result in
    // Different services, same expected output [GeoLocation]
    print(result.data)
}

As you noticed there is no differences in expected output; all services return the same output, an array of [GeoLocation] objects which contains the data fetched (please refer to the doc for further details).

As you see some services require API Keys; it's really boring pass the api to each request uh?
If you plan to use the same key for all services of a family consider using the shared credentials store:

SwiftLocation.credentials[.google] = "<google api key>" // setup the key

// you can now omit the APIKey parameter in your google requests!
// SwiftLocation will automatically read the shared credentials value!
let service = Geocoder.Google(address: "Via Roma 1, Milano")
SwiftLocation.geocodeWith(service).then { result in
    // ...
}

Each service has its unique settings you can configure (see the complete doc here).
Once you created the service object you can configure its own settings before sending it to the requests pool:

let service = Geocoder.MapBox(address: "Trafalgar Square")
// MapBox service has lots of configuration available to get better results
service.locale = "en"
service.limit = 1
service.boundingBox = Geocoder.MapBox.BoundingBox(minLat: 40.7661, minLon: -73.9876,
                                                  maxLat: 40.8002, maxLon: -73.9397)
service.resultTypes = [.place]
SwiftLocation.geocodeWith(service).then { result in
    print(result.data) // different services, same expected output [GeoLocation]
}

It supports the following services (some of these require API keys) under the Geocoder umbrella (check the documentation for more infos):

Get Location By IP Address

Sometimes you don't need of a precise location and don't want to bother your user authorizing the app to get the it.
In this cases you can use one of the six services available to get approximate location (plus some other location data) just by using location by ip service.

The following example shows how to get an IPLocation.Data object with all the infos retrived from current device's IP address (you can also specify other IP address in init of the service).

SwiftLocation.ipLocationWith(IPLocation.IPData()).then { result in
    print(result.location.coordinates)
    // get the city and other data from .info dictionary
    print(result.location.info[.city])
    print(result.location.info[.countryCode])
}

It supports the following services (some of these require API keys). Check the single class to read custom configurations:

Autocomplete Addresses

You can use autocomplete to give your applications the type-ahead-search behavior of the Maps apps search field. The autocomplete service can match on full words and substrings, resolving place names, addresses, and plus codes (1). Applications can therefore send queries as the user types, to provide on-the-fly place predictions.

You can also get the detailed infos about a predictions, ie when the user tap on one of the suggestions and you need to get the coordinates and other data (2).

Services are available under the Autocomplete umbrella and supports.
You can create two kinds of autocomplete:

  • (1) search for full words and substrings (.init(partialMatches: ...)
  • (2) place details (.init(detailsFor: ...))
let australia = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: -33.269290, longitude: 150.479955), latitudinalMeters: 20000, longitudinalMeters: 20000)

let service = Autocomplete.Apple(partialMatches: "Mart", region: australia)
SwiftLocation.autocompleteWith(service).then { result in
    // You get an array of `PartialAddressMatch`
    // From this array you can obtain infos with (2).
    //
    // Martial Arts Search Nearby, 
    // Mart Search Nearby, Martinsville,
    // NSW, Australia
    // ...
    let partialMatches = result.data.map { $0.partialAddress }
}

Now suppose you want to get more infos from the first result Martial Arts Search Nearby.
Just call the service with it:

let detailService = Autocomplete.Apple(detailsFor: partialMatches.first)
SwiftLocation.autocompleteWith(detailService).then { result in
    // Extract the array of found `GeoLocation` places
    let places = result.data?.map({ $0.place })
    let firstResult: GeoLocation = places?.first
    print(firstResult.coordinates) // coordinates
    // .info contains additional fetched information depending from used service
    print(firstResult.info[.formattedAddress])
    print(firstResult.info[.subAdministrativeArea]) 
    // ...
}

There is no difference if you are using Autocomplete.Google(partialMatches: "...") about expected result types, but info dictionary may contains more or less data.
Of course different services exposes different settings:

let service = Autocomplete.Google(partialMatches: "Mart")
// See the doc for all settings each service exposes!
service.placeTypes = [.cities, .address]
service.radius = 500
service.strictBounds = true
SwiftLocation.autocompleteWith(detailService).then { ... }

It supports the the following Geofencing services available under Autocomplete umbrella (check the documentation for more infos):

Geofencing Regions

Geofence is a virtual perimeter for a real-world geographic area.
And example of geofencing usage involves a location-aware device of a location-based service (LBS) user entering or exiting a geo-fence.

With SwiftLocation you can monitor:

  • circular region
  • custom polygon region (actually it does not works correctly so avoid it)
let options = GeofencingOptions(circleWithCenter: CLLocationCoordinate2D(latitude: 3, longitude: 2), radius: 100)
SwiftLocation.geofenceWith(options).then { result in
    guard let event = result.data else { return }

    switch event {
    case .didEnteredRegion(let r):
        print("Triggered region entering! \(r)")
    case .didExitedRegion(let r):
        print("Triggered region exiting! \(r)")
    default:
        break
    }
}

To monitor a custom polygon just create the appropriate GeofencingOptions to pass let options GeofencingOptions(polygon: <MKPolygon>).

Geofencing also works in background and app killed. You can restore saved session and bind to your own trigger event directly at startup:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // The best place to resume your requests when app is killed
    SwiftLocation.onRestoreGeofences = { requests in // do anything you want }
}

Significant Visits Monitoring

Sometimes you may need to monitor updates only when the user’s movements are noteworthy. The visits service is the most power-efficient way of gathering location data.
Each update includes both the location and the amount of time spent at that location. This service isn’t intended for navigation or other real-time activities (
Read More).

// You can also specify an activity To help the system determine how manage the battery use.
SwiftLocation.visits(activityType: .fitness).then { result in
    print("A new visits to \(result.data)")
}

This also works in background an app killed, so you can restore it in your app launch if you need to trigger some events.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // The best place to resume your requests when app is killed
    SwiftLocation.onRestoreVisits = { requests in // do anything you want }
}

Beacon Monitoring

An iBeacon is a device that emits a Bluetooth signal that can be detected by your devices. You decide what actions to take based on the proximity of nearby beacons (Read More).

let beacon = BeaconRequest.Beacon(uuid: <UUID>, minor: <MinorID>, major: <MajorID>)
SwiftLocation.beacon(beacon).then { result in
    guard let event = result.data else { return }
    switch event {
    case .rangingBeacons(let b):
        print("Ranged beacon: \(b)")
    case .didEnterRegion(let r):
        print("Did enter in region: \(r)")
    case .didExitRegion(let r):
        print("Did exit in \(r)")
    }
}

You can of course monitor more than one beacon or a family UUID:

SwiftLocation.beacon(BeaconRequest(UUIDs: [<UUID_Family1>,<UUID_Family2>])).then { ... }

Beacon Broadcaster

Sometimes you may need to broadcast your app as a beacon. SwiftLocation allows you to do it (however due to iOS limitations it will works only in foreground with opened app).

let beaconToSimulate = BroadcastedBeacon(UUID: <UUID>, 
                                        majorID: <MajorID>, minorID: <MinorID>, 
                                        identifier: <Identifier>)
SwiftLocation.broadcastAsBeacon(beaconToSimulate)

SwiftLocation.stopBroadcasting() allows you to stop broadcasting, while SwiftLocation.isBeaconBroadcastActive allows you to check if broadcasting is active or not.

User Authorization Request

Typically you don't need to manage authorization manually because SwiftLocation will handle it based upon the request you add to the pool manager and your Info.plist configuration (you can force preferred authorization mode by setting SwiftLocation.preferredAuthorizationMode = <AuthorizationMode>; by default is .plist).

However if you need to request authorization manually you can still use:

SwiftLocation.requestAuthorization(.onlyInUse) { newStatus in
    print("New status \(newStatus.description)")
}

To get the current authorization status use
let currentStatus = SwiftLocation.authorizationStatus

Installation

SwiftLocation is compatible with Swift 5.x+ under iOS (11+) and macOS platforms.

You can install it via CocoaPods:

use_frameworks!
pod 'SwiftLocation'

or SPM in your Package.swift:

import PackageDescription

  let package = Package(name: "YourPackage",
    dependencies: [
      .Package(url: "https://github.com/malcommac/SwiftLocation.git", majorVersion: 0),
    ]
  )

Consider ❀️ support the development of this library!

Contributing

  • If you need help or you'd like to ask a general question, open an issue.
  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.

Copyright & Acknowledgements

SwiftLocation is currently owned and maintained by Daniele Margutti.
You can follow me on Twitter @danielemargutti.
My web site is https://www.danielemargutti.com

This software is licensed under MIT License.

Follow me on: