Skip to main content

Get Started - iOS

Contents

Introduction

Welcome! This guide provides a foundational example demonstrating how to integrate Trimble’s Mobile Maps SDK into a Flutter application, specifically targeting the iOS platform. Our goal is to help you get a basic map view up and running using Flutter’s PlatformView mechanism.

Who is this guide for?

This guide is intended for developers who have some basic experience with Flutter development. We assume you are familiar with:

  • Creating and running a new Flutter project.
  • The general structure of a Flutter application.
  • Basic Dart and Flutter concepts.

If you are new to Flutter or need a refresher, we highly recommend starting with the official Flutter ‘Get Started’ guide before proceeding.

What will you learn?

By following this guide, you will learn how to:

  1. Set up the necessary configurations in your iOS Flutter project to use the Trimble Maps SDK.
  2. Implement a basic Flutter PlatformView to display a Trimble Map within your Flutter UI.
  3. Establish the initial connection between your Flutter code and the native iOS map view.

Scope and Limitations

Please note that this guide focuses solely on the initial setup and integration for displaying a map on iOS using PlatformView. It serves as a starting point and does not cover:

  • The full range of features and capabilities offered by the Trimble Maps SDK (like routing, custom styling, location services integration, etc.).
  • Advanced PlatformView communication techniques.

For comprehensive information on the SDK’s features and functionalities, this guide should be used in conjunction with the official Trimble Maps SDK for iOS documentation.

Let’s get started!

Set Up Dependencies

Before writing any Dart code to display the map, you need to configure the native iOS part of your Flutter application to include the Trimble Maps SDK libraries. This involves using Carthage to manage dependencies and adding the frameworks to your Xcode project.

Step 1: Create a Cartfile

Carthage uses a Cartfile to specify dependencies. You need to create this file in your iOS project directory.

  1. Navigate to your iOS project directory: ios/

  2. Create a new file named Cartfile (no extension) in the ios/ directory.

  3. Add the Trimble Maps SDK dependencies to the Cartfile:

# Latest release
binary "https://trimblemaps.jfrog.io/artifactory/carthage/TrimbleMaps.json" == 2.0.0
binary "https://trimblemaps.jfrog.io/artifactory/carthage/TrimbleMapsMobileEvents.json" == 1.0.0
binary "https://trimblemaps.jfrog.io/artifactory/carthage/TrimbleMapsAccounts.json" == 2.0.0

Note: The version numbers (2.0.0 for TrimbleMaps and 1.0.0 for TrimbleMapsMobileEvents and 2.0.0 for TrimbleMapsAccounts) provided here are examples. While they should work for this guide, you might want to check the developer portal or the Artifactory repository for the latest released versions for your production applications.

Step 2: Install Dependencies with Carthage

  1. Open Terminal and navigate to your iOS project directory: ios/

  2. Run the Carthage bootstrap command to download and build the frameworks:

carthage bootstrap --platform iOS --use-xcframeworks

This command will:

  • Download the Trimble Maps SDK XCFrameworks from the Artifactory repository
  • Place them in the Carthage/Build/ directory

Note: Make sure you have Carthage installed. If not, install it using Homebrew: brew install carthage

Step 3: Add Frameworks to Xcode Project

  1. Open your project in Xcode: ios/Runner.xcworkspace (or ios/Runner.xcodeproj)

  2. Select your project in the Project Navigator (the top-level item).

  3. Select the Runner target.

  4. Go to the General tab.

  5. Scroll down to the Frameworks, Libraries, and Embedded Content section.

  6. Click the + button to add frameworks.

  7. Click Add Other… and then Add Files…

  8. Navigate to Carthage/Build/ and select:

    • TrimbleMaps.xcframework
    • TrimbleMapsMobileEvents.xcframework
    • TrimbleMapsAccounts.xcframework
  9. For each framework, ensure Embed & Sign is selected in the dropdown next to it.

Configure Permissions

If you plan to display the user’s location on the map or use location services, you need to add location permissions to your app’s Info.plist file.

Step 1: Add Location Usage Description

  1. Open ios/Runner/Info.plist in Xcode (or as a text file).

  2. Add the following keys and values:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Your precise location is used to show your location on the map.</string>
  1. If you need background location updates, also add:
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>
  1. For full-accuracy location access (iOS 14+), add:
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
  <key>map</key>
  <string>Please enable precise location to show your location on the map.</string>
</dict>

Note: The permission strings should be customized to explain how your app uses location data. These strings are shown to users when permission is requested.

