Quickstart

Basic Integration

Let's make a quick demo application to show off the main features of Naurt.

For this demo, we want to make an app that displays the user location on the screen. So, we need to make an object that is both

  • A delegate of the NaurtLocationManager
  • An ObservableObject so changes to the object can update our Views
Swift
import SwiftUI;
import NaurtSDK; // Importing Naurt will give you access to all the classes and protocols defined later in this document

class LocationDelegate: ObservableObject, NaurtDelegate {
    var naurt: NaurtLocationManager;
    @Published public var latitude: Double? = nil;
    @Published public var longitude: Double? = nil;

    init() {
        self.naurt = NaurtLocationManager(apiKey: "<YOUR KEY HERE>");

        self.naurt.delegate = self; // We can set self as the delegate for Naurt since this object conforms to NaurtDelegate

        self.naurt.requestWhenInUseAuthorization(); // It's important that we have location permissions!
    }
}

Since our LocationDelegate method conforms to NaurtDelegate the NaurtLocationManager will invoke the functions of the LocationDelegate. However, this code will not compile since we need to add some mandatory methods to LocationDelegate, as defined by NaurtDelegate

Swift
class LocationDelegate: ObservableObject, NaurtDelegate {
    // ...

    func didUpdateLocation(_ naurtPoint: NaurtLocation) {
        // NaurtLocationManager will call this method to give us the latest location
        DispatchQueue.main.async {
            self.latitude = naurtPoint.latitude;
            self.longitude = naurtPoint.longitude;
        }
    }

    func onAppClose() {
        self.naurt.onAppClose(); // Make sure you clean up the memory!
    }
}

Now the NaurtLocationManager is able to pass NaurtLocation to us when the location updates. We extract the latitude and longitude from this.

Now, we want to represent this on the screen, so we create a simple view

Swift
struct ContentView: View {
    @ObservedObject var naurtDelegate = LocationDelegate();

    var body: some View {
        GeometryReader {geometry in
            HStack {
                let latitude = self.naurtDelegate.latitude;
                let longitude = self.naurtDelegate.longitude;

                if latitude == nil || longitude == nil {
                    Text("No position yet!");
                } else {
                    Text("Latitude: \(latitude!) Longitude: \(longitude!)");
                }
            }
        }
    }
}
Simulators
Although you could build this app with the simulator, it might never produce location fixes. To really see Naurt in action, try building this on a real iPhone and going for a walk outside!

Now, as NaurtLocationManager produces new locations, the location will be updated on the screen.

Checking the Validation Status

Let's try displaying more information on the screen. Since the NaurtSDK needs to be unlocked with a key, there is some delay between the NaurtLocationManager being created and it being validated by the Naurt servers. When the NaurtLocationManager is not validated, it will simple pass through the default location from the device. In other words, you don't have to worry about Naurt not validating causing your app to crash - you will still get locations.

The NaurtLocationManager has two ways for you to check if it is valid. The first (inferior) way is to use the getIsValidated method. This isn't really reccomended for general use outside of debugging.

Remember, that Naurt validation is asynchronous - so the following behaviour is expected

Swift
var naurt = NaurtLocationManager(apiKey: "<YOUR KEY HERE>");

print(naurt.getIsValidated()) // false

// wait some time...

print(naurt.getIsValidated()) // true

However, this isn't useful in the context of an ObservableObject, so let's instead use the delegate.

First, we'll add a new Published property to our LocationDelegate and also add the optional didChangeValidated method from the NaurtDelegate protocol. Now, when the validation status changes, the NaurtLocationManager will call that method

Swift
class LocationDelegate: ObservableObject, NaurtDelegate {
    // ...

    @Published public var validated = false;

    func didChangeValidated(_ isValidated: Bool) {
        DispatchQueue.main.async {
            self.validated = isValidated;
        }
    }
}

And then we can display this on the screen by updating our ContentView

Swift
struct ContentView: View {
    @ObservedObject var naurtDelegate = LocationDelegate();

    var body: some View {
        GeometryReader {geometry in
            VStack {

                HStack {
                    let isValid = self.naurtDelegate.validated;
                    Text("is Validated:")
                    if isValid {
                        Image(systemName:"checkmark.circle.fill").foregroundColor(.green);
                    }else{
                        Image(systemName:"xmark.circle.fill").foregroundColor(.red)
                    }
                }

                HStack {
                let latitude = self.naurtDelegate.latitude;
                let longitude = self.naurtDelegate.longitude;

                if latitude == nil || longitude == nil {
                    Text("No position yet!");
                } else {
                    Text("Latitude: \(latitude!) Longitude: \(longitude!)");
                }
            }
            }

        }
    }
}

Now, we'll get a green check or a red cross telling us the status of the Naurt validation

Automatic POIs

Let's interact with the POI system now. The NaurtLocationManager will automatically make POIs for you when your users park, if some metadata is provided. You can then later search for these POIs using that metadata via the POI API. The SDK also contains a wrapper for this API.

In order to set metadata, you use the newDestination method. You should add metadata that describes the place where you expect your user to be parking. For instance, if they are travelling to an address for a delivery, you could add that address into the metadata.

Important!

