Creating iOS Widgets with MAUI β Part 1: Interoperability
Microsoft .NET is a great platform that allows you to do many different things with the same tech stack, from game development, machine learning, websites to mobile applications. .NET 10 is the latest version released just a few days ago. Do check it out.
MAUI (Multi-platform App UI) lets you build native applications across Windows/macOS/Linux/iOS/Android with a reusable codebase. However it has some limitations (e.g. accessing platform specific hardware), depending on what you need to do.
This is a series of recordings on how I create an iOS widget that works with MAUI. It took me a few days to figure out the tricks and articles about this topic is limited. This is the first post of the series and hope it helps someone working on the MAUI. In this post we will talk about the pre-requisite: Interoperability.
Disclaimer: I am just sharing my personal experience. I do not guarantee the information in this post is 100% correct or accurate. I am not, by any means, expressing that I am an expert in mobile development.
Why do we need interoperability?
This is because there are APIs that MAUI SDK doesnβt provide. e.g. WidgetKit. You can embed a widget into your app, but it is pointless if you cannot control it. Therefore in order to control something that is not available from the MAUI SDK, you will need some workaround for it.
In short, that means finding a way to call Swift code from C#.
In this post we will try to call Swift NSLog() from our MAUI C# code, which simply prints βHello Worldβ in the console.

Pre-requisites
- .NET 10 SDK
- Xcode 26.1
Create a MAUI project.
Run dotnet new maui, then dotnet restore.
If this is the first time you build a MAUI project, you will be asked to run dotnet workload restore with elevated privilege.
Open your new project .csproj file, at the top remove all values from TargetFramework tag except net10.0-ios. This is because we donβt need to support other platforms for this task. Do the same for SupportedOSPlatformVersion and TargetPlatformMinVersion tags.
Your project file should look like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0-ios</TargetFrameworks>
<OutputType>Exe</OutputType>
<RootNamespace>test</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Display name -->
<ApplicationTitle>test</ApplicationTitle>
<!-- App Identifier -->
<ApplicationId>com.companyname.test</ApplicationId>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Essentials" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0" />
</ItemGroup>
</Project>
Create and export a C interface from Xcode.
Open Xcode. From File β New β Project, choose iOS β Framework & Library β Static Library.
Name the product HelloWorldBridge, leave everything else as defaults.
You will have a solution with a single project that having a single file created. The only source file HelloWorldBridge.swift is an empty class.

Remove everything existing from the source file and replace with:
import Foundation
@_cdecl("HelloWorldBridge_SayHello")
public func HelloWorldBridge_SayHello() {
NSLog("π Hello World from Swift (Static Library)!")
}
The @_cdecl directive means this function will be exported as C-ABI (Application Binary Interface), which will be imported into our MAUI app. Even LLMs suggested different ways of doing interoperability and contradicting each other, this approach is what I found the most stable. This has also been verified with very recent MAUI samples repo update by Microsoft folks here.
From the top of Xcode, select βAny iOS Simulator Device (arm64, x86_64)β as build target. Select Product β Build or pressing (Cmd+B) to build it.

Select from menu Product β Show Build Folder in Finder, finder will show your project Build folder, which is the built artefacts will be placed.
The build folder path will usually be: $(Home)/Library/Developer/Xcode/DerivedData/{YourProjectName}-{RandomProjectId}/Build.

Inside Products > Debug-iphonesimulator, you should be able to see your built static library archive libHelloWorldBridge.a.

- Integration
Copy libHelloWorldBridge.a to your MAUI projectβs Platforms/iOS/Frameworks folder (You can place it to Platforms/iOS as well, just my preference).
Create a file named HelloWorldBridge.cs inside Platforms/iOS with content below:
#if IOS
using System.Runtime.InteropServices;
public static class HelloWorldBridge
{
[DllImport("__Internal", EntryPoint = "HelloWorldBridge_SayHello")]
private static extern void _SayHelloStaticLib();
public static void SayHelloStaticLib() => _SayHelloStaticLib();
}
#endif
The #if and #endif directives limited the code within it will be included only when build target is iOS, you can omit them for now as we only target iOS for this demo. What this bridge code does is to link the exported C interface with .NET. Note that no matter you exported a xcframework or static library (.a), "__Internal" is the only valid name, using any name other than this will probably throw DllNotFoundException during run time. You will see the EntryPoint parameter need to match the name we defined with @_cdecl in our Swift file.
The idea of the bridge is to map a public static method in C# to an exposed Objective-C interface, which eventually calls some Swift code.
Now go to MainPage.xaml.cs generated by MAUI project template and edit OnCounterClicked event handler, add these to the bottom of the method:
HelloWorldBridge.SayHelloStaticLib();
The final piece of the puzzle is to tell MSBuild to include your copied static library. Open your project .csproj file, place the following at the bottom of the file, right above the closing tag </Project> :
<ItemGroup>
<NativeReference Include="Platform/iOS/Framework/libHelloWorldBridge.a">
<Kind>Static</Kind>
<ForceLoad>true</ForceLoad>
</NativeReference>
</ItemGroup>
Build and launch your MAUI app with:
dotnet build -t:Run -f net10.0-ios -r iossimulator-arm64
Tap on the βClick meβ button and you should see the Hello World message.

π Congratulations! You have just successfully calling Swift code from C#! π
In next post we will create the widget and embed into a MAUI app.
Complete code sample can be found [here].
The original article is published on [Medium].