How to Build a NuGet Package with iOS Frameworks for .NET MAUI Development
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.
This article is Part 3 in a 3-Part Series.
NuGet Package
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.
Once you have downloaded the source code, navigate to the MRZScannerObjC
directory and execute pod install
to install all necessary dependencies.
Next, open MRZScannerObjC.xcworkspace
in Xcode. This allows you to build and test the MRZ scanner application on an iOS device.
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:
- (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.
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:
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.
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:
<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.
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:
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:
-
Modify the
Platforms/iOS/Info.plist
file by inserting specific keys to secure the necessary permissions.<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>
-
In the
Platforms/iOS/Program.cs
file, callMrzScanner.InitLicense
to activate the Dynamsoft Label Recognizer. You can acquire the license key through the Dynamsoft customer portal.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)); } } }
- Conduct a global search for
#if ANDROID
within the project and replace it with#if ANDROID || IOS
to accommodate both platforms. -
Finally, compile and execute the .NET MAUI application on an iPhone or iPad to validate the integration and functionality of the iOS binding library.
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:
<!-- 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.