ios-slim-bindings

When to use this skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ios-slim-bindings" with this command: npx skills add redth/maui-skillz/redth-maui-skillz-ios-slim-bindings

When to use this skill

Activate this skill when the user asks:

  • How do I create iOS bindings for a native library?

  • How do I wrap an iOS SDK for use in .NET MAUI?

  • How do I create slim bindings for iOS?

  • How do I use Native Library Interop for iOS?

  • How do I bind a Swift library to .NET?

  • How do I use Objective Sharpie for iOS bindings?

  • How do I integrate an xcframework into .NET MAUI?

  • How do I create a Swift wrapper for a native iOS library?

  • How do I update iOS bindings when the native SDK changes?

  • How do I fix iOS binding build errors?

  • How do I expose native iOS APIs to C#?

  • How do I handle CocoaPods dependencies in iOS bindings?

  • How do I handle Swift Package Manager dependencies?

Overview

This skill guides the creation of Native Library Interop (Slim Bindings) for iOS. This modern approach creates a thin native Swift/Objective-C wrapper exposing only the APIs you need from a native iOS library, making bindings easier to create and maintain.

When to Use Slim Bindings vs Traditional Bindings

Scenario Recommended Approach

Need only a subset of library functionality Slim Bindings ✓

Easier maintenance when SDK updates Slim Bindings ✓

Prefer working in Swift/Objective-C for wrapper Slim Bindings ✓

Better isolation from breaking changes Slim Bindings ✓

Need entire library API surface Traditional Bindings

Creating bindings for third-party developers Traditional Bindings

Already maintaining traditional bindings Traditional Bindings

Inputs

Parameter Required Example Notes

libraryName yes FirebaseMessaging , Lottie

Name of the native iOS library to bind

bindingProjectName yes MyBinding.MaciOS

Name for the C# binding project

dependencySource no cocoapods , spm , xcframework

How the native library is distributed

targetFrameworks no net9.0-ios;net9.0-maccatalyst

Target frameworks (default: latest .NET iOS + Mac Catalyst)

exposedApis no List of specific APIs Which native APIs to expose (helps scope the wrapper)

Project Structure

The recommended project structure for Native Library Interop:

MyBinding/ ├── macios/ │ ├── native/ │ │ └── MyBinding/ # Xcode project │ │ ├── MyBinding.xcodeproj/ │ │ │ └── project.pbxproj │ │ ├── MyBinding/ │ │ │ └── DotnetMyBinding.swift # Swift wrapper implementation │ │ └── Podfile # If using CocoaPods │ │ └── Package.swift # If using Swift Package Manager │ └── MyBinding.MaciOS.Binding/ │ ├── MyBinding.MaciOS.Binding.csproj │ └── ApiDefinition.cs ├── sample/ │ └── MauiSample/ # Sample MAUI app │ ├── MauiSample.csproj │ └── MainPage.xaml.cs └── README.md

Step-by-step Process

Step 1: Create Project Structure from Command Line

This section shows how to create the entire binding project structure using only command-line tools—no GUI or template cloning required.

Prerequisites

Install XcodeGen (generates Xcode projects from YAML):

brew install xcodegen

Create Directory Structure

Set your binding name

BINDING_NAME="MyBinding"

Create the full directory structure

mkdir -p ${BINDING_NAME}/macios/native/${BINDING_NAME}/${BINDING_NAME} mkdir -p ${BINDING_NAME}/macios/${BINDING_NAME}.MaciOS.Binding mkdir -p ${BINDING_NAME}/sample/MauiSample

cd ${BINDING_NAME}

Step 2: Create the Xcode Project with XcodeGen

Create the XcodeGen Project Spec

Create macios/native/${BINDING_NAME}/project.yml :

cat > macios/native/${BINDING_NAME}/project.yml << 'EOF' name: MyBinding options: bundleIdPrefix: com.example deploymentTarget: iOS: "15.0" macOS: "12.0" xcodeVersion: "15.0" generateEmptyDirectories: true

settings: base: MARKETING_VERSION: "1.0.0" CURRENT_PROJECT_VERSION: "1" BUILD_LIBRARY_FOR_DISTRIBUTION: YES SKIP_INSTALL: NO MACH_O_TYPE: staticlib SWIFT_VERSION: "5.0" ENABLE_BITCODE: NO DEFINES_MODULE: YES

targets: MyBinding: type: framework platform: iOS sources: - path: MyBinding type: group settings: base: INFOPLIST_FILE: MyBinding/Info.plist PRODUCT_BUNDLE_IDENTIFIER: com.example.mybinding PRODUCT_NAME: MyBinding TARGETED_DEVICE_FAMILY: "1,2" scheme: gatherCoverageData: false shared: true EOF

Create the Swift Source File

Create the Swift wrapper file:

cat > macios/native/${BINDING_NAME}/${BINDING_NAME}/Dotnet${BINDING_NAME}.swift << 'EOF' import Foundation import UIKit

/// Main binding class exposed to .NET @objc(DotnetMyBinding) public class DotnetMyBinding: NSObject {

/// Initialize the native library
@objc(initialize)
public static func initialize() {
    // Initialize your native library here
    print("MyBinding initialized")
}

