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

# Build an iOS marker app from a custom style and tileset

This tutorial demonstrates how to build an iOS app using a [custom map style](https://docs.mapbox.com/help/dive-deeper/map-design/#what-is-a-style) with embedded data and custom markers. This app will use a custom style and data, so the tutorial will focus on the implementation of the style into the map and creating interactions with the style.

Using a custom map style is ideal for cross platform applications as the style can be used in different platforms and reduces frontend code.

Your final map will include markers representing 10 dog spas in Boston. When tapped, the markers will which spawn a window that scrolls up from the bottom of the screen and displays information about the selected business:

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-custom-style--full-demo-0f95913ba6823e04087722204c1bb349.mp4).

## Prerequisites

Before you begin, you will need:

-   **A Mapbox account**: Sign up for a free account on [Mapbox](https://account.mapbox.com/auth/signup/).
-   **Familiarity with iOS development**: Beginner experience with Swift, SwiftUI and iOS Development.
-   **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**: Xcode 16.2 or later
-   **A custom style URL.** A custom style for this tutorial, contains a custom font, marker SVG and layers for the dog spa locations. This style can be previewed at this URL:

```
https://api.mapbox.com/styles/v1/examples/cm37hh1nx017n01qk2hngebzt.html?title=view&access_token=YOUR_MAPBOX_ACCESS_TOKEN&zoomwheel=true&fresh=true#11.41/42.3249/-71.0969
```

> **Note (warning): Troubleshooting the preview link**
> 
> When using the URL above to preview the style, make sure to replace `YOUR_MAPBOX_ACCESS_TOKEN` with your actual Mapbox access token. You can find your access token in your [Mapbox account dashboard](https://account.mapbox.com/access-tokens/).

> **Note: Creating your own style**
> 
> If you would like to create this custom style yourself instead of using the style provided, follow the steps in the [Add and style data in Mapbox Studio](https://docs.mapbox.com/help/help/tutorials/add-data-to-mapbox-style/) tutorial.

## Add a custom style to the map

To begin, you will need to create add a map to your app, and in the constructor of the map, you will need to pass the `StyleURL` so the map will render the custom style instead of the default options.

This style contains a custom tileset with geojson data for dog spas in the Boston area, as well as custom markers to represent each location. This data is represented in two layers in the style, presenting two different marker designs for the same locations, based on the zoom level.

Add the code below to your `ContentView.swift` file to create the map:

```swift
import SwiftUI
import MapboxMaps

struct ContentView: View {
    var body: some View {
        // Center coordinates for Boston, MA
        let center = CLLocationCoordinate2D(
            latitude: 42.34622,
            longitude: -71.09290
        )
        // Create a map
        Map(initialViewport: .camera(center: center, zoom: 7, bearing: 0, pitch: 0))
            .mapStyle(MapStyle(uri: StyleURI(rawValue: "mapbox://styles/examples/cm37hh1nx017n01qk2hngebzt")!))
            .ornamentOptions(OrnamentOptions(scaleBar: ScaleBarViewOptions(visibility: .hidden),compass: CompassViewOptions(visibility: .hidden)))
            .ignoresSafeArea()
            
    }
}
```

Run the app in your simulator to load a full screen map centered over Boston, MA. Your custom style will be displaying a group of markers over the Boston area.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-custom-style--add-a-map-41306946a8e7262fe6ac970c24a7ba45.mp4).

In the next step, you will introduce interactivity to your app by adding a tap interaction to the markers and accessing the underlying POI data.

## Add a tap interaction to the markers

Next you will grab data from a marker when it is tapped, using the [`TapInteraction`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/tapinteraction/) function from the Interactions API.

This functionality allows the developer to detect when a feature has been tapped, and give access to any underlying data from that feature. You'll log the data from the marker to the console for now, and in the next step, you'll display the data in a sheet.

Replace the `Map()` view in your `ContentView.swift` file with the highlighted lines:

> **Note: Add interactions to both layers**
> 
> This custom style uses two layers to represent the dog spas, circles when zoomed out, and a marker icon when zoomed in. The code below listens for taps on both layers, calling `TapInteraction` for each layer to detect once a feature has been tapped.

```swift
import SwiftUI
import MapboxMaps

struct ContentView: View {
    var body: some View {
        // Center coordinates for Boston, MA
        let center = CLLocationCoordinate2D(
            latitude: 42.34622,
            longitude: -71.09290
        )
        // highlight-start
        Map(initialViewport: .camera(center: center, zoom: 7, bearing: 0, pitch: 0))
        {
            // Implements the interactions API, grabbing reference when a feature is tapped by the user. One is needed for each layer containing data in the style.
            TapInteraction(.layer("dog-groomers-boston-marker")) { feature, context in
                
             print(feature.properties.description)
                
             return true // stops propagation
            }
            
            // Implements the interactions API, grabbing reference when a feature is tapped by the user. One is needed for each layer containing data in the style.
            TapInteraction(.layer("dog-groomers-3o4sdb")) { feature, context in
                
             print(feature.properties.description)
                
             return true // stops propagation
            }
            
        }
        // highlight-end
        .mapStyle(MapStyle(uri: StyleURI(rawValue: "mapbox://styles/examples/cm37hh1nx017n01qk2hngebzt")!))
        .ornamentOptions(OrnamentOptions(scaleBar: ScaleBarViewOptions(visibility: .hidden),compass: CompassViewOptions(visibility: .hidden)))
        .ignoresSafeArea()
            
    }
}

```

When you run the simulator, tap on a marker and check the console. You will see the properties of the tapped feature, such as the address, city, country, etc, of the selected POI.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-custom-style--tap-interaction-27d8e8c8a12adbdaeb3738a7067d9b1b.mp4).

With tap interactions working, you're ready to take the data from the tapped feature to present data to the user in a new UI element.

## Display marker properties on tap

With the app responding to taps and extracting the feature data from the tapped marker, the next step is to add UI to display information about a marker its tapped.

This code renders a `sheet` that contains text elements, used to describe the different properties of each POI. When a marker or circle is tapped, the related properties data of the selected feature is grabbed from the style and passed into a set of variables, which will store the data and display it in the `sheet`. Tapping on the marker will also manage the state of the `sheet`, setting `presentSheet` to true, when a marker is tapped, and the next tap will dismiss the UI element, setting `presentSheet` to false and closing the `sheet`. Lastly the map and sheet are placed in a `ZStack` to create a clean interface and properly space out the UI elements.

Add the highlighted code below to your code, and replace the log statements with the new `grabPOI()` function:

```swift
import SwiftUI
import MapboxMaps

struct ContentView: View {

    // highlight-start
    //Stores state of sheet, noting if the sheet is opened or closed.
    @State var presentSheet = false
    @State var locationName = "Test Name"
    @State var locationAddress = "Test Address"
    @State var locationPhoneNumber = "Test Phone Number"
    @State var locationRating = "Test Rating"
    // highlight-end
    
    var body: some View {
        let center = CLLocationCoordinate2D(
            latitude: 42.34622,
            longitude: -71.09290
        )
    // highlight-start
    ZStack(alignment: .bottom) {
    // highlight-end
        Map(initialViewport: .camera(center: center, zoom: 7, bearing: 0, pitch: 0))
        {
            TapInteraction(.layer("dog-groomers-boston-marker")) { feature, context in
            // highlight-start
                grabPOIData(properties: feature.properties)
            // highlight-end
                return true
            }
            
            TapInteraction(.layer("dog-groomers-3o4sdb")) { feature, context in
            // highlight-start
                grabPOIData(properties: feature.properties)
            // highlight-end
                return true
            }
        }
        .mapStyle(MapStyle(uri: StyleURI(rawValue: "mapbox://styles/examples/cm37hh1nx017n01qk2hngebzt")!))
        .ornamentOptions(OrnamentOptions(scaleBar: ScaleBarViewOptions(visibility: .hidden),compass: CompassViewOptions(visibility: .hidden)))
        .ignoresSafeArea()

    // highlight-start  
    }
        // Checks if a marker has been tapped, if so will open the sheet
        .ignoresSafeArea(edges: [.bottom, .leading]).sheet(isPresented: $presentSheet) {
            VStack(alignment: .leading) {
                Text(locationName)
                    .font(.system(size: 30))
                    .presentationDetents([.height(200)])
                Text(locationRating)
                    .safeAreaInset(edge: .leading) { Image(systemName: "star") }
                    .presentationDetents([.height(200)])
                Text(locationAddress)
                    .safeAreaInset(edge: .leading) { Image(systemName: "house") }
                    .presentationDetents([.height(200)])
                Text(locationPhoneNumber)
                    .safeAreaInset(edge: .leading) { Image(systemName: "phone") }
                    .presentationDetents([.height(200)])
            }
        }
        // highlight-end
}
// highlight-start
    // Deconstructs a JSON object, allowing access to each of the property values of the passed in feature.     
    func grabPOIData(properties: JSONObject)
    {
        locationName = (properties["storeName"]!!).rawValue as! String;
        
        let streetAddress = (properties["address"]!!).rawValue as! String;
        let city = (properties["city"]!!).rawValue as! String;
        let postalCode = (properties["postalCode"]!!).rawValue as! String;
        
        locationAddress = streetAddress + ", " + city + ", " + postalCode;
        locationPhoneNumber = (properties["phoneFormatted"]!!).rawValue as! String;
        let rating = (properties["rating"]!!).rawValue as! Double;
        locationRating = "\(rating)"
        
        presentSheet = true;
    }   
// highlight-end
}
```

Now when you run the app in a simulator, you should see the sheet slide up from the bottom of the screen when you tap on a marker. The feature properties will be displayed in the sheet and tapping again will close it.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-custom-style--render-sheet-1cf78f7c216df3318489ec7ff9d8c0fb.mp4).