The newDestination method enables automatic POI creation which can be retrieved later with the POI API. It is crucial to maintain consistent metadata with this function, allowing accurate POI creation and future searches. Here's how it works

Example 1: On Demand Food Delivery

  1. Order Received: You’re running a food delivery app and receive an order for a restaurant. Call newDestination with the restaurant’s metadata.
  2. Driver Arrives at Restaurant: Upon parking, a parking Point Of Interest (POI) is created automatically.
  3. En Route to Final Destination: Call newDestination again, this time with the delivery destination’s metadata, once the driver has collected the food.
  4. Delivery Completed: After the food is delivered, call newDestination with nil as the metadata. This ensures that no unnecessary parking POIs are created with irrelevant metadata.

Example 2: Pre-Planned Mail Delivery

  1. Start of Workday: If you’re a mail delivery company with pre-planned routes, call newDestination with the metadata of the first delivery address as the driver starts their workday.
  2. Parcel Delivery: For each delivery, call newDestination with the next delivery address. This keeps the metadata up-to-date with the driver’s next parking location.

Summary

Make sure that the metadata provided to the newDestination method matches the state of the delivery and aligns with where drivers are expected to park. This will allow Naurt to automatically create POIs for you that can be searched later.

Let's say that you're a company with example 2 from the above pullout. We want a button on the screen the driver can press to switch to the next address. Let's add the button first

Swift
class LocationDelegate: ObservableObject, NaurtDelegate {
    // ...

    func newDestination(metadata: NSDictionary?) {
        self.naurt.newDestination(metadata: metadata);
    }
}

struct ContentView: View {
    @ObservedObject var naurtDelegate = LocationDelegate();

    var body: some View {
        GeometryReader {geometry in
            VStack {

                HStack {
                    Text("is Validated:")
                    if naurtDelegate.isValidated{
                        Image(systemName:"checkmark.circle.fill").foregroundColor(.green);
                    }else{
                        Image(systemName:"xmark.circle.fill").foregroundColor(.red)
                    }
                }

                HStack {
                let latitude = self.naurtDelegate.latitude;
                let longitude = self.naurtDelegate.longitude;

                if latitude == nil || longitude == nil {
                    Text("No position yet!");
                } else {
                    Text("Latitude: \(latitude!) Longitude: \(longitude!)");
                }
                }

                HStack {
                    Button("Next Address", action: nextAddress);
                }
            }

        }
    }
}

Now we need to create that nextAddress method

Swift
class AddressManager {
    private var addresses: [AddressJson] = [
        AddressJson(unit: "flat 12", house_name: nil, street_number: "15", street: "high street", city: "london", county: "essesx", state: "england", country: "uk", postalcode: "sw1 43g"),
        AddressJson(unit: "flat 6", house_name: nil, street_number: "4", street: "main street", city: "london", county: "essesx", state: "england", country: "uk", postalcode: "sw1 23a"),
        AddressJson(unit: nil, house_name: nil, street_number: "24", street: "low street", city: "london", county: "essesx", state: "england", country: "uk", postalcode: "sw2 44h")
        ];
    private var index = 0;

    public func getNextAddress() -> AddressJson? {
        if index > addresses.count - 1 {
            return nil;
        }

        let tmpIndex = self.index;
        self.index += 1;
        return self.addresses[tmpIndex];
    }
}


struct ContentView: View {
    @ObservedObject var naurtDelegate = LocationDelegate();

    var addressManager = AddressManager();

    // ...

    private func nextAddress() {
        let next_address: AddressJson? = self.addressManager.getNextAddress();
        let metadata: NSDictionary = ["some": "metadata"]; // optional
        self.naurtDelegate.newDestination(addressJson: next_address, metadata: metadata);
    }
}

In this example, we've hard coded the addresses. Obviously, in a real application these need to be able to change somehow! Perhaps they can come from a web request?

Finally, let's interact quickly with the POI API. This will only be a quick overview, you can read a lot more about it from the POI system. Manually interacting with the POI API requires the POIManager class. Let's add that to our LocationDelegate since it's already handling our Naurt classes. We'll also add a search method

Swift
class LocationDelegate: ObservableObject, NaurtDelegate {
    // ...

    var poiManager = POIManager(apiKey: "<YOUR KEY HERE>");
    var currnetPois: [String: [PoiEntry]]? = nil;

    func search(address: AddressJson) {
        self.poiManager.getPoi(poiId: nil, latitude: nil, longitude: nil, poiTypes: ["*"], metadata: nil, distanceFilter: nil, address_json: address, address_string: nil, additional_results: nil) {pois, statusCode, error, errorString in
            self.currentPois = pois;
        }
    }
}

In this example, we aren't going to do anything with the POIs except store them in this object. Maybe you could display them on a map?

We'll use this search method in the nextAddress method

Swift
struct ContentView: View {
    @ObservedObject var naurtDelegate = LocationDelegate();

    var addressManager = AddressManager();

    // ...

    private func nextAddress() {
        let address: AddressJson? = self.addressManager.getNextAddress();

        if address != nil {
            self.naurtDelegate.search(address: address!);
        }

        self.naurtDelegate.newDestination(addressJson: address, metadata: nil);
    }
}