{"id":3058,"date":"2025-12-15T18:24:05","date_gmt":"2025-12-15T18:24:05","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/12\/15\/how-to-build-ios-widgets-with-net-maui\/"},"modified":"2025-12-15T18:24:05","modified_gmt":"2025-12-15T18:24:05","slug":"how-to-build-ios-widgets-with-net-maui","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2025\/12\/15\/how-to-build-ios-widgets-with-net-maui\/","title":{"rendered":"How to Build iOS Widgets with .NET MAUI"},"content":{"rendered":"<blockquote>\n<p>This is a guest blog from <a href=\"https:\/\/www.linkedin.com\/in\/toinedeboer\/\">Toine de Boer<\/a>.<\/p>\n<\/blockquote>\n<p>I\u2019m a .NET developer primarily focused on .NET MAUI to ASP.NET backend services. Because I recently have worked a lot with Widgets and encountered many obstacles and very limited documentation in the initial phase, I decided to write this article to show that it is absolutely possible to build complete Widgets with .NET MAUI. And to do so in a professional way comparable to using the native development environment, without having to fear that everything might break with every new build or update.<\/p>\n<p>This isn\u2019t a hands-on tutorial; instead, these are the biggest and most important parts in order of how to tackle the biggest obstacles when building an iOS widget. It\u2019s advisable to have some experience with .NET MAUI or Xamarin, and access to macOS is required, as creating an iOS Widget without macOS unfortunately isn\u2019t possible. You can cherry\u2011pick what you think you need, but I recommend reading from start to finish or you may miss small details that keep the Widget from working. The text starts with creating a simple static widget, and ends with a basic system for a fully interactive widget.<\/p>\n<p>To help you get started quickly, I have created a fully functional interactive widget, which is available on<br \/>\n<a href=\"https:\/\/github.com\/Toine-db\/Maui.WidgetExample\">github &gt; Maui.WidgetExample<\/a><\/p>\n<p><img data-opt-id=573454595  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/12\/iphone-widgetexample.webp\" alt=\"Screenshot of an example widget\" \/><\/p>\n\n<div class=\"alert alert-primary\">\n<p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>\n<p>iOS Widgets are standalone apps that are linked to a host app. For simplicity I mostly refer to the .NET MAUI app as the \u2018app\u2019 and the Widget app as the \u2018Widget\u2019.<\/p><\/div>\n<h2>Prerequisites<\/h2>\n<p>Before we begin we need a few things from Apple\u2019s developer console. Besides the Bundle ID of your existing app you also need a Bundle ID for your Widget. If your app uses <code>com.enbyin.WidgetExample<\/code> then conventionally you append something for a Widget such as <code>com.enbyin.WidgetExample.WidgetExtension<\/code>. Additionally, both Bundle IDs need the App Groups capability with a dedicated group. Create a Group ID by prefixing the app Bundle ID with <code>group<\/code>, for example <code>group.enbyin.WidgetExample<\/code>.<\/p>\n<p>For demo purposes I created a default .NET MAUI app targeting only iOS and Android. I set the iOS target to the newly created Bundle ID <code>com.enbyin.WidgetExample<\/code>. I also added a very noticeable app icon so we can easily observe if the correct icon has been used on the Widget screens.<\/p>\n<h2>Creating the Widget project<\/h2>\n<p>Let\u2019s begin with the biggest step for me as a .NET developer: working on Xcode and Swift. After creating the projects in Xcode, I can recommend to switch to VS Code and pair with Copilot to iterate quickly. You can have a solid small app set up quickly while following Apple\u2019s conventions.<\/p>\n<p>I begin in Xcode by creating an app project using the App template using Swift coding. This serves as the base project to which I attach the actual Widget extension, and optionally, I can use it for some light testing. I give it the same Bundle ID as the .NET MAUI app has; reusing a Bundle ID is not a problem because this almost empty Xcode app will never ship.<\/p>\n<p><img data-opt-id=1626647827  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/12\/xcode-new-app-project.webp\" alt=\"Creating a new Xcode project for apps\" \/><\/p>\n<p>With the app project in place, create the Widget extension next. In Xcode go to File &gt; New &gt; Target and choose the <code>Widget Extension<\/code> template. Pick a name that already uses the correct Bundle ID for the widget so you avoid later edits. To simplify generating sample data select the <code>Include Configuration App Intent<\/code> option; this gives a working widget immediately.<\/p>\n<p><img data-opt-id=1229579504  data-opt-src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2025\/12\/xcode-new-widget-project.webp\"  decoding=\"async\" src=\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20100%%20100%%22%20width%3D%22100%%22%20height%3D%22100%%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22100%%22%20height%3D%22100%%22%20fill%3D%22transparent%22%2F%3E%3C%2Fsvg%3E\" alt=\"Creating a new Xcode widget project\" \/><\/p>\n<p>When all projects are created I always align the desired iOS version, make sure it is the same for all Xcode targets. To check this just tap the solution name to open the solution settings in the main window, then set for all targets on tab \u2018General\u2019 under \u2018Minimum Deployments iOS version\u2019. Now give it a trial run on a device using Product &gt; Build in XCode.<\/p>\n<h3>Objects and flows inside the Widget<\/h3>\n<p>Creating the Widget project in XCode generates many objects. Understandably it is overwhelming at first, especially because almost everything is placed into one file. Therefore I always start by refactoring: moving every object into its own file and add some folder structure. You can do this without penalty because Swift does not really use namespaces; everything inside this project effectively falls under the same namespace regardless of folder structure.<\/p>\n<p>After refactoring the flow is actually straightforward. Here are the main objects, functions and their roles:<\/p>\n<ul>\n<li><strong>WidgetBundle<\/strong>: the entry point of the Widget extension, here you can expose one or more widgets to the end user<\/li>\n<li><strong>Widget<\/strong>: the configuration of a specific widget, here everything is listed such as the View, Provider, ConfigurationIntent and the supported sizes<\/li>\n<li><strong>AppIntentTimelineProvider<\/strong>: provides the data models to build the view, multiple models can be provided which are published according to a timeline.\n<ul>\n<li><strong>func placeholder<\/strong>: provides a minimal data model while the widget is loading (almost never visible)<\/li>\n<li><strong>func snapshot<\/strong>: provides a data model for when the widget is shown in the gallery as a preview and when first added to the screen<\/li>\n<li><strong>func timeline<\/strong>: provides a single data model (or a collection) for normal use, this is the main source where all data models for the widget come from<\/li>\n<\/ul>\n<\/li>\n<li><strong>TimelineEntry<\/strong>: the data model instance<\/li>\n<li><strong>View<\/strong>: the visual elements of the widget<\/li>\n<li><strong>WidgetConfigurationIntent<\/strong>: enables an end-user to configure the widget, in <code>timeline()<\/code> of the AppIntentTimelineProvider you receive these settings so they can be processed into the data model when needed<\/li>\n<\/ul>\n<p>Managing models or any other data in memory, like cache systems or just simple static fields, makes little sense. An iOS Widget is a static object that lives very briefly to perform very small action. In the AppIntentTimelineProvider the functions are invoked almost at the same time but they really run as distinct processes. For exchanging and storing data it is best to use some form of local storage (covered later).<\/p>\n<h3>App Icons<\/h3>\n<p>Previously I had recurring problems with the widget showing wrong icons on different views. Since I explicitly added the AppIcon images to Assets in the Widget extension and referencing them in its info.plist I had almost no problems. If icons are still wrong after updating the assets and info.plist, reboot your test device because iOS seems to do some kind of icon caching with Widgets.<\/p>\n<p>In Xcode the AppIcon Assets are predefined in the Widget project. When you open the AppIcon Assets page and then open the Attributes Inspector (top right) you can select iOS \u2018All Sizes\u2019. This gives you the ability to set all image sizes. Personally I find that too much manual work, so I use an online iOS icon generator that produces all formats and copy them straight into the <code>Assets.xcassets\/AppIcon.appiconset<\/code> folder.<\/p>\n<p>To adjust the plist settings, open the widget extension\u2019s <code>Info.plist<\/code> outside of XCode (e.g. in VS Code) and insert the entries below inside the <code>NSExtension<\/code> section:<\/p>\n<pre><code class=\"language-xml\">&lt;key&gt;NSExtensionPrincipalClass&lt;\/key&gt; \n&lt;string&gt;MyWidgetExtension.MyWidgetBundle&lt;\/string&gt;\n&lt;key&gt;CFBundleIcons&lt;\/key&gt;\n&lt;dict&gt;\n &lt;key&gt;CFBundlePrimaryIcon&lt;\/key&gt;\n &lt;dict&gt;\n  &lt;key&gt;CFBundleIconFiles&lt;\/key&gt;\n  &lt;array&gt;\n   &lt;string&gt;AppIcon&lt;\/string&gt;\n  &lt;\/array&gt;\n  &lt;key&gt;UIPrerenderedIcon&lt;\/key&gt;\n  &lt;false\/&gt;\n &lt;\/dict&gt;\n&lt;\/dict&gt;\n&lt;key&gt;CFBundleIconName&lt;\/key&gt;\n&lt;string&gt;AppIcon&lt;\/string&gt;<\/code><\/pre>\n<p>Adjust NSExtensionPrincipalClass using the following format:<\/p>\n<pre><code class=\"language-xml\">&lt;key&gt;NSExtensionPrincipalClass&lt;\/key&gt;\n&lt;string&gt;{YourWidgetModuleName}.{YourWidgetName}&lt;\/string&gt;\n\n&lt;!-- YourWidgetModuleName can be found in: Extension &gt; Build Settings &gt; Product Module Name --&gt;\n\n&lt;!-- YourWidgetName is the name of the Widget bundle, like \u2018MyWidgetsBundle\u2019 in: --&gt;\n&lt;!-- @main --&gt;\n&lt;!-- struct MyWidgetsBundle: WidgetBundle { --&gt;<\/code><\/pre>\n<h3>Creating a release build of the Widget<\/h3>\n<p>Release builds are easy to make in Xcode, but finding the right settings can be a hassle for me. Therefore, I use a standard script that makes it much easier to collect the releases into a dedicated folder, which can also be used in build pipelines. I run this script from the root of the Xcode projects and the releases go to an <code>XReleases<\/code> folder, the X to prevent them from being excluded by the default Visual Studio <code>.gitignore<\/code>.<\/p>\n<pre><code class=\"language-console\">rm -Rf XReleases\n\nxcodebuild -project XCodeWidgetExample.xcodeproj \n-scheme \"MyWidgetExtension\" \n-configuration Release \n-sdk iphoneos \nBUILD_DIR=$(PWD)\/XReleases clean build\n\nxcodebuild -project XCodeWidgetExample.xcodeproj \n-scheme \"MyWidgetExtension\" \n-configuration Release \n-sdk iphonesimulator \nBUILD_DIR=$(PWD)\/XReleases clean build<\/code><\/pre>\n<h3>Adding the Widget release to the MAUI app<\/h3>\n<p>The widget build output is an <code>.appex<\/code> (a magic macOS bundle folder, similar to an <code>.app<\/code>). On Windows with Visual Studio I previously had much build errors where the appex couldn\u2019t be found. To avoid this I now place the release outputs under <code>Platforms\/iOS\/<\/code> and include them with <code>CopyToOutput<\/code>.<\/p>\n<p>Use the snippet below in your <code>.csproj<\/code> to get the files available for the build:<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup Condition=\"$(TargetFramework.Contains('-ios'))\"&gt; \n   &lt;Content Remove=\"PlatformsiOSWidgetExtensions**\" \/&gt;\n   &lt;Content Condition=\"'$(ComputedPlatform)' == 'iPhone'\" Include=\".PlatformsiOSWidgetExtensionsRelease-iphoneosMyWidgetExtension.appex**\" CopyToOutputDirectory=\"PreserveNewest\" \/&gt;\n   &lt;Content Condition=\"'$(ComputedPlatform)' == 'iPhoneSimulator'\" Include=\".PlatformsiOSWidgetExtensionsRelease-iphonesimulatorMyWidgetExtension.appex**\" CopyToOutputDirectory=\"PreserveNewest\" \/&gt;\n&lt;\/ItemGroup&gt;<\/code><\/pre>\n<p>Now add the Widget extension to the .NET MAUI app project. The ItemGroup below ensures that this is done during the build, pay attention to the paths and filenames because this is very strict.<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup Condition=\"$(TargetFramework.Contains('-ios'))\"&gt;\n   &lt;!-- the appex folder path without the platform suffix --&gt;\n   &lt;AdditionalAppExtensions Include=\"$(MSBuildProjectDirectory)\/Platforms\/iOS\/WidgetExtensions\"&gt;\n      &lt;!-- the appex file without the .appex suffix --&gt;\n      &lt;Name&gt;MyWidgetExtension&lt;\/Name&gt;\n      &lt;!-- the appex folder platform suffixes --&gt;\n      &lt;BuildOutput Condition=\"'$(ComputedPlatform)' == 'iPhone'\"&gt;Release-iphoneos&lt;\/BuildOutput&gt;\n      &lt;BuildOutput Condition=\"'$(ComputedPlatform)' == 'iPhoneSimulator'\"&gt;Release-iphonesimulator&lt;\/BuildOutput&gt;\n   &lt;\/AdditionalAppExtensions&gt;\n&lt;\/ItemGroup&gt;<\/code><\/pre>\n<p>At this point the Widget should be visible in your .NET MAUI app build. Right now it is a Widget that works entirely on its own, without data or communications from your .NET MAUI app.<\/p>\n\n<div class=\"alert alert-primary\">\n<p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>\n<p>Widget extensions will most likely not be visible when you build from Visual Studio for \u2018iOS Local Devices\u2019.<\/p><\/div>\n<h2>Data sharing between App and Widget<\/h2>\n<p>iOS Widgets can best be treated as standalone apps. The .NET MAUI app and the Widget can not freely exchange data or communicate. For data exchange we can use .NET MAUI Preferences which maps to UserDefaults on iOS. To ensure they use the same source, both projects need an <code>Entitlements.plist<\/code> specifying the same Group ID which we created earlier when setting up the Bundle ID with App Groups capability.<\/p>\n<p>An example <code>Entitlements.plist<\/code> with group id <code>group.com.enbyin.WidgetExample<\/code>:<\/p>\n<pre><code class=\"language-xml\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;!DOCTYPE plist PUBLIC \"-\/\/Apple\/\/DTD PLIST1.0\/\/EN\" \"http:\/\/www.apple.com\/DTDs\/PropertyList-1.0.dtd\"&gt;\n&lt;plist version=\"1.0\"&gt;\n  &lt;dict&gt;\n  &lt;key&gt;com.apple.security.application-groups&lt;\/key&gt;\n  &lt;array&gt;\n  &lt;string&gt;group.com.enbyin.WidgetExample&lt;\/string&gt;\n  &lt;\/array&gt;\n  &lt;\/dict&gt;\n&lt;\/plist&gt;<\/code><\/pre>\n<p>For clarity: both the Widget Xcode project and the .NET MAUI project must use such entitlements, and do not forget to create a new release of the Xcode project after adding the entitlements. Additionally the entitlements of the Widget Xcode project must also be referenced in the .NET MAUI project under the <code>AdditionalAppExtensions<\/code> element in the <code>.csproj<\/code> for the .NET MAUI build.<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup Condition=\"$(TargetFramework.Contains('-ios'))\"&gt;\n   &lt;!-- the appex folder path without the platform suffix --&gt;\n   &lt;AdditionalAppExtensions Include=\"$(MSBuildProjectDirectory)\/Platforms\/iOS\/WidgetExtensions\"&gt;\n      &lt;!-- the appex file without the .appex suffix --&gt;\n      &lt;Name&gt;MyWidgetExtension&lt;\/Name&gt;\n      &lt;!-- the appex folder platform suffixes --&gt;\n      &lt;BuildOutput Condition=\"'$(ComputedPlatform)' == 'iPhone'\"&gt;Release-iphoneos&lt;\/BuildOutput&gt;\n      &lt;BuildOutput Condition=\"'$(ComputedPlatform)' == 'iPhoneSimulator'\"&gt;Release-iphonesimulator&lt;\/BuildOutput&gt;\n\n      &lt;!-- entitlements for the appex, without this the shared storage won't work --&gt;\n      &lt;!-- errors that entitlements could not be found: include the entitlements with CopyToOutput --&gt;\n      &lt;!-- errors when reading entitlements during build: store entitlements file with line-ending type LF --&gt;\n      &lt;CodesignEntitlements&gt;Platforms\/iOS\/Entitlements.MyWidgetExtension.plist&lt;\/CodesignEntitlements&gt;\n   &lt;\/AdditionalAppExtensions&gt;\n&lt;\/ItemGroup&gt; <\/code><\/pre>\n<p>At this point the App and Widget should be able to use the same data source. In both projects the usage of a specific Group ID must be indicated explicitly in code. In .NET MAUI do NOT use <code>Preferences.Default<\/code>; instead provide the Group ID in the <code>sharedName<\/code> parameter.<\/p>\n<pre><code class=\"language-csharp\">\/\/ example how to store data in .NET MAUI.\nPreferences.Set(\"MyDataKey\", \"my data to share\", \"group.com.enbyin.WidgetExample\");<\/code><\/pre>\n<pre><code class=\"language-swift\">\/\/ example how to store data in Swift.\nUserDefaults(suiteName: \"group.com.enbyin.WidgetExample\")?.set(\"my data to share\", forKey: \"MyDataKey\")\n\/\/ example how to get data in Swift.\nlet data = UserDefaults(suiteName: \"group.com.enbyin.WidgetExample\")?.string(forKey: \"MyDataKey\")<\/code><\/pre>\n\n<div class=\"alert alert-primary\">\n<p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>\n<p>Storage keys are case-sensitive; I advise to keep keys simple and optionally consistently lowercase to avoid problems.<\/p><\/div>\n<h2>Communication from App to Widget<\/h2>\n<p>The Widget does not know when the App shares data and the App does not know when the Widget does so. Signaling new available data from the App to the Widget uses a different mechanism than from Widget to App. Signaling from App to Widget is easy with Apple\u2019s WidgetKit API. This API is not available in .NET MAUI so you must create a binding yourself. It is a very small API and great to experiment with bindings yourself. For this demo I use a NuGet package <code>WidgetKit.WidgetCenterProxy<\/code> where this has already been done for us.<\/p>\n<p>The WidgetKit API mainly provides two options: reload all widgets on the device or reload only widgets of a specific <code>kind<\/code>. I always use the latter because the platform will ignore you if you use one of these options too frequently; I imagine they also prefer that you only update your own specific widgets. The <code>kind<\/code> of your Widget is easy to find in your Widget object in Swift; under the <code>kind<\/code> property.<\/p>\n<pre><code class=\"language-csharp\">\/\/ Example on how to refresh all Widgets of kind \u2018MyWidget\u2019 in .NET MAUI\nvar widgetCenterProxy = new WidgetKit.WidgetCenterProxy();\nwidgetCenterProxy.ReloadTimeLinesOfKind(\"MyWidget\");<\/code><\/pre>\n\n<div class=\"alert alert-primary\">\n<p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>\n<p>WidgetKit reload functions are a polite request to the OS; it decides when it happens and whether you are using it too often. Usually the widget refresh happens immediately.<\/p><\/div>\n<h2>Communication from Widget to App<\/h2>\n<p>Communication from Widget to App can happen in two ways, optionally with a very small amount of data. By default a Widget opens the corresponding app when you tap it, if you overwrite this with <code>widgetUrl()<\/code> you can open the app with a Deep Link that includes data in the url. A drawback is that a Widget is build as a static object, when using <code>widgetUrl<\/code> the URL must be determined beforehand when setting up the Widget view (often happens in the provider) and passed as string through the data model.<\/p>\n<pre><code class=\"language-swift\">\/\/ example of using a DeepLink url in Swift\nstruct MyView : View {\n   var body: some View {\n   \/\/ my views\n   }.widgetUrl(URL(string: \"mywidget:\/\/something?var1=dummy-data\"))\n}<\/code><\/pre>\n<p>A different way to communicate can originate from AppIntents. An AppIntent is a way to execute actions\/logic that you can attach to interactive elements like buttons. It is also the place where the OS gives you a bit of time to perform longer actions, like for making http calls. For example you can attach a custom AppIntent to a button in your Widget that changes a value in storage, after which the AppIntent itself triggers a refresh of the Widget. The Widget will then be reloaded with the new data, enabling \u201cInteractive Widgets\u201d.<\/p>\n<pre><code class=\"language-swift\">\/\/ example of an AppIntent changing data and reloading widget\nstruct IncrementCounterIntent: AppIntent {\n   static var title: LocalizedStringResource { \"Increment Counter\" }\n   static var description: IntentDescription { \"Increments the counter by 1\" }\n\n   func perform() async throws -&gt; some IntentResult {\n\n   var currentCount = 0\n\n   let userDefaults = UserDefaults(suiteName: Settings.groupId)\n   let storedValue = userDefaults?.integer(forKey: Settings.appIncommingDataKey)\n   if let storedValueCount = storedValue {\n      currentCount = storedValueCount\n   }\n\n   \/\/ do action\n   let newCount = currentCount + 1\n\n   \/\/ Save new value\n   userDefaults?.set(newCount, forKey: Settings.appIncommingDataKey)\n\n   \/\/ Reload timelines &gt; refreshing widget\n   WidgetCenter.shared.reloadTimelines(ofKind: \"MyWidget\")\n\n   return .result()\n}<\/code><\/pre>\n<pre><code class=\"language-swift\">\/\/ example of Button using AppIntent in Swift\nstruct MyWidgetView : View {\n   var entry: Provider.Entry\n\n   var body: some View {\n      VStack(spacing:4) {\n         Button(intent: IncrementCounterIntent()) {\n            Text(\"+\")\n         }\n      }\n      .padding()\n   }\n}<\/code><\/pre>\n<p>On iOS, a Widget can <strong>not<\/strong> communicate with the App in the background. Any direct call brings the App to the foreground. To keep the App closed and still perform work, you can use AppIntents in the Widget to call your backend. The backend can execute the action and, if needed, send a silent push notification to the App. The App can then handle the update in the background if required. This can be done with any web service and existing push notification provider; For that reason, I\u2019ve included only an illustrative SilentNotificationService as entry point in the demo code rather than a full implementation.<\/p>\n<h2>Streamlining widget development<\/h2>\n<p>With the complete interactive widget in place, the next step will be implementing your logic and refining the widget\u2019s layout and styling. Ideally all logic goes into the .NET MAUI app so you can reuse it on other platforms too. Unfortunately you can\u2019t avoid implementing some parts in Swift, such as handling storage, building views, or some small communication with your backend. The transition from C# to Swift has a small learning curve; that\u2019s why I advise to use VS Code and to pair-program with Copilot. Copilot will not make everything perfect without errors in a single run, but it will give you a great sparring partner and will help you get a lot done quickly. Combine this with an open XCode at the same time to build and test frequently to catch issues early. Do this with the Widget opened in a XCode Canvas view using #preview data, so you can see visual changes instantly after every build.<\/p>\n<h2>Wrapping Up and Practical Tips<\/h2>\n<p>With the interactive widget fully implemented, the next step naturally becomes refining your logic, layout, and overall design. While most of your core logic can remain inside your .NET MAUI app for reuse across platforms, a bit of Swift will always be required for widget specific tasks like handling storage, building views, or performing lightweight backend actions. Here are some final tips to help you get up to speed during the transition from C# to Swift:<\/p>\n<ul>\n<li>Use VS Code to pair-program with Copilot when creating your Swift code.<\/li>\n<li>Keep Xcode open for rapid build and preview cycles to catch issues early.<\/li>\n<li>Open the Canvas view in Xcode and use #preview data so you can see visual changes quickly.<\/li>\n<\/ul>\n<p>If you\u2019re interested in taking your widget skills to Android, good news: an article on building Android Widgets with .NET MAUI is coming soon. Stay tuned!<\/p>\n<p>The post <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/how-to-build-ios-widgets-with-dotnet-maui\/\">How to Build iOS Widgets with .NET MAUI<\/a> appeared first on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\">.NET Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>This is a guest blog from Toine de Boer. I\u2019m a .NET developer primarily focused on .NET MAUI to ASP.NET [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3059,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[7],"tags":[],"class_list":["post-3058","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3058","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=3058"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3058\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media\/3059"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=3058"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=3058"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=3058"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}