Using the TMD SDK in a Xamarin.iOS project

When working with a Xamarin.iOS project, it is possible to import XCFrameworks (such as the MOPRIMTmdSdk and the AWS XCFrameworks it depends on) by creating a Bindings Library that references those. You will then have a .dll package that you will be able to reference from your Xamarin.iOS project. This chapter will tell you how to create a Bindings Library for the MOPRIM TMD SDK and how to use it in your Xamarin.iOS project.

Table of content

Get the MOPRIMTmdSdk.xcframework and AWS XCFrameworks

See Add the MOPRIM TMD SDK dependency for the list of required AWS frameworks, and the link to download them.

Setup an iOS Bindings Library in Visual Studio

You can either create a new solution, or setup this Bindings Library inside your existing solution. Here we are creating a new solution:

In Visual Studio, select File > New Solution… and choose the template iOS > Library > Bindings Library.

New Project Dialog

You should now have a project with an ApiDefinition.cs file, and a Structs.cs file.

Add references to the XCFrameworks

In your project, right-click on Native References > Add Native Reference and add the following XCFrameworks:

  • MOPRIMTmdSdk.xcframework
  • AWSAPIGateway.xcframework
  • AWSCognitoIdentityProvider.xcframework
  • AWSCognitoIdentityProviderASF.xcframework
  • AWSCore.xcframework
  • AWSS3.xcframework

Bindings Project Structure

As of Visual Studio for Mac v8.10.14, referencing an XCFramework requires some manual editing of the project file: Right-click on the project (not the solution), and select “Edit Project File”.

For each Native Reference that you just added, you should replace the flag Static with Framework. So you should replace the following lines:

<NativeReference Include="../XCFs\AWSAPIGateway.xcframework">
  <Kind>Static</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSCognitoIdentityProvider.xcframework">
  <Kind>Static</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSCognitoIdentityProviderASF.xcframework">
  <Kind>Static</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSCore.xcframework">
  <Kind>Static</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSS3.xcframework">
  <Kind>Static</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="..\XCFs\MOPRIMTmdSdk.xcframework">
  <Kind>Static</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>

By the following lines:

<NativeReference Include="../XCFs\AWSAPIGateway.xcframework">
  <Kind>Framework</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSCognitoIdentityProvider.xcframework">
  <Kind>Framework</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSCognitoIdentityProviderASF.xcframework">
  <Kind>Framework</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSCore.xcframework">
  <Kind>Framework</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="../XCFs\AWSS3.xcframework">
  <Kind>Framework</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>
<NativeReference Include="..\XCFs\MOPRIMTmdSdk.xcframework">
  <Kind>Framework</Kind>
  <SmartLink>False</SmartLink>
</NativeReference>

In addition, any bindings project using XCFramework requires the NoBindingEmbedding value to be set on the project file. And you also have to set this value manually:

<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
     <!--Other properties omitted for brevity-->
     <PropertyGroup>
        <NoBindingEmbedding>true</NoBindingEmbedding>
    </PropertyGroup>
</Project>

If you encounter problems when using the bindings library from your Xamarin.iOS project, make sure to double-check that the project file for the bindings library is still correct. Xamarin might occasionally change this file automatically without warning.

Write the C# bindings for the TMD SDK’s Objective-C headers

Once the project is properly configured, you can focus on the essential, which is to write the API definition in ApiDefinition.cs. To facilitate this process, Xamarin provides a command-line tool called Objective-Sharpie.

$cd sharpie-tmd-bindings/
  • Run the sharpie command that will generate a new ApiDefinition.cs:
$sharpie bind --output=sharpie_output --namespace=MOPRIMTmdSdk --sdk=iphoneos15.0 -scope MOPRIMTmdSdk.xcframework/ios-arm64/MOPRIMTmdSdk.framework/Headers MOPRIMTmdSdk.xcframework/ios-arm64/MOPRIMTmdSdk.framework/Headers/*.h

You might get a fatal error telling that the ‘MOPRIMTmdSdk/TMD.h’ file is not found. You can ignore this error. The terminal should tell you that two files were generated, and gives you a “Binding Analysis”.

You now have a new directory called “sharpie-tmd-bindings” that contains ApiDefinitions.cs and StructsAndEnums.cs. These files can be used as reference to write the ApiDefinitions.cs file of our iOS Bindings Library.

Note that if Objective-Sharpie helps you bootstrap the creation of the binding, it is still recommended to manually check if the output generated is correct, and every code marked with “Verify” in the generated ApiDefinitions.cs requires special attention.

Here is an example of an ApiDefinition.cs file that has bindings to let us use the TMD.init, TMD.start, and TMD.stop methods, as well as the TMDDelegate. Note that some lines generated by Objective-Sharpie were commented out and corrected:

using System;
using Foundation;
using ObjCRuntime;
using UIKit;

// This using directive generated by Objective-Sharpie is not necessary:
// using MOPRIMTmdSdk;

namespace MOPRIMTmdSdk
{
	
	// audit-objc-generics: @interface TMDTask<__covariant ResultType> : NSObject
	[BaseType(typeof(NSObject))]
	interface TMDTask
	{
		// +(instancetype _Nonnull)taskWithResult:(ResultType _Nullable)result;
		[Static]
		[Export("taskWithResult:")]
		TMDTask TaskWithResult([NullAllowed] NSObject result);

		// +(instancetype _Nonnull)taskWithError:(NSError * _Nonnull)error;
		[Static]
		[Export("taskWithError:")]
		TMDTask TaskWithError(NSError error);

		// @property (readonly, nonatomic, strong) ResultType _Nullable result;
		[NullAllowed, Export("result", ArgumentSemantic.Strong)]
		NSObject Result { get; }

		// @property (readonly, nonatomic, strong) NSError * _Nullable error;
		[NullAllowed, Export("error", ArgumentSemantic.Strong)]
		NSError Error { get; }

		// @property (readonly, getter = isFaulted, assign, nonatomic) BOOL faulted;
		[Export("faulted")]
		bool Faulted { [Bind("isFaulted")] get; }

		// @property (readonly, getter = isCompleted, assign, nonatomic) BOOL completed;
		[Export("completed")]
		bool Completed { [Bind("isCompleted")] get; }

		// -(TMDTask * _Nonnull)continueWithBlock:(TMDContinuationBlock _Nonnull)block __attribute__((swift_name("continueWith(block:)")));
		[Export("continueWithBlock:")]
		TMDTask ContinueWithBlock(TMDContinuationBlock block);

		// -(TMDTask * _Nonnull)continueWithSuccessBlock:(TMDContinuationBlock _Nonnull)block __attribute__((swift_name("continueOnSuccessWith(block:)")));
		[Export("continueWithSuccessBlock:")]
		TMDTask ContinueWithSuccessBlock(TMDContinuationBlock block);
	}


	// Binding generated by Objective-Sharpie:
	// typedef id _Nullable (^TMDContinuationBlock)(TMDTask<ResultType> * _Nonnull);
	//delegate NSObject TMDContinuationBlock(TMDTask<NSObject> arg0);

	// Edited binding:
	// typedef id _Nullable (^TMDContinuationBlock)(TMDTask<ResultType> * _Nonnull);
	delegate NSObject TMDContinuationBlock([BlockCallback] TMDTask arg0);


	// @interface TMD : NSObject <TMDUserApiDelegate>
	[BaseType(typeof(NSObject))]
	interface TMD
	{

		// +(TMDTask * _Nonnull)initWithKey:(NSString * _Nonnull)key withEndpoint:(NSString * _Nonnull)endpoint withLaunchOptions:(NSDictionary * _Nullable)launchOptions;
		[Static]
		[Export("initWithKey:withEndpoint:withLaunchOptions:")]
		TMDTask InitWithKey(string key, string endpoint, [NullAllowed] NSDictionary launchOptions);


		// Binding generated by Objective-Sharpie:
		// +(BOOL)isInitialized;
		//[Static]
		//[Export("isInitialized")]
		//[Verify(MethodToProperty)]
		//bool IsInitialized { get; }

		// Edited binding:
		// +(BOOL)isInitialized;
		[Static]
		[Export("isInitialized")]
		bool isInitialized();

		// Binding generated by Objective-Sharpie:
		// +(BOOL)isRunning;
		//[Static]
		//[Export("isRunning")]
		//[Verify(MethodToProperty)]
		//bool IsRunning { get; }

		// Edited binding:
		// +(BOOL)isRunning;
		[Static]
		[Export("isRunning")]
		bool IsRunning();

		// +(void)start;
		[Static]
		[Export("start")]
		void Start();

		// +(void)stop;
		[Static]
		[Export("stop")]
		void Stop();

		// +(void)setDelegate:(id<TMDDelegate> _Nullable)delegate;
		[Static]
		[Export("setDelegate:")]
		void SetDelegate([NullAllowed] TMDDelegate @delegate);
	}
	
	// @protocol TMDDelegate <NSObject>
	[Protocol, Model(AutoGeneratedName = true)]
	[BaseType(typeof(NSObject))]
	interface TMDDelegate
	{
		// @optional -(void)didStart;
		[Export("didStart")]
		void DidStart();

		// @optional -(void)didNotStartWithError:(NSError *)error;
		[Export("didNotStartWithError:")]
		void DidNotStartWithError(NSError error);

		// @optional -(void)didStop;
		[Export("didStop")]
		void DidStop();

		// @optional -(void)didStopWithError:(NSError *)error;
		[Export("didStopWithError:")]
		void DidStopWithError(NSError error);

		// @optional -(void)didStartAnalysing;
		[Export("didStartAnalysing")]
		void DidStartAnalysing();

		// @optional -(void)didStopAnalysing;
		[Export("didStopAnalysing")]
		void DidStopAnalysing();
	}

}

You now should have a Binding library that can be used to initialize the TMD, start and stop it. If you build, you should obtain a “bin” directory in the root of your project, that contains a “Debug” and a “Release” directory. Each of these directories should contain the generated .dll, along with other files that should be kept in the same folder.

Use the bindings library in your Xamarin.iOS project

In the Xamarin.iOS application project, you can now add a reference to the new library by right-clicking on References > Add Reference… > .Net Assembly > Browse… and select the generated .dll file.

Note that in order to run the app on a device, you need to go to the Project Options > iOS Build, and add the following mtouch arguments with “Platform: iPhone” selected:

--optimize=-remove-dynamic-registrar

Xamarin Project Options

Now, in your AppDelegate.cs, you can add the correct using directive, and call the TMD.init method with your API key and endpoint:

using TMDBindingLibrary; // Or whatever the name of your binding library is.

(...)

[Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
    string key = "YOUR_API_KEY_HERE";
    string endpoint = "YOUR°API_ENDPOINT_HERE";

    TMDContinuationBlock block = TmdInitContinuationBlock;
    TMD.InitWithKey(key, endpoint, launchOptions).ContinueWithBlock(block);

    return true;
}

private TMDTask TmdInitContinuationBlock(TMDTask task)
{
    if (task.Error != null)
    {
        Console.WriteLine("Completed init with error");
    }
    else {
        Console.WriteLine("Completed init with success");
    }
    if (task.Result != null) {
        string res = (NSString)task.Result;
        Console.WriteLine("Completed init with Installation Id:" + res);
    }
    return null;
}

And you can call TMD.Start() and TMD.Stop() from a convenient place in your code.

Here is an example of a ViewController that has a button to request for location access, a button to start the TMD, and a button to stop the TMD:

using Foundation;
using System;
using UIKit;
using CoreLocation;
using MOPRIMTmdSdk;

namespace TMDApp
{
    public partial class ViewController : UIViewController
    {

        protected CLLocationManager locMgr;
        SampleTMDDelegate tmdDelegate;

        public ViewController(IntPtr handle) : base(handle)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            locMgr = new CLLocationManager();
            locMgr.DidChangeAuthorization += LocMgr_DidChangeAuthorization;
            tmdDelegate = new SampleTMDDelegate();
            TMD.SetDelegate(tmdDelegate);
        }
        
        class SampleTMDDelegate : TMDDelegate
        {
            public override void DidStart()
            {
                Console.WriteLine("DidStart");
            }

            public override void DidNotStartWithError(NSError error)
            {
                Console.WriteLine("DidNotStartWithError");
            }

            public override void DidStopWithError(NSError error)
            {
                Console.WriteLine("DidStopWithError");
            }

            public override void DidStop()
            {
                Console.WriteLine("DidStop");
            }

            public override void DidStartAnalysing()
            {
                Console.WriteLine("DidStartAnalysing");
            }

            public override void DidStopAnalysing()
            {
                Console.WriteLine("DidStopAnalysing");
            }
        }

        private void LocMgr_DidChangeAuthorization(object sender, EventArgs e)
        {
            //TODO: Here you should check if the user authorized the app to get locations always.
        }

        public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
            // Release any cached data, images, etc that aren't in use.
        }

        // "Allow Location Access" button. Press once for "While in Use" access. Press again for "Always" access.
        partial void askAuthAlways(Foundation.NSObject sender) {
            if (locMgr.AuthorizationStatus == CLAuthorizationStatus.AuthorizedWhenInUse)
            {
                locMgr.RequestAlwaysAuthorization();
            }
            else {
                locMgr.RequestWhenInUseAuthorization();
            }
        }

        // "Start TMD" button
        partial void startTMD(Foundation.NSObject sender) {
            TMD.Start();
        }

        // "Stop TMD" button
        partial void stopTMD(Foundation.NSObject sender) {
            TMD.Stop();
        }
    }
}

Additional resources