Implement the Native iOS View (TrimbleMapView.swift)

Now that the dependencies are set up, we need to create the native iOS code that will manage and display the Trimble Map. We’ll use Flutter’s PlatformView interface to embed this native view within our Flutter widget tree.

This involves creating a Swift class that:

  1. Initializes the Trimble Maps SDK and the native TMGLMapView.
  2. Manages the lifecycle of the TMGLMapView.
  3. Sets up a MethodChannel to receive commands from Flutter.
  4. Executes map actions (like zooming or changing style) based on those commands.
  5. Returns the actual TMGLMapView instance to be displayed by Flutter.

Step 1: Create the TrimbleMapView.swift File

First, create a new Swift file named TrimbleMapView.swift within your iOS project structure. A common location is:

ios/Runner/TrimbleMapView.swift

Step 2: Add the TrimbleMapView Class Code

Paste the following Swift code into the TrimbleMapView.swift file you just created. (We’ve added extra logging in here so you can follow the workflow in your debugger.):

import Foundation
import UIKit
import Flutter
import TrimbleMaps
import TrimbleMapsAccounts

/**
 * A PlatformView that wraps the Trimble Map SDK's TMGLMapView and
 * exposes zoom, style, and center controls via a MethodChannel.
 * It also handles TMGLMapView lifecycle events.
 */
class TrimbleMapView: NSObject, FlutterPlatformView, AccountManagerDelegate {
    private var frame: CGRect
    private let viewId: Int64                    // Unique identifier for this map view instance
    private let apiKey: String                   // API key for Trimble Maps services
    private let region: Region                   // Region for Trimble Maps services
    private var containerView: UIView            // Container view returned to Flutter
    private var mapView: TMGLMapView?            // The core native Trimble MapView instance
    private var channel: FlutterMethodChannel?   // MethodChannel for communication between Flutter and this native view
    private var isMapReady: Bool = false         // Flag to track if map is ready

    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?
    ) {
        self.viewId = viewId
        self.frame = frame

        // Create container view that will hold the map
        self.containerView = UIView(frame: frame)
        self.containerView.backgroundColor = .systemBackground

        // Extract the API key and region from arguments
        if let argsDict = args as? [String: Any],
           let apiKey = argsDict["apiKey"] as? String {
            self.apiKey = apiKey
        } else {
            self.apiKey = ""
            print("TrimbleMapView: Warning - No API key provided")
        }

        // Parse region from arguments, default to worldwide
        if let argsDict = args as? [String: Any],
           let regionString = argsDict["region"] as? String {
            switch regionString.lowercased() {
            case "northamerica":
                self.region = .northAmerica
            case "europe":
                self.region = .europe
            default:
                self.region = .worldwide
            }
        } else {
            self.region = .worldwide
        }

        super.init()

        print("TrimbleMapView: Initializing for viewId=\(viewId)")

        // Initialize the MethodChannel
        // The channel name includes the viewId to ensure uniqueness if multiple maps are shown.
        if let messenger = messenger {
            self.channel = FlutterMethodChannel(
                name: "trimble_map_view_\(viewId)",
                binaryMessenger: messenger
            )

            // Set up Method Call Handler
            // This listener waits for method calls invoked from the Flutter side using the channel.
            self.channel?.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
                self?.handleMethodCall(call: call, result: result)
            }
        }

        // Check if account is already loaded and licensed
        if AccountManager.default.state == .loaded &&
           AccountManager.default.isLicensed(licensedFeature: .mapsSdk) {
            print("TrimbleMapView: Account already loaded, initializing map immediately")
            initializeMapView()
        } else {
            // Initialize Trimble Maps Account (Authentication)
            // Only set account if not already loaded
            let account = Account(apiKey: self.apiKey, region: self.region)
            AccountManager.default.account = account
            AccountManager.default.delegate = self
        }
    }

    // MARK: - Map Initialization

    /// Creates and adds the TMGLMapView to the container view
    private func initializeMapView() {
        guard mapView == nil else { return } // Prevent duplicate initialization

        // Create the TMGLMapView UI component
        let map = TMGLMapView(frame: containerView.bounds)
        map.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        map.styleURL = TMGLStyle.mobileDayStyleURL

        containerView.addSubview(map)
        self.mapView = map
        self.isMapReady = true

        // Notify Flutter that the map is ready to be interacted with
        self.channel?.invokeMethod("mapReady", arguments: nil)
        print("TrimbleMapView: Map initialized and 'mapReady' notification sent to Flutter")
    }

    // MARK: - AccountManagerDelegate

    func stateChanged(newStatus: AccountManagerState) {
        if newStatus == .loaded {
            if AccountManager.default.isLicensed(licensedFeature: .mapsSdk) {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    print("TrimbleMapView: Account loaded, initializing map")
                    self.initializeMapView()
                }
            } else {
                print("TrimbleMapView: Account is not licensed for Maps SDK")
            }
        }
    }

    // MARK: - Method Call Handling

    private func handleMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
        print("TrimbleMapView: Received method call from Flutter: \(call.method)")

        guard let mapView = self.mapView else {
            result(FlutterError(code: "MAP_NOT_READY", message: "Map view is not initialized", details: nil))
            return
        }

        switch call.method {
        case "zoomIn":
            // Get current zoom level and increase it
            let currentZoom = mapView.zoomLevel
            mapView.setZoomLevel(currentZoom + 1, animated: true)
            result(nil)
            print("TrimbleMapView: zoomIn executed")

        case "zoomOut":
            // Get current zoom level and decrease it
            let currentZoom = mapView.zoomLevel
            mapView.setZoomLevel(currentZoom - 1, animated: true)
            result(nil)
            print("TrimbleMapView: zoomOut executed")

        case "toggleStyle":
            // Check the current style and switch to the other
            let currentStyle = mapView.styleURL
            let newStyle: URL
            if currentStyle == TMGLStyle.mobileDayStyleURL {
                newStyle = TMGLStyle.mobileNightStyleURL
            } else {
                newStyle = TMGLStyle.mobileDayStyleURL
            }
            mapView.styleURL = newStyle
            print("TrimbleMapView: toggleStyle executed, set to: \(newStyle)")
            result(nil)

        case "center":
            // Extract arguments sent from Flutter
            guard let args = call.arguments as? [String: Any],
                  let lat = args["lat"] as? Double,
                  let lng = args["lng"] as? Double else {
                result(FlutterError(code: "INVALID_ARGUMENT", message: "lat and lng must be provided", details: nil))
                return
            }

            print("TrimbleMapView: center executing for Lat: \(lat), Lng: \(lng)")

            // Create a coordinate and center the map on it
            let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lng)
            mapView.setCenter(coordinate, zoomLevel: 8.0, animated: true)
            result(nil)

        default:
            print("TrimbleMapView: Method not implemented: \(call.method)")
            result(FlutterMethodNotImplemented)
        }
    }

    // MARK: - FlutterPlatformView Implementation

    /**
     * Returns the container UIView that Flutter will embed.
     * The TMGLMapView is added to this container once the account is licensed.
     */
    func view() -> UIView {
        return containerView
    }

    // MARK: - Cleanup

    deinit {
        print("TrimbleMapView: Cleaning up MethodChannel and MapView")
        // Remove the method call handler to break the communication link.
        channel?.setMethodCallHandler(nil)
        // Note: TMGLMapView cleanup is handled automatically by iOS
    }
}

