[SwiftUI] Implement Mapbox Map View with SwiftUI – Create Map View

Since we completed the required environment set up in the previous article, I would like to implement the Mapbox map view with SwiftUI. If you didn’t complete the environment setup yet, please refer to the previous article to proceed with the contents of this article. You may think that the most simple quick start has been provided officially and it will help to create a view just by copy and paste. But unfortunately, it’s not true. Actually, the official site provides the simple sample code to create a map view, however, that code is not compatible with SwiftUI as it is. Therefore,  first of all, I will introduce the UIViewRepresentable and UIViewControllerRepresentable that enables you to implement this problem. Then also introduce how to implement a simple Mapbox Map View into your SwiftUI project.

Xcode: 13.1
iOS: 15.0
Swift: 5
Maps SDK for iOS: v10.0.1

The problem with the Maps SDK for iOS sample code

Unfortunately, there is no official sample code with SwiftUI so far. There is only the sample code with UIViewController. UIViewController is not compatible with SwiftUI directly, however, it would be implemented with SwiftUI by using UIViewRepresentable

About UIViewRepresentable

According to the Apple Developer site, UIViewRepresentable is introduced as below.

A wrapper for a UIKit view that you use to integrate that view into your SwiftUI view hierarchy.

https://developer.apple.com/documentation/swiftui/uiviewrepresentable

That means the UIViewRepresentable is worked as some kind of shock absorber between “legacy” UIKit and “New” SwiftUI. UIViewController is included in the UIKit as below, we can implement UIViewController with SwiftUI by utilizing this UIViewRepresentable.

The essential sample of UIViewRepresentable

We understood that we can implement UIViewController in SwiftUI by utilizing the UIViewRepresentable but how? Unfortunately, the Apple Developer site only provides the sample of how to “call” UIViewRepresentable not “implement” UIViewRepresentable. So we need to read the Apple Developer site carefully and have to find out the solutions.

You can find the three actions below in the Topics area in the Apple Developer site of UIViewRepresentable.

  1. Creating and Updating the View
  2. Cleaning Up the View
  3. Providing a Custom Coordinator Object

Only we should know at this time is “1. Creating and Updating the View” to create a simple view in SwiftUI. So let’s deep into this topic.

About UIViewRepresentable: Creating and Updating the View

Some elements have been introduced in ”Creating and Updating the View”, and the following is defined as Required. This implies that we can utilize UIViewRepresentable just by creating a struct with these three elements.

funcmakeUIViewCreate the View
funcupdateUIViewUpdate the View
associatedtypeUIViewType:UIViewThe type of the View to wrap

Implement the map view of Mapbox with SwiftUI

Now we see the required knowledge to implement the map view of Mapbox with SwiftUI. So let’s start to implement using actual sample code. I will create a map view and call it from ContentView in this article.

Import SwiftUI and MapboxMaps

Create a new file named MapView.swift that will implement the Mapbox’s map view. First of all, import the required libraries to implement this MapView.swift. In the Mapbox official document, it is introduced the sample that imports, not SwiftUI but UIKit. But, we need to import SwiftUI instead of UIKit in this case.

import SwiftUI
import MapboxMaps

Implement ViewController

Next, implement the ViewController that is the main part of the map view. This part can reuse the sample code introduced in the official document of Maps SDK for iOS directly. 

class ViewController: UIViewController {
   internal var mapView: MapView!
   override public func viewDidLoad() {
       super.viewDidLoad()
       let myResourceOptions = ResourceOptions(accessToken: "your_public_access_token")
       let myMapInitOptions = MapInitOptions(resourceOptions: myResourceOptions)
       mapView = MapView(frame: view.bounds, mapInitOptions: myMapInitOptions)
       mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
       self.view.addSubview(mapView)
   }
}

Note that you have to fill “your_public_access_token” in the accessToken parameter with your Public Token that can be found in your Mapbox account page. You can use the Default public token here, but I will use the Custom Token created in the previous article which includes two public scopes.

Enable to call ViewController with SwiftUI by using UIViewControllerRepresentable

As mentioned above, we cannot call this UIViewController as it is. So we need to wrap this UIViewController within UIViewControllerRepresentable. I will implement it like below to make it as simple as possible.

funcmakeUIViewJust return the ViewController which is the type of UIViewController
funcupdateUIViewNothing to do
associatedtypeUIViewTypeSpecify the ViewController which I created

As a result, the UIViewControllerRepresentable to implement is as below.

struct MapViewWrapper : UIViewControllerRepresentable {
  
   func makeUIViewController(context: Context) -> ViewController {
       return ViewController()
   }
  
   func updateUIViewController(_ uiViewController: ViewController, context: Context) {
      
   }
}

Implement the call part of ContentView

At last, I will implement the call ViewController from ContentView. Actually, it can be implemented as simply call the MapViewWrapper which I created just like a view of SwiftUI.

//  ContentView.swift

import SwiftUI

struct ContentView: View {
    var body: some View {
        MapViewWrapper()
    }
}

Build and Execute

Now since all the implementation has been completed, let’s try to build and execute the code. Once the build and execution have been completed the app simulator shows the map like below. This map is generated as the point of center with both longitude and latitude is zero. By the way, this center point is called Null Island.

Note that once you show the map, you can confirm the account usage as below on your Mapbox account page. “25,000”  is allocated as a free tier, and it seems good enough to use individually. However, it might be dramatically increased if you leak your tokens. So please take care and carefully manage your tokens for your “peace of mind”.


Wrapup

Related Articles

References

https://www.hackingwithswift.com/books/ios-swiftui/wrapping-a-uiviewcontroller-in-a-swiftui-view

Sample code created in this article

ContentView.swift

//  ContentView.swift
import SwiftUI
struct ContentView: View {
    var body: some View {
        MapViewWrapper()
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

MapView.swift

//  MapView.swift
import SwiftUI
import MapboxMaps
struct MapViewWrapper : UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) -> ViewController {
        return ViewController()
    }
    
    func updateUIViewController(_ uiViewController: ViewController, context: Context) {
        
    }
}
class ViewController: UIViewController {
 
    internal var mapView: MapView!
    override public func viewDidLoad() {
        super.viewDidLoad()
        let myResourceOptions = ResourceOptions(accessToken: "pk.eyJ1IjoiaGFydWJlYXJzIiwiYSI6ImNrdzFuNDh6NzAydGoybm1wZGdudm5ybTkifQ.mnv9PQFHVGZz0kDWZ2mcRg")
        let myMapInitOptions = MapInitOptions(resourceOptions: myResourceOptions)
        mapView = MapView(frame: view.bounds, mapInitOptions: myMapInitOptions)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.addSubview(mapView)
    }
}