/// Example synchronous method
@objc(getVersion)
public static func getVersion() -> String {
    return "1.0.0"
}

/// Example async method with completion handler
@objc(fetchDataWithQuery:completion:)
public static func fetchData(
    query: String,
    completion: @escaping (String?, NSError?) -> Void
) {
    // Simulate async operation
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        completion("Result for: \(query)", nil)
    }
}

/// Example view creation
@objc(createViewWithFrame:)
public static func createView(frame: CGRect) -> UIView {
    let view = UIView(frame: frame)
    view.backgroundColor = .systemBlue
    return view
}

} EOF

Create Info.plist

cat > macios/native/${BINDING_NAME}/${BINDING_NAME}/Info.plist << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>$(DEVELOPMENT_LANGUAGE)</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <key>CFBundleShortVersionString</key> <string>$(MARKETING_VERSION)</string> <key>CFBundleVersion</key> <string>$(CURRENT_PROJECT_VERSION)</string> <key>NSPrincipalClass</key> <string></string> </dict> </plist> EOF

Generate the Xcode Project

cd macios/native/${BINDING_NAME} xcodegen generate cd ../../..

This creates MyBinding.xcodeproj with all the correct build settings.

Verify the Generated Project

List the generated files

ls -la macios/native/${BINDING_NAME}/

Verify the scheme was created and is shared

ls -la macios/native/${BINDING_NAME}/${BINDING_NAME}.xcodeproj/xcshareddata/xcschemes/

Step 3: Create the C# Binding Project

Create the Binding .csproj

cat > macios/${BINDING_NAME}.MaciOS.Binding/${BINDING_NAME}.MaciOS.Binding.csproj << 'EOF' <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>net9.0-ios;net9.0-maccatalyst</TargetFrameworks> <Nullable>enable</Nullable> <ImplicitUsings>true</ImplicitUsings> <IsBindingProject>true</IsBindingProject>

&#x3C;!-- Package metadata -->
&#x3C;PackageId>MyBinding.MaciOS&#x3C;/PackageId>
&#x3C;Version>1.0.0&#x3C;/Version>
&#x3C;Authors>Your Name&#x3C;/Authors>
&#x3C;Description>iOS bindings for MyBinding&#x3C;/Description>

</PropertyGroup>

<!-- Reference the Xcode project --> <ItemGroup> <XcodeProject Include="../native/MyBinding/MyBinding.xcodeproj"> <SchemeName>MyBinding</SchemeName> </XcodeProject> </ItemGroup>

<!-- API definition --> <ItemGroup> <ObjcBindingApiDefinition Include="ApiDefinition.cs" /> </ItemGroup> </Project> EOF

Create Initial ApiDefinition.cs

cat > macios/${BINDING_NAME}.MaciOS.Binding/ApiDefinition.cs << 'EOF' using System; using Foundation; using ObjCRuntime; using UIKit;

namespace MyBinding { // @interface DotnetMyBinding : NSObject [BaseType(typeof(NSObject))] interface DotnetMyBinding { // +(void)initialize; [Static] [Export("initialize")] void Initialize();

    // +(NSString * _Nonnull)getVersion;
    [Static]
    [Export("getVersion")]
    string GetVersion();

    // +(void)fetchDataWithQuery:(NSString * _Nonnull)query completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion;
    [Static]
    [Export("fetchDataWithQuery:completion:")]
    [Async]
    void FetchData(string query, Action&#x3C;string?, NSError?> completion);

    // +(UIView * _Nonnull)createViewWithFrame:(CGRect)frame;
    [Static]
    [Export("createViewWithFrame:")]
    UIView CreateView(CGRect frame);
}

} EOF

Step 4: Build and Verify

Build the Binding Project

cd macios/${BINDING_NAME}.MaciOS.Binding dotnet build

This will:

  • Invoke XcodeBuild to compile the native framework

  • Create the xcframework

  • Generate the C# binding assembly

Verify the Build Output

Check that the xcframework was created

find bin -name "*.xcframework" -type d

Find the generated Swift header (for updating ApiDefinition.cs later)

find bin -name "*-Swift.h" -type f

Optional: Add CocoaPods Support

If your native library uses CocoaPods dependencies:

Create Podfile

cat > macios/native/${BINDING_NAME}/Podfile << 'EOF' platform :ios, '15.0'

target 'MyBinding' do use_frameworks! :linkage => :static

Add your pods here

pod 'FirebaseMessaging', '~> 10.0'

end

post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' end end end EOF

Install Pods and Update Project Reference

cd macios/native/${BINDING_NAME} pod install cd ../../..

Update the binding project to use xcworkspace instead of xcodeproj

sed -i '' 's/.xcodeproj/.xcworkspace/g' macios/${BINDING_NAME}.MaciOS.Binding/${BINDING_NAME}.MaciOS.Binding.csproj

Complete Script: Create Binding Project

Here's a complete bash script that creates everything:

#!/bin/bash set -e

Configuration

BINDING_NAME="${1:-MyBinding}" BUNDLE_ID_PREFIX="${2:-com.example}" MIN_IOS_VERSION="${3:-15.0}"