Step 3: Review the Code

  • Class Definition:
    • TrimbleMapView implements FlutterPlatformView (required by Flutter to get the native UIView) and AccountManagerDelegate (to handle account initialization).
    • The initializer receives frame, viewId (unique ID for this view instance), args (creation parameters from Flutter including API key and region), and binaryMessenger (for MethodChannel).
  • containerView: UIView: A container view that is immediately returned to Flutter. The map view is added to this container only after the account is licensed.
  • mapView: TMGLMapView?: This holds the actual instance of the Trimble Maps TMGLMapView widget.
  • channel: FlutterMethodChannel?: This is the communication bridge. Note the channel name trimble_map_view_\(viewId) – using the viewId ensures that if you had multiple map views, each would have its own distinct channel.
  • Initialization:
    • Container View: A container UIView is created immediately and will be returned by the view() function.
    • Account Check: Before setting up a new account, we check if AccountManager.default.state is already .loaded and licensed. If so, we initialize the map immediately. Otherwise, we set up the account and wait for the delegate callback.
    • initializeMapView Method: Creates the TMGLMapView and adds it to the container view. This is called either immediately (if already licensed) or when stateChanged reports the account is loaded.
    • stateChanged Method: This delegate method is called when the account status changes. When the status is .loaded and the account is licensed for Maps SDK, we call initializeMapView().
    • handleMethodCall Method: This is the listener for incoming messages from Flutter.
      • The switch statement checks the name of the method invoked by Flutter (e.g., "zoomIn", "toggleStyle").
      • Each case handles a specific map action.
  • view() -> UIView: Implements the FlutterPlatformView requirement, returning the containerView so Flutter can display it immediately while the map initializes.
  • deinit: Cleanup method that removes the method channel handler when the view is deallocated. This prevents memory leaks.

