Migrating to Uno Platform 7.0 — Skia-only rendering
Uno Platform 7.0 removes the native UI rendering backends (native Android Views, native iOS/tvOS UIKit, and the native WebAssembly DOM renderer) and makes Skia the single, implicit rendering engine on every target. It also removes the Mac Catalyst target framework entirely.
Skia already runs on every platform — desktop (Win32, WPF, X11, GTK, macOS, FrameBuffer),
Skia-on-Android, Skia-on-iOS, and Skia-on-WebAssembly. In 7.0 it becomes the only UI
rendering path: a UIElement is a plain managed object backed by a Composition.Visual
on all platforms, drawn into a single Skia surface.
This does not drop platform support: Android, iOS, macOS, Windows, Linux, and
WebAssembly all remain supported — they now all render with Skia. The one exception is
Mac Catalyst, which is removed as a target framework; macOS is served by the
net10.0-desktop head.
Important
This is a hard removal in a single major version — there is no [Obsolete] interim.
Plan to recompile every Uno library against 7.0 and update your application heads.
Who is affected
- Apps that opted out of the Skia renderer on mobile by omitting
<UnoFeatures>skiarenderer</UnoFeatures>and relying on native rendering. - Apps that used the native WebAssembly DOM renderer (
Uno.WinUI.WebAssembly). - Code that referenced native rendering types, native element hosting, or native-only
FeatureConfigurationflags (see below). - Apps that still build a Mac Catalyst (
net*-maccatalyst) head.
Apps already running on Skia on every target need only recompile against 7.0 and remove native bootstrap/heads.
What changed
Rendering is Skia everywhere
The NativeRenderer Uno Feature and the renderer-selection logic are gone — Skia is
always used. skiarenderer is now implicit and mandatory for
android/ios/tvos; it is kept as a no-op for back-compat, so you can
leave <UnoFeatures>skiarenderer</UnoFeatures> in place or remove it — either way Skia
renders.
WebAssembly renders to a canvas through Skia; there is no per-element DOM tree, no
Uno.UI.css styling layer, and no WindowManager.ts.
If your project was created before Uno Platform 6.0 and still selects a renderer, follow the Uno 6.0 migration guide first to move to the Uno.SDK single-project model.
Mac Catalyst is removed
The net*-maccatalyst target framework is no longer supported. The Uno.Sdk no longer
produces a Mac Catalyst head: maccatalyst is not a recognized TargetFramework, the
Platforms/MacCatalyst/ folder is no longer picked up, and the MacCatalystProjectFolder
property is gone. The __MACCATALYST__ conditional symbol is never defined, and the
.iOS.cs and .UIKit.cs file suffixes now apply only to net10.0-ios and net10.0-tvos.
macOS remains a fully supported target through the net10.0-desktop head, which runs
on macOS with Skia rendering. To migrate:
- Remove
net*-maccatalystfrom<TargetFrameworks>in every project. - Add
net10.0-desktopif the solution does not already have a desktop head. - Move anything still needed from
Platforms/MacCatalyst/toPlatforms/Desktop/, and drop the CatalystInfo.plistandEntitlements.plist. - Replace
#if __MACCATALYST__blocks with#if __DESKTOP__, or with anOperatingSystem.IsMacOS()runtime check. - Publish with the macOS desktop packaging flow instead of the Mac Catalyst one.
Packages
| Removed / changed | Migration |
|---|---|
Uno.WinUI.WebAssembly package removed (and the older Uno.WinUI.Runtime.WebAssembly) |
Use Uno.WinUI.Runtime.Skia.WebAssembly.Browser. The UI renders to a canvas; there is no DOM tree. With the Uno.SDK, the Skia browser head is referenced implicitly — there is nothing to add. |
Uno.WinUI.Skia.X11, Uno.WinUI.Skia.MacOS, and Uno.WinUI.Skia.Linux.FrameBuffer bootstrapper packages removed |
These were empty meta-packages that only redirected to the real head. With the Uno.SDK, remove the reference — the matching Uno.WinUI.Runtime.Skia.* head is referenced implicitly for executable heads. For a hand-rolled (non-Uno.SDK) head, replace it with the corresponding Uno.WinUI.Runtime.Skia.<variant> package. |
Uno.UI.BindingHelper.Android assembly removed |
Remove the reference; Skia-on-Android needs no Java/JNI binding. |
Uno.UI.FluentTheme.v1 assembly removed |
The Fluent Design V1 styles were deleted several releases ago and the assembly has shipped empty since. Uno.UI.FluentTheme still ships and is what XamlControlsResources has always loaded, so there is nothing to change unless you referenced the V1 types directly — see Fluent Design resource-version types under Public API removed. |
Uno.UI.FluentTheme.v2 assembly merged into Uno.UI.FluentTheme |
With V1 gone there is a single set of Fluent styles, so the two assemblies were collapsed into one. All the styles ship in Uno.UI.FluentTheme; both assemblies come from the Uno.WinUI package, so no reference changes. Only a direct reference to the Uno.UI.FluentTheme.v2 assembly, or to XamlControlsResourcesV2, needs updating. |
Uno.UniversalImageLoader no longer injected (Android) |
Skia handles image loading internally. If you initialized it manually, remove the ConfigureUniversalImageLoader(); call. |
Uno.UI.Maps AddIn removed |
The native Google Maps control has no core Skia equivalent — use a third-party/Skia map or custom rendering. |
Uno.WinUI UI assemblies for net*-android/ios/tvos are now the Skia binaries |
Same TFM string, but binary-incompatible with previously native-built consumers. Recompile all libraries against 7.0 and remove native bootstrap. |
Xamarin.AndroidX.* transitive deps removed (AppCompat, RecyclerView, Activity, Browser, SwipeRefreshLayout) |
If your own code uses AndroidX, add explicit PackageReferences. |
Xamarin.AndroidX.Legacy.Support.V4 no longer injected (Android) |
The Uno.Sdk added it to every Android app head. It is the AndroidX wrapper for the Android Support Library v4, whose job was bridging pre-AndroidX view code — with the native Android Views renderer gone, nothing needs it. Ten packages leave the graph: Legacy.Support.V4, Legacy.Support.Core.UI, Legacy.Support.Core.Utils, AsyncLayoutInflater, LocalBroadcastManager, Media, Print, SlidingPaneLayout, Window and its Window.*Core* companions. Nothing changes version. AndroidX.Window still arrives with the foldable feature. If your own code uses any of these, add an explicit PackageReference. The AndroidXLegacySupportV4Version property is removed and is silently ignored if still set. |
SkiaSharp.Views.Uno.WinUI no longer referenced implicitly |
The Uno.Sdk used to add it to every Uno Platform target, and to WebAssembly heads using the lottie, svg, material, cupertino, or simpletheme features. Nothing in Uno Platform needs it anymore — SVG draws through Uno.WinUI.Graphics2DSK and Lottie through SkiaSharp.Skottie. If your own code uses SKXamlCanvas or SKSwapChainPanel, switch to SKCanvasElement, which is hardware-accelerated and referenced implicitly; otherwise add an explicit PackageReference. |
| Windows App SDK default moved from 1.7 to 2.3.1 | Windows heads now build against Windows App SDK 2.x, so packaged apps take a framework dependency on Microsoft.WindowsAppRuntime.2 and end users need the matching Windows App Runtime — 2.3.1 or later from the Stable release section — installed. To stay on 1.x, set <WinAppSdkVersion> (and <WinAppSdkBuildToolsVersion>) explicitly in your Windows head. |
Uno.UI.Toolkit types moved to the Uno.UI.* namespaces |
The old name was routinely confused with the separate Uno Toolkit (Uno.Toolkit.UI). Each type now sits in the namespace it belongs to — see the mapping table below. Type names and behavior are unchanged. Uno.Diagnostics.UI, Uno.UI.Markup, Uno.Helpers and Uno.UI.Maps are unaffected — only the Uno.UI.Toolkit* namespaces moved. |
Important
This is a hard rename — there is no type-forwarder and no xmlns alias. Uno.UI.Toolkit.dll
and Uno.UI.Extras.dll are different assembly identities, so a library compiled against
Uno 6.x can no longer bind: its references into Uno.UI.Toolkit resolve to nothing and the
build fails with CS0012. Recompile every dependent library against 7.0 rather than mixing
majors.
Note
Referencing Uno.WinUI.WebAssembly (or the older Uno.WinUI.Runtime.WebAssembly)
alongside the Skia browser head raises the UNOB0017 build diagnostic. Removing the
explicit reference resolves it.
Uno.UI.Toolkit types move to the Uno.UI.* namespaces
The Uno.UI.Toolkit namespace — which ships inside the Uno.WinUI package — was routinely
mistaken for the separate Uno Toolkit product
(Uno.Toolkit.UI); the two names are a word-order swap apart. Its types now sit in the
namespace each one belongs to, alongside the rest of the framework. Type names and behavior
are unchanged, and there is no forwarding shim: the old namespaces stop resolving.
| Type | Before | After |
|---|---|---|
ElevatedView, CommandBarExtensions, SplitViewExtensions |
Uno.UI.Toolkit |
Uno.UI.Xaml.Controls |
UIElementExtensions |
Uno.UI.Toolkit |
Uno.UI.Xaml |
AutomationPropertiesExtensions |
Uno.UI.Toolkit |
Uno.UI.Xaml.Automation |
ScrollBarExtensions |
Uno.UI.Toolkit |
Uno.UI.Xaml.Controls.Primitives |
VisibleBoundsPadding |
Uno.UI.Toolkit |
Uno.UI.Behaviors |
StorageFileHelper |
Uno.UI.Toolkit |
Uno.Storage |
| input injection dev helpers | Uno.UI.Toolkit.DevTools.Input (and .DevTools.Xaml) |
Uno.UI.DevTools.Input (and .DevTools.Xaml) |
XAML declarations follow the same mapping, for example
xmlns:toolkit="using:Uno.UI.Toolkit" becomes xmlns:uuxc="using:Uno.UI.Xaml.Controls" for
ElevatedView and the CommandBar/SplitView extensions, or
xmlns:uub="using:Uno.UI.Behaviors" for VisibleBoundsPadding.
The other namespaces carried by that assembly keep their names, so code using
DiagnosticsOverlay (Uno.Diagnostics.UI), FromJsonExtension (Uno.UI.Markup) or
ColorExtensions / ImageHelper (Uno.Helpers) needs no change.
Platform targeting in multi-targeted libraries
A library that multi-targets net10.0-ios, net10.0-android, or net10.0-tvos alongside a plain net10.0 now
keeps its platform-specific asset on the matching application head. In 6.x those assets were replaced by the
library's netX.0 asset, so #if __IOS__ and #if __ANDROID__ blocks inside a library never ran and a netX.0
target framework was mandatory. Both are no longer true: the net10.0-ios assembly is what an iOS head compiles
against and deploys.
Three related mechanisms now follow the target framework rather than an internal runtime identifier, for class libraries and application heads alike:
| Mechanism | 6.x | 7.0 |
|---|---|---|
android: / ios: / wasm: XAML prefixes |
never applied — every target rendered with Skia, which won | apply on net*-android / net*-ios / net*-browserwasm |
tvos: and desktop: XAML prefixes |
did not exist | apply on net*-tvos / net*-desktop |
winappsdk: XAML prefix |
spelled win: |
net*-windows10.x; win: kept as a synonym |
*.skia.cs, *.crossruntime.cs |
compiled only for net*-desktop |
compiled for every target framework except the WinAppSDK one |
__DESKTOP__ |
application heads only | any net*-desktop project |
__WASM__ |
application heads, and libraries referencing the WebAssembly runtime package | any net*-browserwasm project |
What this means for an upgrade:
- Re-test
#if-guarded code and conditional XAML inside multi-targeted libraries. Code that was written under the assumption that it was dead on mobile now executes. In particularnot_android:andnot_ios:no longer apply on Android and iOS. - Rename a
*.skia.csfile that was really desktop-only to*.desktop.cs. It now compiles on every Uno target. - Recompile every multi-targeted library against 7.0. Its
net*-ios/net*-androidassets are now deployed rather than discarded, so a stale asset built against the 6.x binaries fails at run time instead of being silently replaced. The build reports UNOB0020 when it can detect this. - Nothing to do if your libraries already ship a
netX.0asset. It is still the asset used by thenet10.0-desktopandnet10.0-browserwasmheads, and workarounds built for the 6.x behavior keep working.
Note
*.skia.cs means "compiled by Uno Platform rather than by WinUI". It does not name a drawing backend: Uno.UI
is compiled once and resolves its backend at run time.
Removed XAML prefixes
Every conditional prefix is now named after a target framework, so the prefixes that named a renderer or a long-gone distinction are removed. Markup using them no longer resolves and must be rewritten:
| Removed prefix | Replacement |
|---|---|
skia:, netstdref: |
not_winappsdk: |
not_skia:, not_netstdref: |
winappsdk: |
androidskia:, iosskia:, tvosskia:, wasmskia: |
android:, ios:, tvos:, wasm: |
macos: |
desktop: |
not_mux: |
drop the attribute — it dates from UWP support and never applied |
xamarin:, legacy: |
drop the prefix |
Removed file suffixes
| Removed suffix | Replacement |
|---|---|
*.Apple.cs |
*.UIKit.cs — the rule was always identical |
*.reference.cs |
delete the file, or fold it into *.crossruntime.cs. It was gated on a build flavor an application never selected, so it compiled nowhere |
*.iOSmacOS.cs |
*.iOS.cs. It named the native macOS target, removed in 7.0 |
Removed preprocessor symbols
HAS_UNO_SKIA_WIN32, HAS_UNO_SKIA_X11, HAS_UNO_SKIA_MACOS, HAS_UNO_SKIA_LINUX_FB,
HAS_UNO_SKIA_ANDROID, HAS_UNO_SKIA_APPLE_UIKIT, HAS_UNO_SKIA_HEADLESS,
HAS_UNO_SKIA_WEBASSEMBLY_BROWSER and their __UNO_SKIA_*__ counterparts are gone
(#17684).
They read as a compile-time host discriminator but could never be one: the SDK references every desktop host
package together, so all of them were defined at once in a netX.0-desktop head. Use
OperatingSystem.IsWindows() / IsLinux() / IsMacOS(), which is the only check that can be correct for a
target framework that runs on all three. HAS_UNO_SKIA and __UNO_SKIA__ are unaffected.
MRT Core moves to the Uno.WinRT package
The MRT Core surface — the Microsoft.Windows.ApplicationModel.Resources namespace — now lives in
the assembly matching its Windows App SDK home:
| 6.x | 7.0 | |
|---|---|---|
| Assembly | Uno.UI |
Uno.WinRT |
| Package | Uno.WinUI |
Uno.WinRT |
Source code needs no change. The namespace is unchanged, so
using Microsoft.Windows.ApplicationModel.Resources; still resolves, and Uno.WinUI depends on
Uno.WinRT, so both assemblies are already referenced. The types that moved are
ResourceLoader, ResourceManager, ResourceContext, ResourceMap, ResourceCandidate,
ResourceCandidateKind, ResourceNotFoundEventArgs, KnownResourceQualifierName,
MrtCoreContract, IResourceManager, and IResourceContext.
Compiled binaries must be rebuilt. Moving a type between assemblies is a binary break, and
there is no forwarding shim: a library built against 6.x resolves these types from Uno.UI and
fails at run time with
Could not load type 'Microsoft.Windows.ApplicationModel.Resources.ResourceLoader'
from assembly 'Uno.UI, Version=…'.
Rebuild every library that uses MRT Core against 7.0, and update anything that names the
assembly explicitly — assembly-qualified type names, TypeForwardedTo, or an IL merge/repack
configuration:
- Type.GetType("Microsoft.Windows.ApplicationModel.Resources.ResourceLoader, Uno.UI")
+ Type.GetType("Microsoft.Windows.ApplicationModel.Resources.ResourceLoader, Uno.WinRT")
The older WinRT loader — Windows.ApplicationModel.Resources.ResourceLoader, without the
Microsoft. prefix — did not change assemblies; only the Microsoft.Windows.* MRT Core
surface moved. It does still need the same assembly-qualified-name update, because the WinRT
assembly it has always lived in is itself renamed Uno → Uno.WinRT in 7.0.
Public API removed
Native base classes / identity:
BindableView(andBindable*widget wrappers),BindableUIView(andBindableUI*).UIElementno longer inherits a nativeView/UIView/DOM element. Remove casts toAndroid.Views.View/UIKit.UIView; useUIElement.Visual(Composition) and reach platform APIs via theUno.UI.Runtime.Skia.*hosts andUno.Foundation.Native element hosting:
Uno.UI.NativeElementHosting.BrowserHtmlElement,Uno.UI.Runtime.WebAssembly.HtmlElementAttribute, andContentPresenterhosting of a nativeView/UIView/DOM element asContent. UseWebView2for HTML content, or redesign with Uno controls. On iOS, opt-in native embedding remains viaUIKitNativeElementHostingExtension(overlay-composited, reduced performance).Native control / host types:
NativeListViewBase,NativePagedView,NativeScrollContentPresenter,NativeFramePresenter,NativePopup,RootViewController,Window : UIWindowidentity,NativeRenderTransformAdapter,IShadowChildrenProvider,CompositorThread,Uno.UI.Composition.ICompositionRoot. Use the WinUI control (ListView/Frame/Popup/…) — everything renders via Skia.Native flyout opt-in:
FlyoutBase.UseNativePopup, including its conditional-XAML forms (android:UseNativePopup/ios:UseNativePopup). Remove the assignment — flyouts always use the WinUI presentation. The twoUno.UI.Toolkitattached properties that only ever customized that native iOS presentation go with it:MenuFlyoutItemExtensions.IsDestructive(red "destructive" item text) andMenuFlyoutExtensions.CancelTextIosOverride(custom cancel-button caption). Remove the attributes; style theMenuFlyoutItemdirectly for a destructive look.UICommand.IsDestructiveis not removed — it still drives the native iOSMessageDialog— but its Uno Platform wrapper is (see below); set the property directly.Native default styles: the whole
Generic.Native.xamldictionary is gone, so theNativeDefaultButton,NativeDefaultCheckBox,NativeDefaultCommandBar,NativeDefaultAppBarButton,NativeDefaultFrame,NativeDefaultPivot,NativeDefaultProgressBar,NativeDefaultSlider,NativeDefaultTextBox,NativeDefaultToggleSwitch,NativeDefaultSplitViewOpenPaneLength,AndroidButtonStyle,AndroidCheckBoxStyle,AndroidRadioButtonStyle,iOSButtonStyle,IosPickerFlyoutTextButtonStyle,LeftDrawerSplitViewStyle, andRightDrawerSplitViewStyleresource keys no longer resolve. These styles templated native views, so there is no Skia equivalent — drop theStyle="{StaticResource NativeDefault…}"and the control falls back to the WinUI default style it already used when no native style was registered.Uno.UI.Converters.UnoNativeDefaultProgressBarReverseBoolConvertergoes with them.Native-style declaration: the
not_win:IsNativeStyle="True"attribute on aStyleis no longer recognized. A third-party dictionary that still carries it now fails the XAML build instead of silently registering a second, native default style — remove the attribute. The Uno-onlyStyle.RegisterDefaultStyleForType(Type, IXamlResourceDictionaryProvider, bool)also loses itsisNativeparameter; it is[EditorBrowsable(Never)]and normally only called from XAML-generated code, so rebuilding regenerates the correct call.Uno.UI.Toolkitmembers that only ever ran on native targets:Uno.UI.ViewHelper(theUno.UI.Toolkitone, whose only memberArchitecturealways returnednull— the unrelatedUno.UI.ViewHelperinUno.UIstays) andUICommandExtensions.SetDestructive. To flag a destructiveUICommand, setUICommand.IsDestructivedirectly from an iOS-targeted head.- uic.SetDestructive(true); + uic.IsDestructive = true;Composition:
Uno.CompositionConfiguration.Options.UseCompositorThread(the Android RenderNode compositor thread). Remove the flag; Skia composition needs no dedicated native render thread.Deprecated UIKit disposal helper:
Uno.Foundation.NSObjectExtensions.ValidateDispose, deprecated since Uno 5.x. Remove the call from yourNSObject/UIViewDisposeoverrides — Skia does not host native views, so there is nothing to validate.Uno.Extensions.UriExtensions.IsLocalResourceis renamed toIsMsAppx. Same behavior — the predicate has always tested for thems-appxscheme, which the old name did not say. Rename the call; there is no forwarding shim.- if (uri.IsLocalResource()) + if (uri.IsMsAppx())Legacy WebAssembly JavaScript interop:
Uno.Foundation.Interop.IJSObject,IJSObjectMetadata,JSObjectHandle,JSObject, andWebAssemblyRuntime.InvokeJSWithInterop(FormattableString)— the Uno-only managed-to-JavaScript object-marshalling mechanism (no WinUI counterpart). Migrate to the standard .NET WebAssembly interop, JSImport/JSExport fromSystem.Runtime.InteropServices.JavaScript— the recommended, source-generated path (thread-safe, CSP-compliant, noeval). The string-basedWebAssemblyRuntime.InvokeJS(string)is not removed, but it is a legacy eval-based API and is not recommended for new code.Fluent Design resource-version types:
Microsoft.UI.Xaml.Controls.XamlControlsResourcesV1,Microsoft.UI.Xaml.Controls.XamlControlsResourcesV2, theControlsResourcesVersionenum, and theControlsResourcesVersionmember on bothXamlControlsResources(a dependency property) andXamlControlsResourcesV2(an inertobjectproperty that was never read). None of this existed in Windows App SDK —XamlControlsResourcesis the only Fluent resources type there, and it now matches. Use it everywhere:- <XamlControlsResourcesV2 /> + <XamlControlsResources />The V1 styles were deleted several releases ago and
XamlControlsResourceshas loaded V2 regardless of the property ever since, so none of this changes visual behavior. Drop anyControlsResourcesVersionassignment, in code or in XAML — on either type, a leftover attribute now fails the XAML build:- <XamlControlsResources ControlsResourcesVersion="Version2" /> + <XamlControlsResources />The
Uno.UI.FluentTheme.v1assembly is removed along with these types, andUno.UI.FluentTheme.v2is merged intoUno.UI.FluentTheme— see Packages above.Windowevent handler delegates:Microsoft.UI.Xaml.WindowActivatedEventHandler,WindowSizeChangedEventHandler,WindowVisibilityChangedEventHandler, andWindowClosedEventHandler. None of them exists in WinUI, which declares everyWindowevent asTypedEventHandler<object, TArgs>—Activated,SizeChanged, andVisibilityChangednow do the same, andClosedalready did, which leftWindowClosedEventHandlerunreferenced. The handler shape is unchanged forActivatedandSizeChanged, so lambdas, method groups, and+=/-=keep compiling; only code that named the delegate type explicitly breaks:- window.SizeChanged += new WindowSizeChangedEventHandler(OnSizeChanged); + window.SizeChanged += OnSizeChanged;Window.VisibilityChangednow carriesMicrosoft.UI.Xaml.WindowVisibilityChangedEventArgsinstead ofWindows.UI.Core.VisibilityChangedEventArgs, matching WinUI. A handler declared with the old argument type no longer binds, so update its signature. Both members carry the same meaning on the new type —Visibleis get-only andHandledis get/set:- private void OnVisibilityChanged(object sender, VisibilityChangedEventArgs e) + private void OnVisibilityChanged(object sender, WindowVisibilityChangedEventArgs e) { VisibilityLabel.Text = e.Visible ? "Visible" : "Hidden"; }Windows.UI.Core.VisibilityChangedEventArgsitself is not removed — it is still the argument type of the legacyCoreWindow.VisibilityChangedevent.
FeatureConfiguration flags removed
The native-only flags below no longer exist; delete the calls — behavior is the unified Skia/WinUI behavior:
- Android:
ComboBox.AllowPopupUnderTranslucentStatusBar,FrameworkElement.AndroidUseManagedLoadedUnloaded,FrameworkElement.InvalidateNativeCacheOnRemeasure,Popup.UseNativePopup,NativeListViewBase.*,NativeFramePresenter.AndroidUnloadInactivePages,TextBox.UseLegacyInputScope,UIElement.UseLegacyClipping,UIElement.AlwaysClipNativeChildren,ScrollViewer.AndroidScrollbarFadeDelay,WebView.ForceSoftwareRendering,PointerRoutedEventArgs.AllowRelativeTimeStamp,TextBlock.IsJavaStringCachedEnabled/JavaStringCachedCapacity,AppBarButton.EnableBitmapIconTint,TimePickerFlyout.UseLegacyTimeSetting,NavigationView.EnableUno19516Workaround,AndroidSettings.IsEdgeToEdgeEnabled. - iOS:
Image.LegacyIosAlignment,FrameworkElement.IOsAllowSuperviewNeedsLayoutWhileInLayoutSubViews,CommandBar.AllowNativePresenterContent,DatePicker.UseLegacyStyle,TimePicker.UseLegacyStyle,UIElement.FailOnNSObjectExtensionsValidateDispose(seeNSObjectExtensions.ValidateDisposeabove). - WebAssembly:
Interop.ForceJavascriptInterop,UIElement.AssignDOMXamlName,UIElement.AssignDOMXamlProperties,UIElement.RenderToStringWithId,TextBlock.IsMeasureCacheEnabled,Shape.WasmDelayUpdateUntilFirstArrange,Shape.WasmCacheBBoxCalculationResult,Shape.WasmBBoxCacheSize,Cursors.UseHandForInteraction(the "hand" cursor for interactive controls is now never used). - Native (Android + iOS):
ListViewBase.AnimateScrollIntoView. - Native styling: the whole
Styleholder —Style.UseUWPDefaultStyles,Style.UseUWPDefaultStylesOverride,Style.SetUWPDefaultStylesOverride<TControl>(), andStyle.ConfigureNativeFrameNavigation(). The WinUI default styles are now the only ones. - Skia overlay:
TextBox.UseOverlayOnSkia.
WebView2.IsInspectable is also removed; it was an obsolete alias, so switch to
WebView2.EnableDevTools instead.
The cross-platform Control.UseLegacyContentAlignment flag is also removed. It opted into the
legacy Top/Left default for HorizontalContentAlignment/VerticalContentAlignment; the default is
now always the WinUI-correct Center/Center. Apps that set it to true should instead set
HorizontalContentAlignment/VerticalContentAlignment explicitly (via a Style or per control).
The cross-platform WinRTFeatureConfiguration.ApplicationLanguages.UseLegacyPrimaryLanguageOverride
flag is also removed. Unlike the flags above it defaulted to true, so this changes behavior for
apps that never touched it: setting Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride
no longer swaps CultureInfo.CurrentCulture/CurrentUICulture from inside the setter. The override
is persisted and applied to the culture on the next app start, matching WinUI. Resource lookup
(x:Uid, ResourceLoader) still follows the new value immediately, so localized strings keep
updating once the affected pages reload.
Apps that relied on the immediate culture swap — number, date, and currency formatting, or .NET
resource lookup through CurrentUICulture — should set the culture themselves alongside the
override:
ApplicationLanguages.PrimaryLanguageOverride = language;
var culture = new CultureInfo(language);
CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = culture;
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = culture;
If a single codebase must target both pre-7.0 and 7.0, guard the calls with #if.
Behavioral changes (same API, different result)
Because rendering moves from Canvas/CALayer/CSS to Skia, expect subtle differences and
re-baseline visual tests:
- Rendering: gradients, corner radii, shadows/elevation, and anti-aliasing render via Skia — minor pixel differences are possible.
- Text: measurement, line-breaking, bidi, kerning, and
MaxLinesgo through HarfBuzz + SkiaSharp instead ofStaticLayout/UIKit text/DOM — possible layout shifts in text-dense UI. - Lists / scrolling: managed virtualization and scrolling replace
RecyclerView/UICollectionView/CSS overflow — fling curves, edge glows, and snap points may differ. - IME / keyboard: canvas-rendered text input with a platform IME bridge replaces the
native
EditText/UITextField— re-test CJK/emoji composition and selection visuals. - Pickers / command bars / Frame: Skia/WinUI-styled rather than native-styled — appearance changes on Android/iOS.
- Animation timing: Skia interpolation replaces
CABasicAnimation— standard easing matches; exotic timing may not. ContentControlsubclasses without aControlTemplate: Uno used to bypass theContentPresenterand parentContentdirectly when a control had no template at all, so a customContentControlthat set its ownDefaultStyleKeybut shipped noTemplatesetter still rendered its content. That leniency is gone; as on WinUI, such a control now renders nothing. Give it a default style with aTemplate, or leaveDefaultStyleKeyalone so it inherits theContentControldefault template.
Independently of rendering, manipulation recognition was realigned with WinUI:
- No
ManipulationDeltaimmediately afterManipulationStarted. The distance travelled before a manipulation is recognized is now reported throughManipulationStarted'sCumulativeand is never replayed as a delta, so a pan no longer visibly jumps by the recognition threshold the moment it starts. This matches WinUI, where a manipulation can complete without raising a singleManipulationDelta. Handlers that positioned content by accumulatingManipulationDelta.Deltaalone (the classicGridSplitterpattern) will now under-track by the recognition distance — readCumulativeinstead, which still measures from the press point and is the value WinUI's own samples use. - Manipulation start thresholds changed. Touch and pen go from 15px to 10px, so panning
starts sooner. Mouse goes from 1px to 4px, matching the Win32 system drag threshold
(
SM_CXDRAG), so a small wiggle during a click is no longer reported as a drag. Code that relied on a near-zero mouse threshold to start a drag should re-test.
Type-hierarchy changes (WinUI parity)
7.0 realigns several types to their WinUI base classes. Most code is unaffected — the change only breaks code that used the Uno-only members leaked by the wrong base.
MediaPlayerPresenternow derives directly fromFrameworkElement(matching WinUI) instead ofBorder, removing the extraBorderlevel. TheBorder-only surface it used to expose —Child,Background,BorderBrush,BorderThickness,CornerRadius,Padding,BackgroundSizing,ChildTransitions— is gone, andis Borderis no longertruefor a presenter. The video surface is hosted internally, so playback andStretchare unchanged.MediaPlayerElementsets its ownBackground(black) on the template root, so letterbox bars stay black.ImageBrushnow derives from a realTileBrush(Brush → TileBrush → ImageBrush, matching WinUI) instead of directly fromBrush.AlignmentX,AlignmentY, andStretchbehave the same for callers but are now declared onTileBrush— code that referenced the static DPs by their declaring type (ImageBrush.StretchProperty,AlignmentXProperty,AlignmentYProperty) still resolves via inheritance, andis TileBrushis nowtruefor anImageBrush. Instance usage (imageBrush.Stretch, XAMLStretch="…") is unaffected.FadeInThemeAnimation/FadeOutThemeAnimationnow derive directly fromTimeline(matching WinUI) instead of Uno'sDoubleAnimation. TheDoubleAnimation-only members they used to inherit —From,To,By,EasingFunction, andEnableDependentAnimation— are gone; WinUI never exposed them on these theme animations.TargetNameand theTimelinemembers (Duration,BeginTime,RepeatBehavior,FillBehavior) are unchanged. Set onlyTargetName(as the built-in styles do); the fade always animatesOpacityto its fixed target (1 for fade-in, 0 for fade-out).PasswordBoxnow derives directly fromControl(matching WinUI) instead ofTextBox, andis TextBoxis no longertruefor a password box. TheTextBox-only surface it used to inherit is gone —Text,TextChanged,TextChanging,BeforeTextChanging,SelectedText,SelectionStart,SelectionLength,IsReadOnly,AcceptsReturn,TextWrapping,TextAlignment,IsSpellCheckEnabled,CanUndo/CanRedo,Undo()/Redo(),CopySelectionToClipboard()/CutSelectionToClipboard(), andProofingMenuFlyout. None of these exists on WinUI'sPasswordBox, so code written against WinUI is unaffected. UsePasswordto read or write the value andPasswordChangedin place ofTextChanged; the password is no longer reachable as text. Everything WinUI does expose —PasswordChar,PasswordRevealMode,Header,HeaderTemplate,PlaceholderText,Description,InputScope,SelectionHighlightColor,SelectionFlyout,CanPasteClipboardContent,ContextMenuOpening,Paste,SelectAll(),PasteFromClipboard()— behaves as before; it is declared onPasswordBoxitself now rather than inherited.MaxLengthnow limitsPassworddirectly: it previously applied only through the inheritedTextmirror, so an over-long value assigned toPasswordwas accepted while the mirror rejected it.BeforeTextChanginghas no replacement.PasswordChangingremains unimplemented, and itsPasswordBoxPasswordChangingEventArgscarries noCancelmember, so it would not substitute even once implemented — nothing can veto password input. Bound the length withMaxLengthand validate after the fact inPasswordChanged. Dropping the inheritedIsSpellCheckEnabledalso stops a password box spell-checking its own masked text, which removes the squiggly underline it used to draw.
WinRT projection alignment (WinUI parity)
A few WinRT members were projected differently by Uno than by WinUI's own C# projection. 7.0 aligns them. Most call sites stay source-compatible, but the underlying signatures change, so recompile against 7.0 rather than swapping assemblies in place.
ThicknessandCornerRadiusmembers are properties, not fields.Thickness.Left,Top,Right,BottomandCornerRadius.TopLeft,TopRight,BottomRight,BottomLeftare{ get; set; }properties, as they are in WinUI. Reading and assigning them is unchanged; what no longer compiles is passing one by reference:- Normalize(ref thickness.Left); + thickness.Left = Normalize(thickness.Left);The same applies to
Uno.UI.Composition.ThicknessandUno.UI.Composition.CornerRadius.GridLength.ValueandGridLength.GridUnitTypeare read-only properties. WinUI exposes both as get-only, so aGridLengthis built entirely through its constructor. Code that mutated adefaultinstance has to construct one instead:- GridLength star = default; - star.GridUnitType = GridUnitType.Star; - star.Value = 1.0; + GridLength star = new(1.0, GridUnitType.Star);GridLength's second constructor parameter is renamed totype. WinUI's signature isGridLength(double value, GridUnitType type); Uno called itgridUnitType. Only named arguments are affected:- new GridLength(1.0, gridUnitType: GridUnitType.Star) + new GridLength(1.0, type: GridUnitType.Star)Duration.TimeSpanis a read-only property. WinUI exposes it as get-only, so aDurationcarrying a time span comes from the constructor. Assigning the old field left the duration in itsAutomaticdefault state, soHasTimeSpanstayedfalse; the constructor sets both:- Duration d = default; - d.TimeSpan = TimeSpan.FromSeconds(2); + Duration d = new(TimeSpan.FromSeconds(2));Duration.Typeis no longer public. WinUI'sDurationexposes noTypemember at all; the state is reached throughHasTimeSpanand comparison againstDuration.AutomaticandDuration.Forever. TheDurationTypeenum itself stays public, as it is in WinUI:- if (d.Type == DurationType.TimeSpan) { } + if (d.HasTimeSpan) { } - if (d.Type == DurationType.Forever) { } + if (d == Duration.Forever) { }DataTemplate.LoadContent()returnsDependencyObject. WinUI's return type isDependencyObject, notUIElement. Pattern-matching call sites (is FrameworkElement fe,as UIElement) are unaffected; an explicitly typed local needs a cast:- UIElement root = template.LoadContent(); + UIElement root = (UIElement)template.LoadContent();Windows.Foundation.GuidHelper.Equalstakesin Guidinstead ofref Guid.inarguments are passed without a keyword, so droprefat the call site:- GuidHelper.Equals(ref first, ref second); + GuidHelper.Equals(first, second);
XAML changes
The WPF-style
clr-namespace:xmlns form is no longer accepted. WinUI only supportsusing:; Uno used to also acceptclr-namespace:MyApp.Controls;assembly=MyLiband silently strip the prefix and the;assembly=token. Replace each declaration with theusing:form — the assembly is inferred, so the;assembly=part is simply dropped:- xmlns:local="clr-namespace:MyApp.Controls;assembly=MyLib" + xmlns:local="using:MyApp.Controls"This is enforced at build time (the
UXAML0006diagnostic) and at run time byXamlReader.Loadand Hot Reload, which throw aXamlParseException. The declaration itself is rejected whether or not the prefix is used, so unusedclr-namespace:declarations must also be removed — the only exemption is a prefix listed inmc:Ignorableon the root element.A relative URI on a
Uri-typed property now compiles toms-resource:///Files/…, the MRT local-resource form WinUI produces. Previously Uno emitted the relative string verbatim. This affects customUriproperties,HyperlinkButton.NavigateUri,Hyperlink.NavigateUri,BitmapIcon.UriSource,BitmapIconSource.UriSource,WebView2.Source,MediaPlayerElement.Source,LottieVisualSource.UriSource, and theUripassed toRandomAccessStreamReference.CreateFromUri:<BitmapIcon UriSource="Assets/icon.png" /> - // compiled value: Assets/icon.png + // compiled value: ms-resource:///Files/Assets/icon.pngThe rewrite is a prefix concat that drops a leading
/and ignores the folder of the XAML file containing it, exactly as WinUI does. Images declared in application XAML keep loading the same asset — Uno resolvesms-resource:///Files/Xasms-appx:///X. To keep a value verbatim, write it as an explicitms-appx:///…URI in XAML.Only the four image sinks Uno already resolved as assets carry that mapping. The properties above that never resolved a relative URI stay inert with the new value, exactly as they are on WinUI — a hand-written
ms-resource:///Files/…is treated the same as the compiler-generated form, since that is the MRT local-file namespace and Uno ships no MRT to resolve it any other way.Two changes go beyond the value you read back:
- A relative value is now absolute at the point of use.
WebView2.Sourcewith a relative value used to throwArgumentExceptionwhile the page initialized; it now navigates ams-resource:URI no web view can service, so the failure is silent instead of loud. ANavigateUriwritten without a scheme (www.example.com) likewise becomesms-resource:///Files/www.example.comand reaches the launcher as an unregistered scheme, and a relativeControl.DefaultStyleResourceUrino longer throws while resolving. Give these properties absolute URIs —https://…,ms-appx:///…. - In a library, relative URIs resolve differently per property type. On an
ImageSource-typed property the value now carries the library's assembly prefix (ms-appx:///MyLib/Assets/x.png), so a library's own assets resolve — but a library asset that previously resolved from the consuming app's root no longer does. On aUri-typed property thems-resourceform cannot express that prefix, so it resolves against the app root; a library shipping assets forBitmapIconmust use an explicitms-appx:///MyLib/…URI.
Properties typed
ImageSourceare unaffected in shape but now resolve consistently:Image.Source,ImageBrush.ImageSource, and customImageSourceproperties all resolve a relative URI against the base URI.ImageBrush.ImageSourceand customImageSourceproperties previously kept the relative string.ResourceDictionary.Sourceis unchanged.Two divergences from WinUI remain here, both deliberate:
- The base URI is the assembly root, not the XAML file's folder.
Assets/logo.pngwritten inViews/MainPage.xamlresolves asms-appx:///Assets/logo.pngon Uno andms-appx:///Views/Assets/logo.pngon WinUI. This predates the rewrite and is unchanged by it. - Svg assets keep the
ms-appx:///form. Measured on WinAppSDK 1.7, WinUI compiles every relative svg URI toms-resource:///Files/…— including one written onImage.Sourcerather than onSvgImageSource.UriSource. Uno keepsms-appx:///, because that form is resolved as an asset path and so carries the assembly prefix a library's own svg assets need; thems-resourceform resolves only against the application root.
- A relative value is now absolute at the point of use.
The Fluent theme-resources dictionary is now
themeresources.xaml, matching WinUI. Itsms-appx:URI changes accordingly, so a dictionary that merged the old path by hand no longer resolves. MergingXamlControlsResources— the supported way to load the Fluent styles — is unaffected, since it resolves the URI internally:- <ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml/Themes/themeresources_v2.xaml" /> + <ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml/Themes/themeresources.xaml" />The
_v2suffix was an Uno-only artifact of the Fluent V1/V2 split; with V1 removed there is one theme-resources dictionary, under the name WinUI itself uses.
Android head uses the host builder
The Android head now builds its host through UnoPlatformHostBuilder, like every other target.
Microsoft.UI.Xaml.NativeApplication is now abstract; its AppBuilder delegate type and the
constructor taking one have been removed, replaced by an abstract CreateHost() method.
// Before
public class Application : Microsoft.UI.Xaml.NativeApplication
{
public Application(IntPtr javaReference, JniHandleOwnership transfer)
: base(() => new App(), javaReference, transfer)
{
}
}
// After
using Uno.UI.Hosting;
public class Application : Microsoft.UI.Xaml.NativeApplication
{
public Application(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected override UnoPlatformHost CreateHost() =>
UnoPlatformHostBuilder.Create()
.App(() => new App())
.UseAndroid()
.Build();
}
Two related behavior changes:
- Startup failures now propagate. Previously an exception during Android host initialization was written to the console and swallowed, leaving a blank screen. It is now logged and rethrown. Apps with latent initialization failures that used to start degraded will now crash on launch.
Uno.UI.Runtime.Skia.Android.AndroidHostis nowinternal. UseUseAndroid()instead of constructing it.
See Customizing the Application class on Android.
Templates and project heads
New apps get Skia heads only. Existing apps should drop native *.Mobile / native
*.Wasm (DOM) heads in favor of the Skia heads (Skia.netcoremobile,
Skia.WebAssembly.Browser, and the desktop Skia head) and remove native bootstrap code.
Migration checklist
- Remove
<UnoFeatures>skiarenderer</UnoFeatures>(now implicit) — and any native-only feature switches. - Retarget any
net*-maccatalysthead tonet10.0-desktopand deletePlatforms/MacCatalyst/. - Recompile every Uno-dependent library against 7.0.
- Re-test
#if __IOS__/#if __ANDROID__code andios:/android:XAML inside multi-targeted libraries — those assets are now deployed on mobile heads instead of being replaced by thenetX.0asset. - Remove references to the deleted assemblies/types and to native element hosting.
- Delete native-only
FeatureConfigurationcalls. - Replace the WASM DOM head with the Skia WebAssembly Browser head; remove any DOM/CSS
customization and
HtmlElementusage. - Remove manual
ConfigureUniversalImageLoader();(Android) and other native bootstrap. - Convert every
xmlns:…="clr-namespace:…"declaration in your XAML to theusing:form. - Convert the Android
Applicationclass to overrideCreateHost()instead of passing anAppBuilderdelegate to the base constructor. - Rename
Uno.UI.Toolkitusings andxmlnsdeclarations to their newUno.UI.*namespaces. - Update assembly-qualified type names that reach MRT Core (
Microsoft.Windows.ApplicationModel.Resources.*) — the assembly is nowUno.WinRT, notUno.UI. - Retype
Window.VisibilityChangedhandlers toWindowVisibilityChangedEventArgs, and drop any explicitWindow*EventHandlerdelegate construction. - Re-baseline visual/snapshot tests and re-test text, lists/scroll, IME, pickers, and safe-area/notch handling on devices.
See the Uno 6.0 migration guide for the full Android/iOS/WebAssembly Skia bootstrapping steps.