echo "Creating iOS binding project: ${BINDING_NAME}"

Check prerequisites

if ! command -v xcodegen &> /dev/null; then echo "Installing xcodegen..." brew install xcodegen fi

Create directory structure

mkdir -p ${BINDING_NAME}/macios/native/${BINDING_NAME}/${BINDING_NAME} mkdir -p ${BINDING_NAME}/macios/${BINDING_NAME}.MaciOS.Binding cd ${BINDING_NAME}

Create XcodeGen project spec

cat > macios/native/${BINDING_NAME}/project.yml << EOF name: ${BINDING_NAME} options: bundleIdPrefix: ${BUNDLE_ID_PREFIX} deploymentTarget: iOS: "${MIN_IOS_VERSION}" macOS: "12.0" xcodeVersion: "15.0" generateEmptyDirectories: true

settings: base: MARKETING_VERSION: "1.0.0" CURRENT_PROJECT_VERSION: "1" BUILD_LIBRARY_FOR_DISTRIBUTION: YES SKIP_INSTALL: NO MACH_O_TYPE: staticlib SWIFT_VERSION: "5.0" ENABLE_BITCODE: NO DEFINES_MODULE: YES

targets: ${BINDING_NAME}: type: framework platform: iOS sources: - path: ${BINDING_NAME} type: group settings: base: INFOPLIST_FILE: ${BINDING_NAME}/Info.plist PRODUCT_BUNDLE_IDENTIFIER: ${BUNDLE_ID_PREFIX}.${BINDING_NAME,,} PRODUCT_NAME: ${BINDING_NAME} TARGETED_DEVICE_FAMILY: "1,2" scheme: gatherCoverageData: false shared: true EOF

Create Swift wrapper

cat > macios/native/${BINDING_NAME}/${BINDING_NAME}/Dotnet${BINDING_NAME}.swift << EOF import Foundation import UIKit

@objc(Dotnet${BINDING_NAME}) public class Dotnet${BINDING_NAME}: NSObject {

@objc(initialize)
public static func initialize() {
    print("${BINDING_NAME} initialized")
}

@objc(getVersion)
public static func getVersion() -> String {
    return "1.0.0"
}

} EOF

Create Info.plist

cat > macios/native/${BINDING_NAME}/${BINDING_NAME}/Info.plist << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>$(DEVELOPMENT_LANGUAGE)</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <key>CFBundleShortVersionString</key> <string>$(MARKETING_VERSION)</string> <key>CFBundleVersion</key> <string>$(CURRENT_PROJECT_VERSION)</string> </dict> </plist> EOF

Generate Xcode project

cd macios/native/${BINDING_NAME} xcodegen generate cd ../../..

Create binding .csproj

cat > macios/${BINDING_NAME}.MaciOS.Binding/${BINDING_NAME}.MaciOS.Binding.csproj << EOF <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>net9.0-ios;net9.0-maccatalyst</TargetFrameworks> <Nullable>enable</Nullable> <ImplicitUsings>true</ImplicitUsings> <IsBindingProject>true</IsBindingProject> <PackageId>${BINDING_NAME}.MaciOS</PackageId> <Version>1.0.0</Version> </PropertyGroup>

<ItemGroup> <XcodeProject Include="../native/${BINDING_NAME}/${BINDING_NAME}.xcodeproj"> <SchemeName>${BINDING_NAME}</SchemeName> </XcodeProject> </ItemGroup>

<ItemGroup> <ObjcBindingApiDefinition Include="ApiDefinition.cs" /> </ItemGroup> </Project> EOF

Create ApiDefinition.cs

cat > macios/${BINDING_NAME}.MaciOS.Binding/ApiDefinition.cs << EOF using System; using Foundation; using ObjCRuntime; using UIKit;

namespace ${BINDING_NAME} { [BaseType(typeof(NSObject))] interface Dotnet${BINDING_NAME} { [Static] [Export("initialize")] void Initialize();

    [Static]
    [Export("getVersion")]
    string GetVersion();
}

} EOF

echo "" echo "✅ Created ${BINDING_NAME} binding project!" echo "" echo "Structure:" find . -type f -name ".swift" -o -name ".cs" -o -name "*.csproj" -o -name "project.yml" | sort echo "" echo "Next steps:" echo " 1. cd ${BINDING_NAME}/macios/${BINDING_NAME}.MaciOS.Binding" echo " 2. dotnet build" echo " 3. Add your native library code to Dotnet${BINDING_NAME}.swift" echo " 4. Update ApiDefinition.cs to match your Swift API"

Save as create-ios-binding.sh and run:

chmod +x create-ios-binding.sh ./create-ios-binding.sh MyAwesomeBinding com.mycompany 15.0

Alternative: Create Xcode Project Manually (Without XcodeGen)

If you prefer not to use XcodeGen, you can create a minimal Xcode project using plutil and direct file creation. However, this is more complex and error-prone.

Using Swift Package as Alternative

For simpler cases, you can use Swift Package Manager instead of an Xcode project:

cd macios/native mkdir ${BINDING_NAME} cd ${BINDING_NAME}

Initialize Swift package

swift package init --type library --name ${BINDING_NAME}

The binding project can reference the Package.swift

