> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/help/llms.txt)

# Implement Geofencing in an iOS App

This tutorial teaches you how to implement geofencing in an iOS app using the **Mapbox Maps SDK for iOS**. Geofencing is a location-based service that allows you to define a virtual fence around a real-world geographic area. When a device enters, dwells in, or exits the perimeter, the app can trigger a notification or do other actions.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-geofencing-complete-f9c266423103c8742294fab697214597.mp4).

You'll learn to add GeoJSON data to your project and track geofences for user events. This tutorial also guides you through simulating user location to watch these events in real-time and displaying geofence events dynamically in your app. Additionally, you'll implement styling changes to geofenced areas, enhancing the app's interactivity and user experience.

## Prerequisites

Before you begin, you will need:

-   A **Mapbox account**: [Sign up](https://account.mapbox.com/auth/signup/) for free or [log in](https://console.mapbox.com/) if you already have an account.
-   **A project with the Maps SDK installed**: Follow the [Getting Started with the Maps SDK for iOS](https://docs.mapbox.com/ios/maps/guides/install/) guide.
-   **Xcode**: Version 16.2 or later
-   **Familiarity with iOS development**: Beginner experience with Swift and iOS Development..

This tutorial assumes you have a basic understanding of Swift and iOS development and requires you to have setup the **Mapbox Maps SDK for iOS** in a SwiftUI project as covered in our [Getting Started with Mapbox on iOS](https://docs.mapbox.com/ios/maps/guides/install/) guide. That guide walks you through adding the **Mapbox Maps SDK for iOS** dependency (version 11.11.0 and later to use geofencing) and adding your public access token to your project's `Info.plist` file as seen below.

![Screenshot of the Info.plist file with the MBXAccessToken key highlighted.](https://docs.mapbox.com/help/assets/ideal-img/tutorials--ios-mbxaccesstoken.6cfa0af.480.png)

The Getting Started guide should leave you with a new SwiftUI project open in Xcode with a globe displayed.

![Screenshot of an iOS app showing a globe.](https://docs.mapbox.com/help/assets/ideal-img/tutorials--ios-geofencing-1.dd91bc2.480.png)

## Add geofence data to your app

To set up geofences you need to add geographic data that defines the regions you want to watch. For this tutorial, we will use a GeoJSON file that defines several polygons in [Yellowstone National Park](https://en.wikipedia.org/wiki/Yellowstone_National_Park). In this section we add the GeoJSON file to our project and parse it into a [FeatureCollection](https://docs.mapbox.com/ios/maps/api/latest/documentation/turf/featurecollection/). We will then use the `FeatureCollection` to visualize the polygons on the map using a [GeoJSONSource](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/geojsonsource/) and [FillLayer](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/filllayer/).

### Download the GeoJSON file and add it to your project

Download the Yellowstone National Park GeoJSON file and add it to your project as `yellowstone.geojson`. To add the file to your project, select File > Add Files to "YourProjectName"... and select the GeoJSON file. Make sure the Action is "Copy files to destination" and you select the correct target.

[Download GeoJSON](https://docs.mapbox.com/help/help/data/yellowstone.geojson)

### Decode the GeoJSON file into a FeatureCollection

To start, adjust the initial camera viewport of the map to center on Yellowstone National Park (latitude: 44.5979, longitude: -110.6123) and zoom to level 9.

For the map to show the geographic data in the GeoJSON file, you need to parse the file into a [FeatureCollection](https://docs.mapbox.com/ios/maps/api/latest/documentation/turf/featurecollection/). We will use a helper function to do this. Add the following code `decodeGeoJSON(from:)` function to your project.

```swift
private func decodeGeoJSON(from fileName: String) -> FeatureCollection {
    guard let path = Bundle.main.path(forResource: fileName, ofType: "geojson") else {
        preconditionFailure("File '\(fileName)' not found.")
    }

    let filePath = URL(fileURLWithPath: path)
    var featureCollection: FeatureCollection
    do {
        let data = try Data(contentsOf: filePath)
        featureCollection = try JSONDecoder().decode(FeatureCollection.self, from: data)
    } catch {
        print("Error parsing data: \(error)")
        featureCollection = FeatureCollection(features: [])
    }

    return featureCollection
}
```

We will use this function to parse the `yellowstone.geojson` file into a FeatureCollection and store it in a property in our View above the `body`.

```swift
let featureCollection = decodeGeoJSON(from: "yellowstone")
```

### Display the GeoJSON polygons on the map

Then, inside the map we will add a [GeoJSONSource](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/geojsonsource/) using the data in this [FeatureCollection](https://docs.mapbox.com/ios/maps/api/latest/documentation/turf/featurecollection/) and a [FillLayer](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/filllayer/) to render the polygons on the map.

```swift
Map(initialViewport: .camera(center: center, zoom: 9, bearing: 0, pitch: 0)) {
    GeoJSONSource(id: "yellowstone-locations")
        .data(.featureCollection(featureCollection))

    FillLayer(id: "yellowstone-regions", source: "yellowstone-locations")
}
```

We will adjust the styling later. For now, you should see a few black polygons rendered on the map.

![Screenshot of an iOS app showing a map of Yellowstone National Park with black polygons.](https://docs.mapbox.com/help/assets/ideal-img/tutorials--ios-geofencing-2.93e16a0.480.png)

Your full `ContentView` should look like this:

```swift
import SwiftUI
@_spi(Experimental) import MapboxMaps // Geofencing is an experimental feature so we need to use the experimental import

struct ContentView: View {
    let featureCollection = decodeGeoJSON(from: "yellowstone")

    var body: some View {
        let center = CLLocationCoordinate2D(latitude: 44.5979, longitude: -110.6123)
        Map(initialViewport: .camera(center: center, zoom: 9, bearing: 0, pitch: 0)) {
            GeoJSONSource(id: "yellowstone-locations")
                .data(.featureCollection(featureCollection))

            FillLayer(id: "yellowstone-regions", source: "yellowstone-locations")
        }
        .ignoresSafeArea()
    }
}

// Load GeoJSON file from local bundle and decode into a `FeatureCollection`.
private func decodeGeoJSON(from fileName: String) -> FeatureCollection {
    guard let path = Bundle.main.path(forResource: fileName, ofType: "geojson") else {
        preconditionFailure("File '\(fileName)' not found.")
    }

    let filePath = URL(fileURLWithPath: path)
    var featureCollection: FeatureCollection
    do {
        let data = try Data(contentsOf: filePath)
        featureCollection = try JSONDecoder().decode(FeatureCollection.self, from: data)
    } catch {
        print("Error parsing data: \(error)")
        featureCollection = FeatureCollection(features: [])
    }

    return featureCollection
}

#Preview {
    ContentView()
}
```

## Show user location on the map

This step will allow your app to show the user's location on the map. To do this, you need to request location permissions and add a puck to the map to show the user's location. You do not need to show a user puck to use geofencing, but it is helpful in this example to see the user's location in the geofenced areas. You do need to request location permissions to use geofencing and show the user's location on the map in all circumstances.

### Request location permissions

To use geofencing you need to request user's permission to access their precise location. Review our [User Location](https://docs.mapbox.com/ios/maps/guides/user-location/) guide for complete information on requesting location permissions, responding to changes in location permission authorization, and displaying a user puck. For this tutorial, add the following code to your `Info.plist` file to request location permissions.

```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your precise location is used to calculate turn-by-turn directions, show your location on the map, and help improve the map.</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
  <key>LocationAccuracyAuthorizationDescription</key>
  <string>Please enable precise location. Geofencing only works when precise location data is available.</string>
</dict>
```

### Show the user's location on the map

Now that you have requested location permissions, you can show the user's location on the map. Add a [Puck2D](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/puck2d/) to your [Map](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/map/) to show the user's location on the map.

```swift
Map(initialViewport: .camera(center: center, zoom: 9, bearing: 0, pitch: 0)) {
    Puck2D()

    GeoJSONSource(id: "yellowstone-locations")
        .data(.featureCollection(featureCollection))

    FillLayer(id: "yellowstone-regions", source: "yellowstone-locations")
}
```

Run your application on simulator and set the location to a location within Yellowstone National Park. To simulate a location, select the simulator and go to `Features` > `Location` > `Custom Location...` and enter the same coordinates as the map camera: `latitude: 44.5979, longitude: -110.6123`. You should see a blue dot representing the user's location on the map, like this:

![Screenshot of an iOS app showing a map of Yellowstone National Park with a blue dot representing the user's location.](https://docs.mapbox.com/help/assets/ideal-img/tutorials--ios-geofencing-3.c4aa7a9.480.png)

## Watch geofence events

Now that you have added geographic data to your app and are showing the user's location on the map, you can add geofences and watch when the user [enters](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingobserver/onentry(event:)/), [dwells in](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingobserver/ondwell(event:)/), or [exits](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingobserver/onexit(event:)/) the geofence.

### Add geofences to the map

Create a new `Geofencing` class that conforms to `ObservableObject` with two methods `start` and `add`. The `start` method will configure the [GeofencingService](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingservice/) and add an observer to watch [GeofencingEvents](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingevent/). The add method registers a new feature with the geofencing service, defining the geofence to be watched.

```swift
private final class Geofencing: ObservableObject {
    func start(_ completion: @escaping () -> Void) {
        let geofencing = GeofencingFactory.getOrCreate()
        geofencing.configure(options: GeofencingOptions()) { [weak self] result in
            guard let self else { return }
            /// Geofences are stored in database on disk.
            /// To make this example isolated and synchronized with the UI we delete existing feature from database.
            geofencing.clearFeatures { result in
                geofencing.addObserver(observer: self) { result in 
                  print("Add observer: \(result)") 
                }
                completion()
            }
        }
    }

    func add(feature: Turf.Feature) {
        let geofencing = GeofencingFactory.getOrCreate()
        geofencing.addFeature(feature: feature) { result in
            print("Add feature result: \(result)")
        }
    }
}
```

Next, we will extend our `Geofencing` class to conform to [GeofencingObserver](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingobserver/) and implement the required methods. For now, we will print the geofencing events to the console.

```swift
extension Geofencing: GeofencingObserver {
    func onEntry(event: GeofencingEvent) {
        DispatchQueue.main.async {
            print(event.feature)
        }
    }

    func onDwell(event: GeofencingEvent) {
        DispatchQueue.main.async {
            print(event.feature)
        }
    }

    func onExit(event: GeofencingEvent) {
        DispatchQueue.main.async {
            print(event.feature)
        }
    }

    func onError(error: GeofencingError) {
        DispatchQueue.main.async {
            print(error)
        }
    }

    func onUserConsentChanged(isConsentGiven: Bool) {
        DispatchQueue.main.async {
            print("Is consent given: \(isConsentGiven)")
        }
    }
}
```

Now that we've added the `Geofencing` class, we will add an instance of it to our `ContentView` and call the `start` method in the `onMapLoaded` modifier. For each feature in the `featureCollection`, we will call the `add` method to add the feature to the Geofencing service. Now the Geofencing service will watch those features and trigger events when the user enters, dwells in, or exits the geofenced area. To receive dwell events, we need to set a time using [GeofencingPropertiesKeys.dwellTimeKey](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingpropertieskeys/). After a user has spent that amount of time in the geofence, the Geofencing service will send a dwell event.

```swift
@ObservedObject private var geofencing = Geofencing()
... 
Map {
  ...
}
.onMapLoaded { _ in
    geofencing.start {
        for feature in featureCollection.features {
            // To receive dwell events we need to set a time.
            // After a user has spent that amount of time in the geofence
            // the Geofencing service will send a dwell event
            var geofencingFeature = feature
            geofencingFeature.properties?[GeofencingPropertiesKeys.dwellTimeKey] = 1 // minutes
            geofencing.add(feature: geofencingFeature)
        }
    }
}
```

At this point your full code should look like this:

```swift
import SwiftUI
@_spi(Experimental) import MapboxMaps

struct ContentView: View {
    @ObservedObject private var geofencing = Geofencing()
    let featureCollection = decodeGeoJSON(from: "yellowstone")

    var body: some View {
        let center = CLLocationCoordinate2D(latitude: 44.5979, longitude: -110.6123)
        Map(initialViewport: .camera(center: center, zoom: 9, bearing: 0, pitch: 0)) {
            Puck2D()

            GeoJSONSource(id: "yellowstone-locations")
                .data(.featureCollection(featureCollection))

            FillLayer(id: "yellowstone-regions", source: "yellowstone-locations")
        }
        .onMapLoaded { _ in
            geofencing.start {
                for feature in featureCollection.features {
                    // To receive dwell events we need to set a time.
                    // After a user has spent that amount of time in the geofence
                    // the Geofencing service will send a dwell event
                    var geofencingFeature = feature
                    geofencingFeature.properties?[GeofencingPropertiesKeys.dwellTimeKey] = 1 // minutes
                    geofencing.add(feature: geofencingFeature)
                }
            }
        }
        .ignoresSafeArea()
    }
}

private final class Geofencing: ObservableObject {
    func start(_ completion: @escaping () -> Void) {
        let geofencing = GeofencingFactory.getOrCreate()
        geofencing.configure(options: GeofencingOptions()) { [weak self] result in
            guard let self else { return }
            /// Geofences are stored in database on disk.
            /// To make this example isolated and synchronized with the UI we delete existing feature from database.
            geofencing.clearFeatures { result in
                geofencing.addObserver(observer: self) { result in
                    print("Add observer: \(result)")
                }
                completion()
            }
        }
    }

    func add(feature: Turf.Feature) {
        let geofencing = GeofencingFactory.getOrCreate()
        geofencing.addFeature(feature: feature) { result in
            print("Add feature result: \(result)")
        }
    }
}

extension Geofencing: GeofencingObserver {
    func onEntry(event: GeofencingEvent) {
        DispatchQueue.main.async {
            print(event.feature)
        }
    }

    func onDwell(event: GeofencingEvent) {
        DispatchQueue.main.async {
            print(event.feature)
        }
    }

    func onExit(event: GeofencingEvent) {
        DispatchQueue.main.async {
            print(event.feature)
        }
    }

    func onError(error: GeofencingError) {
        DispatchQueue.main.async {
            print(error)
        }
    }

    func onUserConsentChanged(isConsentGiven: Bool) {
        DispatchQueue.main.async {
            print("Is consent given: \(isConsentGiven)")
        }
    }
}

// Load GeoJSON file from local bundle and decode into a `FeatureCollection`.
private func decodeGeoJSON(from fileName: String) -> FeatureCollection {
    guard let path = Bundle.main.path(forResource: fileName, ofType: "geojson") else {
        preconditionFailure("File '\(fileName)' not found.")
    }

    let filePath = URL(fileURLWithPath: path)
    var featureCollection: FeatureCollection
    do {
        let data = try Data(contentsOf: filePath)
        featureCollection = try JSONDecoder().decode(FeatureCollection.self, from: data)
    } catch {
        print("Error parsing data: \(error)")
        featureCollection = FeatureCollection(features: [])
    }

    return featureCollection
}

#Preview {
    ContentView()
}
```

## Add sample location data

Next, we will add sample location data with a GPX file to imitate a user traveling around the [Yellowstone Grand Loop Road](https://en.wikipedia.org/wiki/Grand_Loop_Road).

### Download the GPX file and add it to your project

Download the Yellowstone Grand Loop Road GPX file and add it to your project as `yellowstone_grand_loop_road.gpx`. To add the file to your project, select File > Add Files to "YourProjectName"... and select the GPX file. Make sure the Action is "Copy files to destination" and you select the correct target.

[Download GPX](https://docs.mapbox.com/help/help/data/yellowstone_grand_loop_road.gpx)

### Use the GPX file to simulate a user traveling around Yellowstone National Park

With your application running in simulator, go back to Xcode. In the Debug menu select `Simulate Location` > `yellowstone_grand_loop_road.gpx`. In simulator you should see the user's location move along the Grand Loop Road. As the simulated location enters and exits the geofenced areas, you should see the geofencing events printed to the console.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-geofencing-polygons-a4581b9b8dae465a00a306bd8bd88cac.mp4).

## Receive geofence events in your app

Now that you have added sample location data to your app and are receiving geofence events in the console, you can use these events to trigger notifications and style changes in your app. First, let's create a struct to store the geofence event data. This struct will store the type of event, the feature that triggered the event, the name of the geofence, and the timestamp of the event. It will also provide a formatted description of the event type.

```swift
private struct GeofenceEvent {
    enum GeofenceEventType {
        case entry
        case dwell
        case exit

        var description: String {
            switch self {
            case .entry:
                return "entry"
            case .dwell:
                return "dwell"
            case .exit:
                return "exit"
            }
        }

        var formatted: String {
            switch self {
            case .entry:
                return "Entered"
            case .dwell:
                return "Dwelled in"
            case .exit:
                return "Exited"
            }
        }
    }

    var type: GeofenceEventType
    var feature: Turf.Feature
    var geofenceName: String
    var timestamp: Date

    init(type: GeofenceEventType, event: GeofencingEvent) {
        self.type = type
        self.feature = event.feature
        self.geofenceName = {
            switch event.feature.properties?["name"] {
            case let .string(name):
                name
            default:
                "unknown geofence"
            }
        }()
        self.timestamp = event.timestamp
    }
}
```

In your `Geofencing` class, add a `@Published` property to store the last geofence event. Update the `onEntry`, `onDwell`, and `onExit` methods to store the event in the `lastEvent` property.

```swift
private final class Geofencing: ObservableObject {
 @Published var lastEvent: GeofenceEvent?
...
}
...
extension Geofencing: GeofencingObserver {
    // Push a new GeofenceEvent when the user enters a geofence region
    func onEntry(event: GeofencingEvent) {
        DispatchQueue.main.async { self.lastEvent = GeofenceEvent(type: .entry, event: event) }
    }

    // Push a new GeofenceEvent when the user dwells in a geofence region
    func onDwell(event: GeofencingEvent) {
        DispatchQueue.main.async { self.lastEvent = GeofenceEvent(type: .dwell, event: event) }
    }

    // Push a new GeofenceEvent when the user exits a geofence region
    func onExit(event: GeofencingEvent) {
        DispatchQueue.main.async { self.lastEvent = GeofenceEvent(type: .exit, event: event) }
    }
...
}
```

### Display geofence events in your app

Now that you are storing the last geofence event in your `Geofencing` class, you can display it in your app. Add a new `EventView` struct to your project to display the geofence event.

```swift
private struct EventView: View {
    let event: GeofenceEvent?
    var body: some View {
        if let event {
            VStack {
                Text(event.type.formatted) +
                Text(" \(event.geofenceName) at ") +
                Text(event.timestamp, style: .time)
            }
            .font(.subheadline)
            .padding(10)
            .background(.white)
            .clipped()
            .shadow(radius: 1.4, y: 0.7)
            .cornerRadius(10)
            .offset(y: -50)
        }
    }
}
```

On your `Map`, add an overlay modifier to display the `EventView` at the bottom of the screen.

```swift
...
.ignoresSafeArea()
.overlay(alignment: .bottom) {
    EventView(event: geofencing.lastEvent)
}
```

With this addition, you should see the last geofence event displayed at the bottom of the screen.

![Screenshot of an iOS app showing a map of Yellowstone National Park with the last geofence event displayed at the bottom of the screen.](https://docs.mapbox.com/help/assets/ideal-img/tutorials--ios-geofencing-4.431eb2c.480.png)

### Style the geofenced area based on the event type

Update the `FillLayer` to change the color of the geofenced area based on the type of event using two expressions: [id](https://docs.mapbox.com/style-spec/reference/expressions/#id) and [match](https://docs.mapbox.com/style-spec/reference/expressions/#match). The `id` expression will match the feature's identifier with the last event's feature identifier so only that feature is updated. The `match` expression will change the color based on the type of event. When the event is an entry, the color will be green. When the event is an exit, the color will be red. When the event is a dwell, the color will be blue. When there is no event, the color will be gray. Additionally, update the `fillOpacity` to 0.7 to make the color slightly transparent.

```swift
FillLayer(id: "yellowstone-regions", source: "yellowstone-locations")
    .fillColor(Exp(.match) {
        // Use an expression to update the color of the feature's polygon when an event is received for that feature
        Exp(.id)
        geofencing.lastEvent?.feature.identifier?.string ?? "default"
        Exp(.match) {
            // Change to color based on the type of event
            geofencing.lastEvent?.type.description ?? "none"
            "entry"
            "rgba(7, 144, 30, 0.8)" // green
            "exit"
            "rgba(173, 17, 5, 0.8)" // red
            "dwell"
            "rgba(17, 97, 195, 0.8)" // blue
            "rgba(119, 119, 119, 1)" // gray
        }
        "rgba(119, 119, 119, 1)" // gray
    })
    .fillOpacity(0.7)
```

## Run your app

Your application is now complete! Run your app on a simulator, restarting the Grand Loop Road GPX file to simulate a user traveling around Yellowstone National Park. As the user moves along the road, you should see the geofenced areas change color based on the type of event. The last geofence event should be displayed at the bottom of the screen.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-geofencing-complete-f9c266423103c8742294fab697214597.mp4).

Your final code should look like this:

```swift
import SwiftUI
@_spi(Experimental) import MapboxMaps

struct ContentView: View {
    @ObservedObject private var geofencing = Geofencing()
    let featureCollection = decodeGeoJSON(from: "yellowstone")

    var body: some View {
        let center = CLLocationCoordinate2D(latitude: 44.5979, longitude: -110.6123)
        Map(initialViewport: .camera(center: center, zoom: 9, bearing: 0, pitch: 0)) {
            Puck2D()

            GeoJSONSource(id: "yellowstone-locations")
                .data(.featureCollection(featureCollection))

            FillLayer(id: "yellowstone-regions", source: "yellowstone-locations")
                .fillColor(Exp(.match) {
                    // Use an expression to update the color of the feature's polygon when an event is received for that feature
                    Exp(.id)
                    geofencing.lastEvent?.feature.identifier?.string ?? "default"
                    Exp(.match) {
                        // Change to color based on the type of event
                        geofencing.lastEvent?.type.description ?? "none"
                        "entry"
                        "rgba(7, 144, 30, 0.8)" // green
                        "exit"
                        "rgba(173, 17, 5, 0.8)" // red
                        "dwell"
                        "rgba(17, 97, 195, 0.8)" // blue
                        "rgba(119, 119, 119, 1)" // gray
                    }
                    "rgba(119, 119, 119, 1)" // gray
                })
                .fillOpacity(0.7)
        }
        .onMapLoaded { _ in
            geofencing.start {
                for feature in featureCollection.features {
                    // To receive dwell events we need to set a time.
                    // After a user has spent that amount of time in the geofence
                    // the Geofencing service will send a dwell event
                    var geofencingFeature = feature
                    geofencingFeature.properties?[GeofencingPropertiesKeys.dwellTimeKey] = 1 // minutes
                    geofencing.add(feature: geofencingFeature)
                }
            }
        }
        .ignoresSafeArea()
        .overlay(alignment: .bottom) {
            EventView(event: geofencing.lastEvent)
        }
    }
}

private final class Geofencing: ObservableObject {
    @Published var lastEvent: GeofenceEvent?

    func start(_ completion: @escaping () -> Void) {
        let geofencing = GeofencingFactory.getOrCreate()
        geofencing.configure(options: GeofencingOptions()) { [weak self] result in
            guard let self else { return }
            /// Geofences are stored in database on disk.
            /// To make this example isolated and synchronized with the UI we delete existing feature from database.
            geofencing.clearFeatures { result in
                geofencing.addObserver(observer: self) { result in
                    print("Add observer: \(result)")
                }
                completion()
            }
        }
    }

    func add(feature: Turf.Feature) {
        let geofencing = GeofencingFactory.getOrCreate()
        geofencing.addFeature(feature: feature) { result in
            print("Add feature result: \(result)")
        }
    }
}

extension Geofencing: GeofencingObserver {
    // Push a new GeofenceEvent when the user enters a geofence region
    func onEntry(event: GeofencingEvent) {
        DispatchQueue.main.async { self.lastEvent = GeofenceEvent(type: .entry, event: event) }
    }

    // Push a new GeofenceEvent when the user dwells in a geofence region
    func onDwell(event: GeofencingEvent) {
        DispatchQueue.main.async { self.lastEvent = GeofenceEvent(type: .dwell, event: event) }
    }

    // Push a new GeofenceEvent when the user exits a geofence region
    func onExit(event: GeofencingEvent) {
        DispatchQueue.main.async { self.lastEvent = GeofenceEvent(type: .exit, event: event) }
    }

    func onError(error: GeofencingError) {
        DispatchQueue.main.async {
            print(error)
        }
    }

    func onUserConsentChanged(isConsentGiven: Bool) {
        DispatchQueue.main.async {
            print("Is consent given: \(isConsentGiven)")
        }
    }
}

private struct GeofenceEvent {
    enum GeofenceEventType {
        case entry
        case dwell
        case exit

        var description: String {
            switch self {
            case .entry:
                return "entry"
            case .dwell:
                return "dwell"
            case .exit:
                return "exit"
            }
        }

        var formatted: String {
            switch self {
            case .entry:
                return "Entered"
            case .dwell:
                return "Dwelled in"
            case .exit:
                return "Exited"
            }
        }
    }

    var type: GeofenceEventType
    var feature: Turf.Feature
    var geofenceName: String
    var timestamp: Date

    init(type: GeofenceEventType, event: GeofencingEvent) {
        self.type = type
        self.feature = event.feature
        self.geofenceName = {
            switch event.feature.properties?["name"] {
            case let .string(name):
                name
            default:
                "unknown geofence"
            }
        }()
        self.timestamp = event.timestamp
    }
}

private struct EventView: View {
    let event: GeofenceEvent?
    var body: some View {
        if let event {
            VStack {
                Text(event.type.formatted) +
                Text(" \(event.geofenceName) at ") +
                Text(event.timestamp, style: .time)
            }
            .font(.subheadline)
            .padding(10)
            .background(.white)
            .clipped()
            .shadow(radius: 1.4, y: 0.7)
            .cornerRadius(10)
            .offset(y: -50)
        }
    }
}

// Load GeoJSON file from local bundle and decode into a `FeatureCollection`.
private func decodeGeoJSON(from fileName: String) -> FeatureCollection {
    guard let path = Bundle.main.path(forResource: fileName, ofType: "geojson") else {
        preconditionFailure("File '\(fileName)' not found.")
    }

    let filePath = URL(fileURLWithPath: path)
    var featureCollection: FeatureCollection
    do {
        let data = try Data(contentsOf: filePath)
        featureCollection = try JSONDecoder().decode(FeatureCollection.self, from: data)
    } catch {
        print("Error parsing data: \(error)")
        featureCollection = FeatureCollection(features: [])
    }

    return featureCollection
}

#Preview {
    ContentView()
}
```

## Next steps

Congratulations! You have successfully implemented geofencing in an iOS app using the **Mapbox Maps SDK for iOS**. You can now trigger notifications and style changes based on geofence events.

### What we covered

-   Adding GeoJSON data to your project, visualizing it on the map
-   Initializing the geofencing service and adding geofences to watch
-   Imitating a user's location with sample location data and watching geofence events
-   Displaying geofence events in your app and styling the geofenced area based on the event type

### Learn more

If you'd like an additional challenge try implementing the following features:

-   Change the color of the last geofence event displayed at the bottom of the screen based on the type of event.
-   Implement the ability to remove geofences using the `.remove(featureId:)` method in the `Geofencing` class.
-   Add another geofence region to the map and watch events for that region as well.

For more information on the Mapbox Maps SDK for iOS, read the [Mapbox Maps SDK for iOS documentation](https://docs.mapbox.com/ios/maps/). Additionally, you can explore our other geofencing examples.

> **Related content (example): [Create geofence zone around user's location](https://docs.mapbox.com/ios/maps/examples/swiftui-geofencing/)**
> 
> This example shows the usage of the Mapbox Maps SDK for iOS [Geofencing API](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingservice/) to create a geofence zone for a radius around the user's location, updating its color based on [events](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingevent/) such as entering, dwelling, or leaving the geofence zone.

> **Related content (example): [Create geofence zone on tapped area using Isochrone API](https://docs.mapbox.com/ios/maps/examples/swiftui-extended-geofencing/)**
> 
> This example demonstrates the usage of the [Geofencing API](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingservice/) using the Mapbox Maps SDK for iOS with a custom polygon. The polygon is sourced from the [Mapbox Isochrone API](https://docs.mapbox.com/api/navigation/isochrone/) to get a travel-time polygon for the user's current location. The Geofencing API handles [events](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/geofencingevent/), displaying notifications for entry, dwell, and exit.