## (Optional) Add title card and call to action

In this step you will add a title card to name the app and an additional box to instruct the user how to use the app.

Add the following code to add a `VStack` to display the title card floating above the map:

```swift
import SwiftUI
import MapboxMaps

struct ContentView: View {
    
    @State var presentSheet = false
    @State var locationName = "Test Name"
    @State var locationAddress = "Test Address"
    @State var locationPhoneNumber = "Test Phone Number"
    @State var locationRating = "Test Rating"
    
    var body: some View {
        // Center coordinates for Boston, MA
        let center = CLLocationCoordinate2D(
            latitude: 42.34622,
            longitude: -71.09290
        )
    ZStack(alignment: .bottom) {
        Map(initialViewport: .camera(center: center, zoom: 7, bearing: 0, pitch: 0))
        {
            TapInteraction(.layer("dog-groomers-boston-marker")) { feature, context in
                grabPOIData(properties: feature.properties)
                return true // stops propagation
            }
            
            TapInteraction(.layer("dog-groomers-3o4sdb")) { feature, context in
                grabPOIData(properties: feature.properties)
                return true // stops propagation
            }
        }
        .mapStyle(MapStyle(uri: StyleURI(rawValue: "mapbox://styles/examples/cm37hh1nx017n01qk2hngebzt")!))
        .ornamentOptions(OrnamentOptions(scaleBar: ScaleBarViewOptions(visibility: .hidden),compass: CompassViewOptions(visibility: .hidden)))
        .ignoresSafeArea()
        
        // highlight-start
        // Adds UI element with call to action and app title text
        VStack(alignment: .trailing) {
                HStack {
                    Image(systemName: "pawprint.fill")
                        .foregroundColor(.blue)
                        .padding([.leading, .top, .bottom], 6)
                    Text("Pet Spa Finder")
                        .font(.headline)
                        .foregroundColor(.black)
                        .cornerRadius(3)
                        .padding([.top, .trailing, .bottom], 6)
                }
                .padding(12)
                .background(
                    RoundedRectangle(cornerRadius: 3)
                        .fill(Color.white)
                )
                
                Text("Click a marker for more information.")
                    .font(.caption)
                    .foregroundColor(.black)
                    .padding(8)
                    .frame(width: UIScreen.main.bounds.width * 0.45)
                    .background(
                        RoundedRectangle(cornerRadius: 3)
                            .fill(Color.white)
                    )
                    .cornerRadius(2)
        }.offset(x:100, y: -650)
        // highlight-end
        
    }
        .ignoresSafeArea(edges: [.bottom, .leading]).sheet(isPresented: $presentSheet) {
            VStack(alignment: .leading) {
                Text(locationName)
                    .font(.system(size: 30))
                    .presentationDetents([.height(200)])
                Text(locationRating)
                    .safeAreaInset(edge: .leading) { Image(systemName: "star") }
                    .presentationDetents([.height(200)])
                Text(locationAddress)
                    .safeAreaInset(edge: .leading) { Image(systemName: "house") }
                    .presentationDetents([.height(200)])
                Text(locationPhoneNumber)
                    .safeAreaInset(edge: .leading) { Image(systemName: "phone") }
                    .presentationDetents([.height(200)])
            }
        }
}
    
    func grabPOIData(properties: JSONObject)
    {
        locationName = (properties["storeName"]!!).rawValue as! String;
        
        let streetAddress = (properties["address"]!!).rawValue as! String;
        let city = (properties["city"]!!).rawValue as! String;
        let postalCode = (properties["postalCode"]!!).rawValue as! String;
        
        locationAddress = streetAddress + ", " + city + ", " + postalCode;
        locationPhoneNumber = (properties["phoneFormatted"]!!).rawValue as! String;
        let rating = (properties["rating"]!!).rawValue as! Double;
        locationRating = "\(rating)"
        
        presentSheet = true;
    }
    
    
}

```

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-custom-style--full-demo-0f95913ba6823e04087722204c1bb349.mp4).