Then update the binding .csproj to use <XcodeProject> pointing to the directory containing Package.swift .

Note: The <XcodeProject> MSBuild item supports both .xcodeproj and Swift Package directories.

Step 5: Add Native Library Dependencies

Choose the appropriate method for your library's distribution:

Option A: CocoaPods

Create macios/native/MyBinding/Podfile :

platform :ios, '15.0'

target 'MyBinding' do use_frameworks! :linkage => :static

Add your native library pod

pod 'FirebaseMessaging', '~> 10.0'

Add other dependencies as needed

pod 'FirebaseCore' end

post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' end end end

Install dependencies:

cd macios/native/MyBinding pod install

After this, open MyBinding.xcworkspace instead of .xcodeproj

Option B: Swift Package Manager

In Xcode:

  • File → Add Package Dependencies

  • Enter the package repository URL

  • Select version rules

  • Add to your target

Or create Package.swift :

// swift-tools-version:5.9 import PackageDescription

let package = Package( name: "MyBinding", platforms: [.iOS(.v15), .macCatalyst(.v15)], products: [ .library(name: "MyBinding", type: .static, targets: ["MyBinding"]) ], dependencies: [ .package(url: "https://github.com/example/SomeLibrary.git", from: "1.0.0") ], targets: [ .target( name: "MyBinding", dependencies: [ .product(name: "SomeLibrary", package: "SomeLibrary") ] ) ] )

Option C: Manual XCFramework

  • Drag the .xcframework into the Xcode project

  • Ensure "Copy items if needed" is checked

  • Add to target's "Frameworks and Libraries" section

  • Set "Embed" to Do Not Embed (for static linking)

Step 6: Implement the Swift Wrapper

Create macios/native/MyBinding/MyBinding/DotnetMyBinding.swift :

import Foundation import UIKit import TheNativeLibrary // Import your native library

/// Main binding class exposed to .NET /// The @objc attribute with explicit name ensures stable Objective-C naming @objc(DotnetMyBinding) public class DotnetMyBinding: NSObject {

// MARK: - Initialization

/// Initialize the native library
/// Call this from your .NET app's startup (e.g., MauiProgram.cs)
@objc(initializeWithApiKey:)
public static func initialize(apiKey: String) {
    TheNativeLibrary.configure(withApiKey: apiKey)
}

/// Check if the library is initialized
@objc(isInitialized)
public static func isInitialized() -> Bool {
    return TheNativeLibrary.isConfigured
}

// MARK: - Synchronous Methods

/// Get a simple value from the native library
@objc(getVersion)
public static func getVersion() -> String {
    return TheNativeLibrary.version
}

/// Process data and return result
@objc(processDataWithInput:)
public static func processData(input: String) -> String? {
    guard let result = TheNativeLibrary.process(input) else {
        return nil
    }
    return result.stringValue
}

// MARK: - Asynchronous Methods (Completion Handlers)

/// Perform async operation with completion handler
/// .NET can use [Async] attribute to generate async/await version
@objc(fetchDataWithQuery:completion:)
public static func fetchData(
    query: String,
    completion: @escaping (String?, NSError?) -> Void
) {
    TheNativeLibrary.fetch(query: query) { result in
        switch result {
        case .success(let data):
            completion(data.stringValue, nil)
        case .failure(let error):
            completion(nil, error as NSError)
        }
    }
}

/// Async method with complex result data
@objc(performOperationWithConfig:completion:)
public static func performOperation(
    config: NSDictionary,
    completion: @escaping (NSData?, NSError?) -> Void
) {
    guard let configDict = config as? [String: Any] else {
        let error = NSError(
            domain: "DotnetMyBinding",
            code: -1,
            userInfo: [NSLocalizedDescriptionKey: "Invalid configuration"]
        )
        completion(nil, error)
        return
    }
    
    TheNativeLibrary.performOperation(config: configDict) { result in
        switch result {
        case .success(let data):
            completion(data, nil)
        case .failure(let error):
            completion(nil, error as NSError)
        }
    }
}

// MARK: - View Creation

/// Create a native view to embed in .NET MAUI
/// Return UIView for cross-platform compatibility
@objc(createViewWithFrame:)
public static func createView(frame: CGRect) -> UIView {
    let nativeView = TheNativeLibrary.createCustomView()
    nativeView.frame = frame
    return nativeView
}

/// Create a configured view with options
@objc(createViewWithFrame:options:)
public static func createView(frame: CGRect, options: NSDictionary) -> UIView {
    let config = options as? [String: Any] ?? [:]
    let nativeView = TheNativeLibrary.createCustomView(options: config)
    nativeView.frame = frame
    return nativeView
}

// MARK: - Delegate/Callback Pattern

private static var callbackHandler: ((String) -> Void)?

/// Register a callback for events
/// .NET will pass an Action&#x3C;string> that gets invoked
@objc(registerCallbackWithHandler:)
public static func registerCallback(handler: @escaping (String) -> Void) {
    callbackHandler = handler
    TheNativeLibrary.setEventHandler { event in
        callbackHandler?(event.description)
    }
}