With this class created, you have the core native component ready. The next step is to work on the TrimbleMapViewFactory so that Flutter can use it to create instances of this TrimbleMapView.

Create the PlatformView Factory (TrimbleMapViewFactory.swift)

Flutter needs a way to create instances of the TrimbleMapView you defined earlier whenever it needs to display the map widget. This is done using a FlutterPlatformViewFactory.

The factory is a relatively simple class whose primary responsibility is to receive creation parameters from Flutter (like your API key and region) and use them to instantiate and return a new TrimbleMapView.

Step 1: Create the TrimbleMapViewFactory.swift File

Create a new Swift file named TrimbleMapViewFactory.swift in the same directory as your TrimbleMapView.swift:

ios/Runner/TrimbleMapViewFactory.swift

Step 2: Add the TrimbleMapViewFactory Class Code

Paste the following Swift code into the TrimbleMapViewFactory.swift file.

import Foundation
import Flutter

/**
 * Factory responsible for creating instances of TrimbleMapView.
 * Extends FlutterPlatformViewFactory to integrate with Flutter's
 * platform view mechanism using StandardMessageCodec.
 */
class TrimbleMapViewFactory: NSObject, FlutterPlatformViewFactory {
    private var messenger: FlutterBinaryMessenger

    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }

    /**
     * Called by Flutter when a new platform view is needed.
     * @param frame The frame for the view
     * @param viewId Unique identifier for this view instance
     * @param args Initialization parameters passed from Dart
     * @return A new instance of TrimbleMapView
     */
    func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
    ) -> FlutterPlatformView {
        // Create and return the native TrimbleMapView with provided frame, view ID, arguments, and messenger
        return TrimbleMapView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger
        )
    }

    /**
     * Returns the codec used to encode/decode arguments.
     * StandardMessageCodec is the default mechanism Flutter uses.
     */
    func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }
}

Step 3: Review the Code

  • Class Definition:
    • TrimbleMapViewFactory implements Flutter’s FlutterPlatformViewFactory protocol.
    • It uses FlutterStandardMessageCodec.sharedInstance() which is the default mechanism Flutter uses to encode the args parameter passed during view creation.
    • The initializer takes a FlutterBinaryMessenger instance. This is essential because the TrimbleMapView needs it to set up its MethodChannel for communication back to Flutter.
  • create Method:
    • This is the core method implemented from FlutterPlatformViewFactory. Flutter calls this method automatically whenever your Dart code requests a new instance of the platform view associated with this factory.
    • frame: CGRect: The frame for the view.
    • viewId: Int64: A unique integer ID assigned by Flutter for this specific instance of the platform view.
    • args: Any?: This parameter holds the data passed from your Dart code when you create the UiKitView widget. In this example, we expect it to be a dictionary containing the API key and region.
    • Instantiation: Finally, it creates a new TrimbleMapView instance, passing the required parameters. This new TrimbleMapView object is then returned to Flutter, which handles embedding its view into the Flutter UI.
  • createArgsCodec Method:
    • Returns the codec used to encode/decode arguments. FlutterStandardMessageCodec is the standard codec that handles common types like strings, numbers, lists, and maps.

With the TrimbleMapView and its TrimbleMapViewFactory created, the next step is to register this factory with the Flutter engine so that Flutter knows how to create your native map view when requested from Dart.

Register the PlatformView Factory

Now that you have the native view (TrimbleMapView) and its factory (TrimbleMapViewFactory), you need to tell Flutter about the factory so it can use it. This registration happens in your app’s AppDelegate.swift.

Step 1: Locate and Modify AppDelegate.swift

Open your app delegate file, located at:

ios/Runner/AppDelegate.swift

Step 2: Add the Factory Registration Code

Update your AppDelegate.swift to include the factory registration:

import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)

      if let pluginRegistrar = self.registrar(forPlugin: "TrimbleMapViewPlugin") {
          let factory = TrimbleMapViewFactory(messenger: pluginRegistrar.messenger())
          pluginRegistrar.register(factory, withId: "trimble_map_view")
      }

      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Step 3: Review the Code

  • registrar(forPlugin:): Obtains a registrar for the plugin, which provides access to the binary messenger.
  • TrimbleMapViewFactory(messenger:): Creates an instance of the factory, passing the messenger for MethodChannel communication.
  • pluginRegistrar.register(factory, withId:): Registers the factory with the view type identifier "trimble_map_view". You must use this exact same string in your Dart code when creating the UiKitView widget.

