How to Build a NuGet Package with iOS Frameworks for .NET MAUI Development
Tags: ios, mobile, dotnet, maui
Published: 2024-02-29 06:39:34

Building a NuGet package with iOS frameworks for .NET MAUI development is significantly more complicated than for Android. In this article, we will explore the process of adding iOS frameworks to an existing NuGet package that already supports **Windows**, **Linux** and **Android**. Our goal is to create a comprehensive NuGet package that can be utilized for building machine-readable zone (MRZ) recognition applications with .NET console, .NET WinForms, and .NET MAUI.


## NuGet Package
[https://www.nuget.org/packages/MrzScannerSDK](https://www.nuget.org/packages/MrzScannerSDK)


## Tools
- **Xcode**
    
    Build an iOS framework for MRZ recognition.    

- **Objective Sharpie**
    
    Generate C# bindings for Objective-C libraries.

- **Visual Studio for Windows** or **Visual Studio for Mac**
    
    Create an iOS binding library project that references the iOS framework and the C# bindings.


## Step 1: Build an iOS Framework for MRZ Recognition Using Xcode
Dynamsoft offers a demo MRZ scanner for iOS, which you can download from [GitHub](
https://github.com/Dynamsoft/mrz-scanner-for-mobile-samples/tree/main/ios/Objective-C/MRZScannerObjC).

Once you have downloaded the source code, navigate to the `MRZScannerObjC` directory and execute `pod install` to install all necessary dependencies.

![MRZ iOS SDK dependencies](https://www.dynamsoft.com/codepool/img/2024/02/mrz-ios-sdk-dependencies.png)

Next, open `MRZScannerObjC.xcworkspace` in Xcode. This allows you to build and test the MRZ scanner application on an iOS device.

![Xcode mrz scanner](https://www.dynamsoft.com/codepool/img/2024/02/xcode-mrz-scanner.png)

The `MRZRecognizer` subproject is crucial as it loads MRZ model files and offers APIs for recognizing MRZ data from images. To ensure it provides the same APIs as its Android counterpart, you'll need to add specific Objective-C code in the `DynamsoftMRZRecognizer.m` file:

```objc
- (NSArray<iDLRResult *> *)recognizeMrzFile:(NSString *)fileName error:(NSError * _Nullable __autoreleasing *)error {
    return [self recognizeFile:fileName error:error];
}

- (NSArray<iDLRResult *> *)recognizeMrzBuffer:(iImageData *)imageData error:(NSError * _Nullable __autoreleasing *)error {
    return [self recognizeBuffer:imageData error:error];
}

- (NSArray<iDLRResult *> *)recognizeMrzImage:(UIImage *)image error:(NSError * _Nullable __autoreleasing *)error {
    return [self recognizeImage:image error:error];
}
```
    
Finally, switch the `Build Configuration` to `Release` and build the `MRZRecognizer.framework` project.

![ios framework build release](https://www.dynamsoft.com/codepool/img/2024/02/ios-framework-build-release.png)

## Step 2: Generate C# Bindings for Objective-C Libraries with Objective Sharpie
**Objective Sharpie** serves as a command-line tool designed to generate C# bindings for Objective-C libraries, simplifying the initial phase of the binding process. It works by analyzing the header files of a native library and translating its public API into a set of binding definitions. This process results in the creation of two essential files: `ApiDefinition.cs` and `StructsAndEnums.cs`, both of which are integral to the development of the iOS binding library project.

In this step, we aim to generate C# bindings for three specific Objective-C libraries: `DynamsoftCore.framework`, `DynamsoftLabelRecognizer.framework`, and `MRZRecognizer.framework` with the `sharpie bind` command:

```bash
sharpie bind -f xxx.framework -sdk iphoneos16.0
```

## Step 3: Create an iOS Binding Library Project in Visual Studio
In Visual Studio, scaffold a new project with the `iOS Binding Library` template.

![iOS binding library template](https://www.dynamsoft.com/codepool/img/2024/02/ios-binding-library-template.png)

Then incorporate the three frameworks (`DynamsoftCore.framework`, `DynamsoftLabelRecognizer.framework`,` MRZRecognizer.framework`) along with the C# bindings (`ApiDefinition.cs` and `StructsAndEnums.cs`) generated by Objective Sharpie. The complete `csproj` file is written as follows:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net7.0-ios</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <AssemblyName>MrzScannerSDK</AssemblyName>
    <IsBindingProject>true</IsBindingProject>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
  </PropertyGroup>

  <ItemGroup>
    <ObjcBindingApiDefinition Include="DynamsoftCore/ApiDefinitions.cs" />
    <ObjcBindingCoreSource Include="DynamsoftCore/StructsAndEnums.cs" />
    <NativeReference Include="DynamsoftCore.framework">
      <Kind>Framework</Kind>
      <Frameworks></Frameworks>
    </NativeReference>
  </ItemGroup>

  <ItemGroup>
    <ObjcBindingApiDefinition Include="DynamsoftLabelRecognizer/ApiDefinitions.cs" />
    <ObjcBindingCoreSource Include="DynamsoftLabelRecognizer/StructsAndEnums.cs" />
    <NativeReference Include="DynamsoftLabelRecognizer.framework">
      <Kind>Framework</Kind>
      <Frameworks></Frameworks>
    </NativeReference>
  </ItemGroup>

  <ItemGroup>
    <ObjcBindingApiDefinition Include="MRZRecognizer/ApiDefinitions.cs" />
    <ObjcBindingCoreSource Include="MRZRecognizer/StructsAndEnums.cs" />
    <NativeReference Include="MRZRecognizer.framework">
      <Kind>Framework</Kind>
      <Frameworks></Frameworks>
    </NativeReference>
  </ItemGroup>

</Project>
```

Additionally, copy `MrzParser.cs`, `MrzResult.cs`, and `MrzScanner.cs` files from the existing Android binding library project to this new iOS binding library project. Modifications to `MrzScanner.cs` are required to ensure compatibility with iOS APIs.

```csharp
using DynamsoftCore;
using Com.Dynamsoft.Dlr;
using MRZRecognizer;
using System.Runtime.InteropServices;
using Foundation;

namespace Dynamsoft
{
    public class MrzScanner
    {
        private DynamsoftMRZRecognizer? recognizer;
        ...

        public class LicenseVerification : LicenseVerificationListener
        {
            public override void LicenseVerificationCallback(bool isSuccess, NSError error)
            {
                if (!isSuccess)
                {
                    System.Console.WriteLine(error.UserInfo);
                }
            }
        }

        public static void InitLicense(string license, object? context = null)
        {
            DynamsoftLicenseManager.InitLicense(license, new LicenseVerification());
        }

        private MrzScanner()
        {
            recognizer = new DynamsoftMRZRecognizer();
        }

        ...

        public static string? GetVersionInfo()
        {
            return DynamsoftLabelRecognizer.Version;
        }

        public Result[]? DetectFile(string filename)
        {
            if (recognizer == null) return null;

            NSError error;
            iDLRResult[]? mrzResult = recognizer.RecognizeMrzFile(filename, out error);
            return GetResults(mrzResult);
        }

        public Result[]? DetectBuffer(byte[] buffer, int width, int height, int stride, ImagePixelFormat format)
        {
            if (recognizer == null) return null;

            IntPtr data = Marshal.AllocHGlobal(buffer.Length);
            Marshal.Copy(buffer, 0, data, buffer.Length);
            NSData converted = NSData.FromBytes(data, (nuint)buffer.Length);

            iImageData imageData = new iImageData()
            {
                Bytes = converted,
                Width = width,
                Height = height,
                Stride = stride,
                Format = (EnumImagePixelFormat)format,
            };
            NSError error;
            iDLRResult[]? mrzResult = recognizer.RecognizeMrzBuffer(imageData, out error);
            Marshal.Release(data);

            return GetResults(mrzResult);
        }

        private Result[]? GetResults(iDLRResult[]? mrzResult)
        {
            if (mrzResult != null && mrzResult[0].LineResults != null)
            {
                iDLRLineResult[] lines = mrzResult[0].LineResults.ToArray();
                Result[] result = new Result[lines.Length];
                for (int i = 0; i < lines.Length; i++)
                {
                    result[i] = new Result()
                    {
                        Confidence = (int)lines[i].Confidence,
                        Text = lines[i].Text ?? "",
                        Points = new int[8]
                        {
                             (int)((NSValue)lines[i].Location.Points[0]).CGPointValue.X,
                             (int)((NSValue)lines[i].Location.Points[0]).CGPointValue.Y,
                             (int)((NSValue)lines[i].Location.Points[1]).CGPointValue.X,
                             (int)((NSValue)lines[i].Location.Points[1]).CGPointValue.Y,
                             (int)((NSValue)lines[i].Location.Points[2]).CGPointValue.X,
                             (int)((NSValue)lines[i].Location.Points[2]).CGPointValue.Y,
                             (int)((NSValue)lines[i].Location.Points[3]).CGPointValue.X,
                             (int)((NSValue)lines[i].Location.Points[3]).CGPointValue.Y
                        }
                    };
                }

                return result;
            }

            return null;
        }
    }
}
```

The final structure of the iOS binding library project is like this:

![iOS binding library structure](https://www.dynamsoft.com/codepool/img/2024/02/ios-binding-library-structure.png)

With these steps completed, your iOS binding library project is nearly finalized. The subsequent phase involves addressing any build errors and warnings associated with the `ApiDefinition.cs` and `StructsAndEnums.cs` files. Manual corrections are essential here, as Objective Sharpie's automated process might not perfectly map Objective-C libraries to C# bindings. Once these adjustments are made, compiling the project should produce the `MrzScannerSDK.dll` file.

## Step 4: Test the iOS Binding Library in a .NET MAUI Application
Open the .NET MAUI example project located within the `example` directory. The project was initially set up for Android use. Now we will integrate the iOS binding library into the project and implement iOS-specific modifications:
1. Modify the `Platforms/iOS/Info.plist` file by inserting specific keys to secure the necessary permissions.

    ```xml
    <key>NSCameraUsageDescription</key>
	<string>This app is using the camera</string>
	<key>NSPhotoLibraryAddUsageDescription</key>
	<string>This app is saving photo to the library</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>This app needs access to microphone for taking videos.</string>
	<key>NSPhotoLibraryAddUsageDescription</key>
	<string>This app needs access to the photo gallery for picking photos and videos.</string>
	<key>NSPhotoLibraryUsageDescription</key>
	<string>This app needs access to photos gallery for picking photos and videos.</string>
    ```

2. In the `Platforms/iOS/Program.cs` file, call `MrzScanner.InitLicense` to activate the Dynamsoft Label Recognizer. You can acquire the license key through the [Dynamsoft customer portal](https://www.dynamsoft.com/customer/license/trialLicense/?product=dlr&source=codepool).

    ```csharp
    using ObjCRuntime;
    using UIKit;
    using Dynamsoft;
    
    namespace MauiAndroidMrz
    {
        public class Program
        {
            static void Main(string[] args)
            {
                MrzScanner.InitLicense("LICENSE-KEY");
                UIApplication.Main(args, null, typeof(AppDelegate));
            }
        }
    }
    ```

3. Conduct a global search for `#if ANDROID` within the project and replace it with `#if ANDROID || IOS` to accommodate both platforms.
4. Finally, compile and execute the .NET MAUI application on an iPhone or iPad to validate the integration and functionality of the iOS binding library.
    
    ![.NET MAUI iOS MRZ recognition](https://www.dynamsoft.com/codepool/img/2024/02/dotnet-maui-ios-ipados-mrz-recognition.png)

## Step 5. Create a NuGet Package with iOS Frameworks and C# Bindings
To incorporate the iOS binding library into the existing NuGet package, you'll need to modify the `MrzScannerSDK.nuspec` file by adding specific entries for iOS:

```xml
<!-- iOS -->
<file src="ios\sdk\bin\Release\net7.0-ios\*.dll" target="lib\net7.0-ios16.1" />
<file src="ios\sdk\bin\Release\net7.0-ios\MrzScannerSDK.resources\**\*.*"
    target="lib\net7.0-ios16.1\MrzScannerSDK.resources" />
```

After updating the `.nuspec` file, utilize the `nuget pack` command to compile a comprehensive NuGet package that supports .NET MAUI development across multiple platforms.

![nuget package for windows, linux, android, and iOS](https://www.dynamsoft.com/codepool/img/2024/02/nuget-package-with-ios-framework.png)

## Source Code
[https://github.com/yushulx/dotnet-mrz-sdk](https://github.com/yushulx/dotnet-mrz-sdk)