## Final Product

**Congratulations!** You've built an iOS application with the **Mapbox Maps SDK for iOS**, that imports a custom Mapbox style, access the data from the style and shows feature information when the user interacts with the map. Your app should look like the video below:

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-custom-style--full-demo-0f95913ba6823e04087722204c1bb349.mp4).

## Next Steps

### What we covered

-   Adding a `Map` to display a custom style centered over Boston, MA.
-   Adding a `TapInteraction` to manage clicks on the map layer with and set state in your app with the clicked feature.
-   Creating a sheet to display feature properties when a marker is tapped.
-   Adding title card and call to action to describe the app and how to use it.

Now that you've finished building your app, here are some potential features you could add to further build out your project:

-   Improve the UI of the information panel.
-   Create a custom style with your own data by following the [Add and style data in Mapbox Studio](https://docs.mapbox.com/help/help/tutorials/add-data-to-mapbox-style/) tutorial.
-   Re-center the map on a marker after it is tapped.
-   Add a reset map button to return users to the original view of the map.
-   Add more fields to the dataset, such as the company website.

### Learn More

-   Browse the source code for this tutorial on [GitHub](https://github.com/mapbox/tutorials/tree/main/ios-marker-app/)
-   Setup and run the [**Mapbox Maps SDK for iOS** examples app](https://docs.mapbox.com/help/tutorials/maps-sdk-ios-examples-app/) to learn more about what you can do with Mapbox maps on iOS.
-   Learn more about the [**Mapbox Maps SDK for iOS**](https://docs.mapbox.com/ios/maps/overview/) in the documentation.