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.
See Add the MOPRIM TMD SDK dependency for the list of required AWS frameworks, and the link to download them.
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.
You should now have a project with an ApiDefinition.cs file, and a Structs.cs file.
In your project, right-click on Native References > Add Native Reference and add the following XCFrameworks:
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.
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.
Download and install Objective-Sharpie from the Xamarin website.
Create a new directory on your computer, that you can call for example “sharpie-tmd-bindings”, and put a copy of MOPRIMTmdSdk.xcframework in that directory. Open a Terminal window, and cd into this directory.
$cd sharpie-tmd-bindings/
$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.
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
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();
}
}
}