Post icon

Binding to native iOS frameworks in .NET 9

by Ivan Mir on Jun 23, 2025

Building apps with mobile .NET is great because most of the time it just works. But when it doesn’t, you suddenly need to become an expert in long chains of MSBuild calls.

One of the more complex chains involves creating C# bindings for native libraries. It used to be more straightforward, but with the rapid evolution of the .NET ecosystem and the transition away from Xamarin breaking some old workflows, fresh examples are hard to come by. LLMs do not help much either and keep giving outdated tips.

So, here’s a step-by-step guide on how to create bindings from scratch in .NET 9 and Xcode 16, even if you’ve never done it before. You can get the sources from Github or the package from Nuget.

Creating the binding project

Start by creating a new binding project for .NET 9:

dotnet new iosbinding -n "Lottie.iOS"

This generates just three files: Lottie.iOS.csproj, ApiDefinition.cs, and StructsAndEnums.cs.

Open Lottie.iOS.csproj in a text editor. You'll see this essential configuration:

  <ItemGroup>
    <ObjcBindingApiDefinition Include="ApiDefinition.cs"/>
    <ObjcBindingCoreSource Include="StructsAndEnums.cs"/>
  </ItemGroup>

Next, add a reference to the native framework (which we'll create shortly):

<ItemGroup>
    <NativeReference Include="Lottie-Static.xcframework">
      <Kind>Framework</Kind>
      <Frameworks>CoreGraphics CoreImage Foundation QuartzCore UIKit</Frameworks>
    </NativeReference>
  </ItemGroup>

The <Frameworks> element must list all frameworks that your native library depends on. You can look up these dependencies in the native project in the next step. (Note that in the case of Lottie, CoreAnimation isn't a separate framework in Apple's stack – it's part of QuartzCore.)

Building the native framework

First, we need to compile the native library into a format that .NET can consume:

  1. Download the native Xcode project
  2. Open Lottie.xcworkspace
  3. Select the Lottie-iOS target from the project root
  4. Navigate to Build Settings
  5. Search for "Mach-O Type"
  6. Change it to "Static Library"

In Lottie's case, its package dependencies are also static libraries, so no additional configuration is needed. For other frameworks, check their Package.swift files to understand their dependency structure.

Now build the framework for iOS devices. Run this from the Xcode project root:

xcodebuild archive \
  -scheme "Lottie (iOS)" \
  -destination 'generic/platform=iOS' \
  -archivePath ./build/Lottie \
  SKIP_INSTALL=NO \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES

You'll also need a simulator version for development:

xcodebuild archive \
  -scheme "Lottie (iOS)" \
  -destination 'generic/platform=iOS Simulator' \
  -archivePath ./build/LottieSim \
  SKIP_INSTALL=NO \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES

With both architectures ready, create the final XCFramework:

xcodebuild -create-xcframework \
-framework ./build/Lottie.xcarchive/Products/Library/Frameworks/Lottie.framework \
-framework ./build/LottieSim.xcarchive/Products/Library/Frameworks/Lottie.framework \
-output ./build/Lottie-Static.xcframework

The resulting Lottie-Static.xcframework contains:

  • Info.plist describing the framework's libraries
  • ios-arm64 directory for device builds
  • ios-arm64_x86_64-simulator directory for simulator builds

Move this XCFramework to your binding project's root directory.

Generating bindings with Objective Sharpie

The binding generation relies on Objective Sharpie, a closed-source tool that's showing its age. According to the .NET iOS/Mac team, a replacement is in the worksб but for now, this is what we’ve got.

Setting up Sharpie requires some specific dependencies:

  1. Install Xamarin.iOS – yes, the legacy version that supports only iOS 16
  2. Download Xcode 16.1 using Xcodes.app – Sharpie depends on Clang 16, which requires the iOS 18.1 SDK
  3. Install Objective Sharpie itself: brew install --cask objectivesharpie

With everything installed, generate the bindings from your project root:

sharpie bind \
--sdk=iphoneos18.1 \
--output=SharpieOutput \
--framework=/path/to/your/project/Lottie-Static.xcframework/ios-arm64/Lottie.framework

Sharpie creates ApiDefinitions.cs and StructsAndEnums.cs in the output directory. Move these to your project root, but watch out – the template creates ApiDefinition.cs (singular), while Sharpie generates ApiDefinitions.cs (plural). Rename accordingly.

Now comes the manual cleanup. Add your namespace to both files and review the generated code. Sharpie isn't perfect and often includes problematic exports. For instance, it adds initWithCoder: methods to every class, which conflict with the methods added by the linker. You have to remove these manually.

I also encountered a particularly cryptic issue while binding Lottie: Sharpie exported CAAnimation as a concrete type instead of the ICAAnimation interface, causing linker crashes with the error "no type or protocol named 'Microsoft_iOS__CoreAnimation_CAAction'". Thanks to Rolf Bjarne from the .NET iOS team for helping me debug this one.

So, watch out for the types it generates: for most libraries, quickly reviewing ApiDefinition(s).cs is not a big deal.

Packaging and using your bindings

Build the release package:

dotnet build Lottie.iOS.csproj -c Release

The NuGet package appears in bin/Release. If you're not publishing to NuGet, you can reference it locally from your iOS project:

dotnet add package QotoQot.Bindings.Lottie.iOS --source /PATH/TO/YOUR/PROJECT/Lottie.iOS/bin/Release

Caution: if you’re updating your package during development, make sure to bump its version and then re-add it. I forgot about NuGet caching and was puzzled for a while why my app kept crashing even after reinstalling the package.

I also tried referencing the binding project directly in my iOS app, but IDE support was unreliable – JetBrains Rider couldn't resolve the bound classes properly. Creating a Nuget package, even for local use, turned out to be simpler.

That's it, try using your freshly created native bindings!


Ivan Mir's photo
Ivan Mir has been making indie software since 2009. In this blog, he shares tips about cross-platform development in .NET
Mastodon | Bluesky