/// Unregister the callback
@objc(unregisterCallback)
public static func unregisterCallback() {
    callbackHandler = nil
    TheNativeLibrary.setEventHandler(nil)
}

}

Swift Wrapper Design Guidelines

Type Mapping Rules

Only use types that .NET already knows how to marshal:

Swift Type Objective-C Type C# Type

String

NSString *

string

Bool

BOOL

bool

Int , Int32

int

int

Int64

long long

long

Double

double

double

Float

float

float

Data

NSData *

NSData

[String: Any]

NSDictionary *

NSDictionary

[Any]

NSArray *

NSArray

UIView

UIView *

UIView

UIImage

UIImage *

UIImage

URL

NSURL *

NSUrl

Custom Class Must inherit NSObject

Interface with [BaseType]

Required Annotations

// Class: Must be public and have @objc with explicit name @objc(ClassName) public class ClassName: NSObject {

// Method: Must be public with @objc selector
@objc(methodNameWithParam:anotherParam:)
public func methodName(param: String, anotherParam: Int) -> Bool {
    // Implementation
}

// Static method
@objc(staticMethodWithValue:)
public static func staticMethod(value: String) -> String {
    // Implementation
}

// Property (read-only)
@objc(propertyName)
public var propertyName: String {
    return "value"
}

// Property (read-write)
@objc
public var readWriteProperty: String = ""

}

Completion Handler Pattern

For async operations, use completion handlers that .NET can convert to async /await :

// Swift @objc(operationWithInput:completion:) public static func operation( input: String, completion: @escaping (String?, NSError?) -> Void // Result, Error ) { // Async work... DispatchQueue.main.async { completion(result, nil) // Success // OR completion(nil, error as NSError) // Failure } }

// C# ApiDefinition.cs - Add [Async] for automatic async wrapper [Static] [Export("operationWithInput:completion:")] [Async] void Operation(string input, Action<string?, NSError?> completion);

// Usage in C# var result = await DotnetMyBinding.OperationAsync("input");

Error Handling Pattern

Always convert errors to NSError for proper propagation:

@objc(riskyOperationWithCompletion:) public static func riskyOperation(completion: @escaping (Bool, NSError?) -> Void) { do { try TheNativeLibrary.riskyOperation() completion(true, nil) } catch { let nsError = NSError( domain: "DotnetMyBinding", code: (error as NSError).code, userInfo: [ NSLocalizedDescriptionKey: error.localizedDescription, NSUnderlyingErrorKey: error ] ) completion(false, nsError) } }

Step 7: Create the C# Binding Project (If Not Using Script)

If you created the project using the script in Step 1-4, skip to Step 8.

Create macios/MyBinding.MaciOS.Binding/MyBinding.MaciOS.Binding.csproj :

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>net9.0-ios;net9.0-maccatalyst</TargetFrameworks> <Nullable>enable</Nullable> <ImplicitUsings>true</ImplicitUsings> <IsBindingProject>true</IsBindingProject>

&#x3C;!-- Optional: Package metadata for NuGet -->
&#x3C;PackageId>MyBinding.MaciOS&#x3C;/PackageId>
&#x3C;Version>1.0.0&#x3C;/Version>
&#x3C;Authors>Your Name&#x3C;/Authors>
&#x3C;Description>iOS bindings for MyLibrary&#x3C;/Description>

</PropertyGroup>

<!-- Reference the Xcode project - MSBuild will build it automatically --> <ItemGroup> <XcodeProject Include="../native/MyBinding/MyBinding.xcodeproj"> <SchemeName>MyBinding</SchemeName> <!-- Optional overrides --> <!-- <Configuration>Release</Configuration> --> <!-- <Kind>Framework</Kind> --> <!-- <SmartLink>true</SmartLink> --> </XcodeProject> </ItemGroup>

<!-- If using xcworkspace (CocoaPods), reference it instead --> <!-- <ItemGroup> <XcodeProject Include="../native/MyBinding/MyBinding.xcworkspace"> <SchemeName>MyBinding</SchemeName> </XcodeProject> </ItemGroup> -->

<!-- API definition file --> <ItemGroup> <ObjcBindingApiDefinition Include="ApiDefinition.cs" /> </ItemGroup> </Project>

XcodeProject Properties

Property Description Default

SchemeName

Xcode scheme to build Required

Configuration

Build configuration Release

Kind

Framework or Static

Auto-detected

SmartLink

Enable smart linking true

ForceLoad

Force load all symbols false

Step 8: Build and Generate API Definition

Initial Build

Build the binding project to compile the native framework:

cd macios/MyBinding.MaciOS.Binding dotnet build

This creates the xcframework at:

bin/Debug/net9.0-ios/MyBinding.MaciOS.Binding.resources/MyBindingiOS.xcframework/

Locate the Generated Swift Header

After building, find the generated Objective-C header:

Find the Swift header

find bin -name "*-Swift.h" -type f

Typical location:

bin/Debug/net9.0-ios/MyBinding.MaciOS.Binding.resources/

MyBindingiOS.xcframework/ios-arm64/MyBinding.framework/Headers/MyBinding-Swift.h

Generate ApiDefinition.cs with Objective Sharpie

Install Objective Sharpie if not already installed:

brew install --cask objectivesharpie

Check available iOS SDKs:

sharpie xcode -sdks

Generate bindings:

Set variables for clarity

HEADER_PATH="bin/Debug/net9.0-ios/MyBinding.MaciOS.Binding.resources/MyBindingiOS.xcframework/ios-arm64/MyBinding.framework/Headers/MyBinding-Swift.h" SDK_VERSION="iphoneos18.0" # Use your installed SDK version NAMESPACE="MyBinding"

sharpie bind
--output=sharpie-output
--namespace=$NAMESPACE
--sdk=$SDK_VERSION
--scope=Headers
"$HEADER_PATH"

Review and Clean Up Generated Code

The generated ApiDefinition.cs requires cleanup:

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

namespace MyBinding { // @interface DotnetMyBinding : NSObject [BaseType(typeof(NSObject))] interface DotnetMyBinding { // +(void)initializeWithApiKey:(NSString * _Nonnull)apiKey; [Static] [Export("initializeWithApiKey:")] void Initialize(string apiKey);

    // +(BOOL)isInitialized;
    [Static]
    [Export("isInitialized")]
    bool IsInitialized { get; }

    // +(NSString * _Nonnull)getVersion;
    [Static]
    [Export("getVersion")]
    string GetVersion();

    // +(NSString * _Nullable)processDataWithInput:(NSString * _Nonnull)input;
    [Static]
    [Export("processDataWithInput:")]
    [return: NullAllowed]
    string ProcessData(string input);

    // +(void)fetchDataWithQuery:(NSString * _Nonnull)query 
    //                completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion;
    [Static]
    [Export("fetchDataWithQuery:completion:")]
    [Async]  // Generates FetchDataAsync method
    void FetchData(string query, Action&#x3C;string?, NSError?> completion);

    // +(void)performOperationWithConfig:(NSDictionary * _Nonnull)config 
    //                        completion:(void (^ _Nonnull)(NSData * _Nullable, NSError * _Nullable))completion;
    [Static]
    [Export("performOperationWithConfig:completion:")]
    [Async]
    void PerformOperation(NSDictionary config, Action&#x3C;NSData?, NSError?> completion);

    // +(UIView * _Nonnull)createViewWithFrame:(CGRect)frame;
    [Static]
    [Export("createViewWithFrame:")]
    UIView CreateView(CGRect frame);

    // +(UIView * _Nonnull)createViewWithFrame:(CGRect)frame options:(NSDictionary * _Nonnull)options;
    [Static]
    [Export("createViewWithFrame:options:")]
    UIView CreateView(CGRect frame, NSDictionary options);

    // +(void)registerCallbackWithHandler:(void (^ _Nonnull)(NSString * _Nonnull))handler;
    [Static]
    [Export("registerCallbackWithHandler:")]
    void RegisterCallback(Action&#x3C;string> handler);

    // +(void)unregisterCallback;
    [Static]
    [Export("unregisterCallback")]
    void UnregisterCallback();
}

}

Common Cleanup Tasks

Issue Solution

Missing namespace Add namespace MyBinding { ... }

[Verify] attributes Review each, remove after confirming correctness

InitWithCoder constructors Remove - conflicts with linker

Protocol type mismatches Use interface types (e.g., ICAAnimation )

Missing [NullAllowed]

Add for nullable parameters/returns

Completion handlers Add [Async] attribute for async generation

Step 9: Build the Final Binding

cd macios/MyBinding.MaciOS.Binding dotnet build -c Release

Verify the output:

ls -la bin/Release/net9.0-ios/

Should contain: MyBinding.MaciOS.Binding.dll and resources

Step 10: Use in Your MAUI App

Add Project Reference

In your MAUI app's .csproj :

<ItemGroup Condition="$(TargetFramework.Contains('ios')) Or $(TargetFramework.Contains('maccatalyst'))"> <ProjectReference Include="....\macios\MyBinding.MaciOS.Binding\MyBinding.MaciOS.Binding.csproj" /> </ItemGroup>

Initialize in MauiProgram.cs

using Microsoft.Maui.Hosting;

#if IOS || MACCATALYST using MyBinding; #endif

public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder.UseMauiApp<App>();

#if IOS || MACCATALYST // Initialize the native library DotnetMyBinding.Initialize("your-api-key"); #endif

    return builder.Build();
}

}

Use Async APIs

#if IOS || MACCATALYST using MyBinding; #endif