With the factory registered, the iOS native side is now fully configured. The next steps involve writing the Dart code in your Flutter application to display this native view and communicate with it.


Implement the Flutter UI (main.dart)

With the native iOS components set up and registered, we can now write the Flutter code to display the map and add interactive controls.

This involves:

  1. Setting up the main application structure.
  2. Using the UiKitView widget to embed the native view, identified by the viewType string we registered earlier.
  3. Passing the API key and region to the native side during view creation.
  4. Establishing a MethodChannel to communicate between Dart and the native TrimbleMapView.
  5. Handling the mapReady callback from native code to know when the map is initialized.
  6. Creating UI buttons that invoke methods (like zoomIn, toggleStyle) on the native map view via the MethodChannel.

Step 1: Locate main.dart

This code typically resides in the main entry point of your Flutter application:

lib/main.dart

Step 2: Add the Flutter Application Code

Replace the contents of your lib/main.dart with the following code:

import 'package:flutter/foundation.dart';	// Provides defaultTargetPlatform
import 'package:flutter/material.dart';  	// Material widgets and theming
import 'package:flutter/services.dart';  	// MethodChannel and message codecs

void main() {
  // Ensure Flutter framework is fully initialized before creating the app
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());                	// Launch the app
}

/// Root widget of the application
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
	return const MaterialApp(
  	// IMPORTANT: Replace 'YOUR_API_KEY' with your actual Trimble Maps API key
  	home: MapWithControls(
    	apiKey: 'YOUR_API_KEY',
    	region: 'worldwide', // Options: 'worldwide', 'northAmerica', 'europe'
  	),
	);
  }
}

/// Stateful widget that hosts the native Trimble map and control buttons
class MapWithControls extends StatefulWidget {
  final String apiKey; // API Key passed from MyApp
  final String region; // Region for Trimble Maps services
  const MapWithControls({
    super.key,
    required this.apiKey,
    this.region = 'worldwide', // Options: 'worldwide', 'northAmerica', 'europe'
  });

  @override
  State<MapWithControls> createState() => _MapWithControlsState();
}

class _MapWithControlsState extends State<MapWithControls> {
  // Channel for communicating Dart -> Native (TrimbleMapView)
  late MethodChannel _channel;
  // Flag to track if the native map has finished loading its initial style
  bool _mapReady = false;

  /// Callback triggered by UiKitView widget AFTER the native view is created.
  void _onPlatformViewCreated(int id) {
	debugPrint('Native PlatformView created with id: $id');

	// Initialize the MethodChannel.
	// IMPORTANT: The channel name MUST match the one used in TrimbleMapView.swift
	_channel = MethodChannel('trimble_map_view_$id');

	// Set up a handler for messages FROM Native TO Dart.
	_channel.setMethodCallHandler((call) async {
  	debugPrint('Dart received method call: ${call.method}');
  	// Check if the native side signaled that the map is ready
  	if (call.method == 'mapReady') {
    	debugPrint('mapReady received from native, enabling controls.');
    	// Update state to enable UI controls that interact with the map
    	setState(() {
      	_mapReady = true;
    	});
  	}
  	// Handle other potential methods from native if needed
	});
  }

