.NET Binding Sample App
Create sample Mac Catalyst or iOS apps to exercise and verify .NET binding project APIs at runtime.
When to Use This Skill
- •After creating binding projects for xcframeworks, to verify they work
- •To create demo/sample apps showcasing bound APIs
- •To debug runtime failures in bindings (selector not found, init crashes, etc.)
Quick Start
1. Create the App Project
# Mac Catalyst app (runs on macOS, uses iOS APIs) dotnet new maccatalyst -n TestApp -o TestApp # Or iOS app dotnet new ios -n TestApp -o TestApp
2. Reference Binding Projects
Edit TestApp.csproj to add project references to your binding projects:
<ItemGroup> <ProjectReference Include="../MyFramework.Binding/MyFramework.Binding.csproj" /> </ItemGroup>
3. Add Native Framework References
Critical: Native xcframework references from binding projects do NOT automatically flow to consuming app projects through ProjectReference. You must add them directly:
<ItemGroup>
<NativeReference Include="../../Frameworks/MyFramework.xcframework">
<Kind>Framework</Kind>
<SmartLink>True</SmartLink>
</NativeReference>
</ItemGroup>
Add one <NativeReference> for each xcframework your bindings depend on, including transitive dependencies.
4. Write API Exercise Code
Put test code in SceneDelegate.cs (Mac Catalyst) or AppDelegate.cs (iOS). Use the WillConnect or FinishedLaunching method:
using MyFramework;
[Export ("scene:willConnectToSession:options:")]
public void WillConnect (UIScene scene, UISceneSession session,
UISceneConnectionOptions connectionOptions)
{
// Test API
var result = MyClass.SharedInstance;
Console.WriteLine ($"✅ MyFramework: {result}");
}
5. Build and Run
# Build dotnet build TestApp/TestApp.csproj # Run (Mac Catalyst) open TestApp/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/TestApp.app # Or run directly to see Console.WriteLine output ./TestApp/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/TestApp.app/Contents/MacOS/TestApp
Note: dotnet run does NOT launch GUI apps. Use open or run the binary directly.
Writing Test Code
Testing Patterns
Structure tests to exercise different API surface areas:
void RunAllTests ()
{
TestEnums ();
TestConstants ();
TestStaticProperties ();
TestObjectCreation ();
TestMethods ();
TestDelegates ();
TestNotifications ();
}
void TestEnums ()
{
// Verify enum values resolve correctly
var value = MyEnum.OptionA;
Console.WriteLine ($"✅ Enum: {value} = {(int)value}");
}
void TestConstants ()
{
// Verify [Field] constants load without linker errors
var domain = MyConstants.ErrorDomain;
Console.WriteLine ($"✅ Constant: {domain}");
}
void TestStaticProperties ()
{
// Verify static properties return non-null
var version = Settings.SdkVersion;
Console.WriteLine ($"✅ Version: {version}");
}
void TestObjectCreation ()
{
// Test constructors — use parameterized constructors for Swift classes
var obj = new MyClass ("test");
Console.WriteLine ($"✅ Created: {obj.Name}");
}
void TestMethods ()
{
// Test instance methods
var obj = new MyClass ("test");
obj.Configure (new NSDictionary ());
Console.WriteLine ("✅ Method call succeeded");
}
What to Test for Each Framework
For each binding project, test at minimum:
- •One enum value — verifies
StructsAndEnums.cscompiles and links - •One
[Field]constant — verifies library name is correct (not"__Internal") - •One static property or method — verifies class resolution at runtime
- •One object instantiation — verifies constructor binding works
- •One instance method/property — verifies selector names are correct
Result Reporting Pattern
void Test (string framework, string test, Action action)
{
try {
action ();
Console.WriteLine ($"✅ {framework}: {test}");
} catch (Exception ex) {
Console.WriteLine ($"❌ {framework}: {test} — {ex.Message}");
}
}
Common Runtime Errors and Fixes
Selector not found / unrecognized selector sent to instance
The ObjC selector string in [Export ("...")] doesn't match the actual method. Check:
- •Missing colons (
:) for parameter separators - •Capitalization mismatches
- •Method was renamed in Swift-generated header vs actual implementation
unrecognized selector sent to class (class methods)
Swift factory class methods (e.g., +[MyClass classWithName:]) may not exist in the ObjC runtime even though they appear in the -Swift.h header. Use initWith* constructors instead.
Fatal error: Use of unimplemented initializer 'init()'
The Swift class doesn't support default initialization. The binding must have [DisableDefaultCtor]. Fix the binding and rebuild, then use a parameterized constructor in your test:
// ❌ Crashes
var obj = new SwiftClass ();
// ✅ Use parameterized constructor
var obj = new SwiftClass ("value");
Undefined symbol _OBJC_CLASS_$_ClassName (linker error)
The native xcframework isn't linked. Add <NativeReference> directly to the app project .csproj.
Undefined symbol _ConstantName (linker error)
The [Field] attribute uses wrong library name. Change from "__Internal" to the framework name:
[Field ("kMyConstant", "MyFramework")] // ✅ framework name
Could not create an native instance
The binding constructor isn't matching a real ObjC/Swift initializer. Verify the selector in [Export] matches the header exactly.
Project Structure
For a multi-framework binding validation:
MySdk/ ├── Framework1.Binding/ │ ├── Framework1.Binding.csproj │ ├── ApiDefinition.cs │ └── StructsAndEnums.cs ├── Framework2.Binding/ │ ├── Framework2.Binding.csproj │ ├── ApiDefinition.cs │ └── StructsAndEnums.cs ├── TestApp/ │ ├── TestApp.csproj # References all binding projects + xcframeworks │ ├── AppDelegate.cs │ ├── SceneDelegate.cs # Main test code here │ └── Info.plist ├── Frameworks/ │ ├── Framework1.xcframework │ └── Framework2.xcframework └── MySdk.slnx
Multi-TFM Considerations
Binding projects should target both iOS and Mac Catalyst:
<TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
The test app targets one platform:
<!-- Mac Catalyst --> <TargetFramework>net10.0-maccatalyst</TargetFramework> <!-- Or iOS --> <TargetFramework>net10.0-ios</TargetFramework>
Choose the target framework matching your installed .NET workloads and Xcode version:
- •Xcode 26+: requires
net10.0-*workloads - •Xcode 15/16: use
net9.0-*workloads (legacy)
Mac Catalyst App Template (SceneDelegate pattern)
Complete SceneDelegate.cs template for a Mac Catalyst test app:
using Foundation;
using UIKit;
namespace TestApp;
[Register ("SceneDelegate")]
public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
{
[Export ("window")]
public UIWindow? Window { get; set; }
[Export ("scene:willConnectToSession:options:")]
public void WillConnect (UIScene scene, UISceneSession session,
UISceneConnectionOptions connectionOptions)
{
var windowScene = scene as UIWindowScene;
if (windowScene == null) return;
Window = new UIWindow (windowScene);
var vc = new UIViewController ();
vc.View!.BackgroundColor = UIColor.SystemBackground;
var results = new List<string> ();
// Run tests and collect results
try {
// ADD YOUR TESTS HERE
results.Add ("✅ Test passed");
} catch (Exception ex) {
results.Add ($"❌ Test failed: {ex.Message}");
}
// Display results
var label = new UILabel (vc.View.Bounds) {
Text = string.Join ("\n", results),
Lines = 0,
Font = UIFont.GetMonospacedSystemFont (14, UIFontWeight.Regular),
TextAlignment = UITextAlignment.Left,
AutoresizingMask = UIViewAutoresizing.All,
};
vc.View.AddSubview (label);
// Also print to console
foreach (var r in results)
Console.WriteLine (r);
Window.RootViewController = vc;
Window.MakeKeyAndVisible ();
}
}
References
- •references/test-patterns.md — Detailed patterns for exercising different binding types (enums, delegates, notifications, blocks, async)