public partial class MainPage : ContentPage { private async void OnFetchClicked(object sender, EventArgs e) { #if IOS || MACCATALYST try { // Using the async version generated by [Async] attribute var result = await DotnetMyBinding.FetchDataAsync("my query"); await DisplayAlert("Success", result ?? "No data", "OK"); } catch (NSErrorException ex) { await DisplayAlert("Error", ex.Error.LocalizedDescription, "OK"); } #endif }

private void OnCreateViewClicked(object sender, EventArgs e)
{

#if IOS || MACCATALYST var nativeView = DotnetMyBinding.CreateView(new CoreGraphics.CGRect(0, 0, 300, 200));

    // Add to a MAUI view using a custom handler or platform view
    // This requires additional platform-specific integration

#endif } }

Register Callbacks

#if IOS || MACCATALYST protected override void OnAppearing() { base.OnAppearing(); DotnetMyBinding.RegisterCallback((message) => { MainThread.BeginInvokeOnMainThread(() => { StatusLabel.Text = $"Event: {message}"; }); }); }

protected override void OnDisappearing() { base.OnDisappearing(); DotnetMyBinding.UnregisterCallback(); } #endif

Updating Bindings When Native SDK Changes

Step-by-step Update Process

  1. Update Native Dependency Version

CocoaPods:

Podfile

pod 'FirebaseMessaging', '~> 11.0' # Updated version

cd macios/native/MyBinding pod update

Swift Package Manager: Update version in Xcode's Package Dependencies or Package.swift

Manual XCFramework: Replace the xcframework file with the new version

  1. Update Swift Wrapper (If Needed)

Review release notes for the native library and update DotnetMyBinding.swift :

  • Add new methods for new APIs

  • Update method signatures for changed APIs

  • Remove deprecated API wrappers

  • Handle any breaking changes

  1. Regenerate API Definition

Clean and rebuild

cd macios/MyBinding.MaciOS.Binding dotnet clean dotnet build

Regenerate Objective Sharpie output

sharpie bind
--output=sharpie-output-new
--namespace=MyBinding
--sdk=iphoneos18.0
--scope=Headers
"bin/Debug/net9.0-ios/MyBinding.MaciOS.Binding.resources/MyBindingiOS.xcframework/ios-arm64/MyBinding.framework/Headers/MyBinding-Swift.h"

  1. Diff and Merge Changes

Compare the new Sharpie output with existing ApiDefinition.cs :

diff ApiDefinition.cs sharpie-output-new/ApiDefinitions.cs

Manually merge:

  • Add new method bindings

  • Update changed signatures

  • Remove deleted methods

  • Preserve custom attributes ([Async] , [NullAllowed] , etc.)

  1. Test the Updated Bindings

dotnet build -c Release dotnet test # If you have unit tests

Run the sample app to verify functionality.

Troubleshooting

Build Errors

"Framework not found" / "Library not found"

Causes & Solutions:

  • XCFramework path incorrect - Verify the path in <XcodeProject> or <NativeReference>

  • Missing architectures - Ensure xcframework includes arm64 (device) and arm64/x86_64 (simulator)

  • CocoaPods not installed - Run pod install in the native directory

Verify xcframework architectures

lipo -info path/to/Framework.framework/Framework

"Undefined symbols for architecture"

Causes & Solutions:

  • Missing linked frameworks - Add system frameworks to Xcode project's "Link Binary with Libraries"

  • Static vs Dynamic mismatch - Ensure consistent linkage (all static or all dynamic)

  • Symbol visibility - Verify Swift classes/methods are public and have @objc

<!-- Force load symbols if needed --> <XcodeProject Include="..."> <SchemeName>MyBinding</SchemeName> <ForceLoad>true</ForceLoad> <SmartLink>false</SmartLink> </XcodeProject>

"No type or protocol named..."

Causes & Solutions:

  • Missing import - Add required imports to ApiDefinition.cs (using UIKit; , etc.)

  • Protocol vs Interface - Use interface types (ICAAnimation not CAAnimation )

  • Namespace mismatch - Verify namespace matches between wrapper and binding

"Duplicate symbol" / "Symbol already defined"

Causes & Solutions:

  • Multiple references to same framework - Check for duplicate <NativeReference> entries

  • Conflicting dependency versions - Resolve CocoaPods/SPM version conflicts

  • InitWithCoder constructor - Remove from ApiDefinition.cs (auto-generated by linker)

Objective Sharpie Errors

"Unable to find SDK":

List available SDKs

sharpie xcode -sdks

Update Xcode command line tools

xcode-select --install sudo xcode-select -s /Applications/Xcode.app

"Parse error in header":

  • Header may use features Sharpie doesn't support

  • Simplify the Swift wrapper to use basic types

  • Use --scope=Headers to limit parsing

Runtime Errors

"Native class hasn't been loaded"

Causes & Solutions:

  • Framework not embedded - Check that native resources are included in app bundle

  • Static library not linked - Verify <ForceLoad>true</ForceLoad> is set

  • Missing Objective-C class registration - Ensure @objc(ClassName) annotation is present

"unrecognized selector sent to instance"

Causes & Solutions:

  • Selector mismatch - Verify [Export("selector:")] matches Swift @objc(selector:) exactly

  • Method signature mismatch - Check parameter count and types match

  • Static vs instance method - Ensure [Static] attribute is correct

"Library not loaded: @rpath/..."

Causes & Solutions:

  • Swift runtime missing - Add linker flags for Swift libraries

  • Framework not embedded - Set "Embed & Sign" in Xcode for dynamic frameworks

  • rpath not set - Add -Wl,-rpath -Wl,@executable_path/Frameworks

Callbacks Not Working

Causes & Solutions:

  • Callback on wrong thread - Use DispatchQueue.main.async in Swift for UI updates

  • Callback garbage collected - Store strong reference to callback handler

  • Missing @escaping

  • Completion handlers must be @escaping in Swift

IntelliSense Issues

IntelliSense shows errors but project compiles: This is expected behavior. Binding projects don't use source generators. The solution:

  • Build the binding project first

  • Reload the solution/project

  • IntelliSense may still show errors - trust the compiler

Quick Reference

ApiDefinition Attributes

Attribute Purpose Example

[BaseType(typeof(NSObject))]

Specifies base class [BaseType(typeof(UIView))]

[Static]

Static method/property [Static] [Export("shared")]

[Export("selector:")]

Objective-C selector [Export("doSomethingWithValue:")]

[Async]

Generate async wrapper On completion handler methods

[NullAllowed]

Nullable parameter/return [return: NullAllowed]

[Protocol]

Objective-C protocol [Protocol] interface IMyDelegate

[Model]

Protocol implementation Combined with [Protocol]

[Abstract]

Required protocol method In protocol interface

[Internal]

Don't expose publicly Hide helper methods

[Wrap("...")]

Wrap with helper Strongly-typed helpers

[Sealed]

Prevent subclassing On final classes

XcodeProject MSBuild Properties

<XcodeProject Include="path/to/Project.xcodeproj"> <SchemeName>MyScheme</SchemeName> <!-- Required: Xcode scheme --> <Configuration>Release</Configuration> <!-- Build configuration --> <Kind>Framework</Kind> <!-- Framework or Static --> <SmartLink>true</SmartLink> <!-- Enable smart linking --> <ForceLoad>false</ForceLoad> <!-- Force load all symbols --> </XcodeProject>

NativeReference MSBuild Properties (Traditional Bindings)

<NativeReference Include="Library.xcframework"> <Kind>Framework</Kind> <!-- Framework or Static --> <Frameworks>Foundation UIKit</Frameworks> <!-- Required Apple frameworks --> <LinkerFlags>-lsqlite3</LinkerFlags> <!-- Additional linker flags --> <SmartLink>true</SmartLink> <!-- Enable smart linking --> <ForceLoad>false</ForceLoad> <!-- Force load all symbols --> <IsCxx>false</IsCxx> <!-- C++ library --> </NativeReference>

Common Swift-to-C# Type Mappings

// Swift // C# ApiDefinition String string String? [NullAllowed] string Bool bool Int / Int32 nint / int Int64 long Double double Float float Data NSData [String: Any] NSDictionary [Any] NSArray URL NSUrl Date NSDate UIView UIView UIImage UIImage CGRect CGRect CGPoint CGPoint CGSize CGSize (Result, Error?) -> Void Action<Result?, NSError?>

Resources

Official Documentation

  • Native Library Interop - .NET Community Toolkit

  • iOS Binding Project Migration

  • Binding Objective-C Libraries

  • Objective Sharpie

Tools

  • XcodeGen - Generate Xcode projects from YAML specification

Templates and Examples

  • Maui.NativeLibraryInterop Repository

  • CommunityToolkit.Maui Bindings

Related Skills

  • See docs/ios-bindings-guide.md for comprehensive reference

  • See docs/android-bindings-guide.md for Android bindings

Appendix A: Using the Community Toolkit Template (Alternative)

If you prefer to start from an existing template rather than creating from scratch:

git clone https://github.com/CommunityToolkit/Maui.NativeLibraryInterop cp -r Maui.NativeLibraryInterop/template ./MyBinding cd MyBinding

Rename files and update references

find . -name "NewBinding" -exec bash -c 'mv "$0" "${0//NewBinding/MyBinding}"' {} ; find . -type f ( -name ".cs" -o -name ".csproj" -o -name ".swift" -o -name ".yml" ) | xargs sed -i '' 's/NewBinding/MyBinding/g'

The template includes pre-configured:

  • Xcode project with correct build settings

  • Binding .csproj with XcodeProject reference

  • Sample MAUI app

  • GitHub Actions CI/CD workflows

Output Format

When assisting with iOS slim bindings, provide:

  • Project structure - File/folder layout for the binding

  • Swift wrapper code - Complete DotnetMyBinding.swift implementation

  • Xcode configuration - Build settings and dependency setup

  • C# binding project - .csproj and ApiDefinition.cs files

  • Usage examples - How to call the binding from MAUI/C#

  • Troubleshooting guidance - Common issues and solutions for the specific library

Always verify:

  • Swift classes have @objc(ClassName) annotations

  • Methods have @objc(selector:) annotations matching Objective-C conventions

  • Types are marshallable between Swift and C#

  • Async operations use completion handlers with [Async] attribute

  • Error handling uses NSError for proper propagation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

maui-release-notes

No summary provided by upstream source.

Repository SourceNeeds Review
General

android-slim-bindings

No summary provided by upstream source.

Repository SourceNeeds Review
General

PayPilot by AGMS

Process payments, send invoices, issue refunds, manage subscriptions, and detect fraud via a secure payment gateway proxy. Use when a user asks to charge som...

Registry SourceRecently Updated
General

Poem Generator

诗歌生成助手。现代诗、俳句、对联、藏头诗、古诗翻译。Poem generator with free verse, haiku, couplets, acrostic poems, classical poetry translation. Use when you need poem generator cap...

Registry SourceRecently Updated