  @override
  Widget build(BuildContext context) {
	// This example uses UiKitView, so restrict to iOS platform
	if (defaultTargetPlatform != TargetPlatform.iOS) {
  	return const Scaffold(
    	body: Center(child: Text('This example currently supports iOS only.')),
  	);
	}

	// Main UI structure
	return Scaffold(
  	appBar: AppBar(title: const Text('Trimble Map Flutter Example')),
  	body: Stack( // Use Stack to overlay controls on top of the map
    	children: [
      	// The widget that embeds the native iOS view
      	UiKitView(
        	// IMPORTANT: Must match the viewType registered in AppDelegate.swift
        	viewType: 'trimble_map_view',
        	// Callback function called when the native view is created
        	onPlatformViewCreated: _onPlatformViewCreated,
        	// Parameters passed to the native PlatformViewFactory's 'create' method
        	creationParams: <String, dynamic>{
          	// IMPORTANT: Keys must match those expected in TrimbleMapView.swift
          	'apiKey': widget.apiKey,
          	'region': widget.region,
        	},
        	// Codec used for encoding 'creationParams', must match the factory
        	creationParamsCodec: const StandardMessageCodec(),
      	),

      	// Overlay container for control buttons
      	Positioned(
        	bottom: 20,
        	left: 20,
        	right: 20, // Allow buttons to space out if needed
        	child: Card( // Wrap buttons in a Card for better visibility
          	elevation: 4.0,
          	child: Padding(
            	padding: const EdgeInsets.all(8.0),
            	child: Wrap( // Use Wrap for button layout flexibility
              	spacing: 8.0, // Horizontal space between buttons
              	runSpacing: 4.0, // Vertical space if buttons wrap
              	alignment: WrapAlignment.center,
              	children: [
                	// --- Map Control Buttons ---

                	ElevatedButton(
                  	// Enable button only when _mapReady is true
                  	onPressed: _mapReady ? _zoomIn : null,
                  	child: const Text('Zoom In'),
                	),

                	ElevatedButton(
                  	onPressed: _mapReady ? _zoomOut : null,
                  	child: const Text('Zoom Out'),
                	),

                	ElevatedButton(
                  	onPressed: _mapReady ? _toggleStyle : null,
                  	child: const Text('Toggle Style'),
                	),

                	ElevatedButton(
                  	onPressed: _mapReady ? _centerMap : null,
                  	child: const Text('Center (CA)'),
                	),
              	],
            	),
          	),
        	),
      	),
    	],
  	),
	);
  }

  // --- Button Action Methods ---

  void _zoomIn() {
	debugPrint('Button: Zoom In');
	// Invoke the 'zoomIn' method on the native side via the channel
	_channel.invokeMethod('zoomIn');
  }

  void _zoomOut() {
	debugPrint('Button: Zoom Out');
	// Invoke the 'zoomOut' method on the native side
	_channel.invokeMethod('zoomOut');
  }

  void _toggleStyle() {
	debugPrint('Button: Toggle Style');
	// Invoke the 'toggleStyle' method on the native side
	_channel.invokeMethod('toggleStyle');
  }

  void _centerMap() {
	// Define coordinates (Example: somewhere in California)
	const double lat = 36.7783;
	const double lng = -119.4179;
	debugPrint('Button: Center Map ($lat, $lng)');
	// Invoke the 'center' method on the native side, passing arguments
	_channel.invokeMethod('center', <String, double>{
  	'lat': lat,
  	'lng': lng,
	});
  }
}

Summary and Next Steps

Congratulations! You have successfully integrated a basic Trimble Map view into a Flutter application for iOS using PlatformView.

In this guide, we covered the essential steps:

  • Configured Dependencies: Added the Trimble Maps SDK frameworks using Carthage and embedded them in the Xcode project.
  • Configured Permissions: Added location permissions to Info.plist for location services support.
  • Implemented Native View: Created the TrimbleMapView.swift class, which implements FlutterPlatformView, initializes the native Trimble TMGLMapView, handles account initialization via AccountManagerDelegate, and sets up a MethodChannel for communication.
  • Created View Factory: Implemented TrimbleMapViewFactory.swift to enable Flutter to create instances of the native TrimbleMapView, passing the API key and region during creation.
  • Registered Factory: Updated AppDelegate.swift to register the TrimbleMapViewFactory with the Flutter engine using a unique view type identifier ("trimble_map_view").
  • Built Flutter UI: Developed the Dart code in lib/main.dart using the UiKitView widget to embed the native map.
  • Established Communication: Set up the MethodChannel on both the Dart and native sides to:
    • Send commands (zoom, style change, center) from Flutter buttons to the native map.
    • Receive a mapReady signal from the native map to enable Flutter UI controls.

Get Support

If you encounter issues specifically related to the Trimble Maps SDK, this integration guide, or have further questions about using our SDK features, please don’t hesitate to reach out to our support team:

Further Reading

This guide focused on the fundamental setup for displaying a map using Flutter’s PlatformView on iOS. The Trimble Maps SDK for iOS offers a wide range of additional features not covered here, such as:

  • Displaying user location
  • Routing
  • Advanced camera controls
  • Custom map styling
  • Displaying custom data

To explore these features and gain a deeper understanding of the underlying native SDK capabilities, please refer to the comprehensive Trimble Maps SDK for iOS - Getting Started Guide.

We encourage you to use the foundation built in this guide as a starting point for integrating more advanced map functionalities into your Flutter applications.

Last updated February 19, 2026.