diff --git a/src/Core/AppleNative/ApiDefinition.cs b/src/Core/AppleNative/ApiDefinition.cs
new file mode 100644
index 000000000000..c563d5b48e2a
--- /dev/null
+++ b/src/Core/AppleNative/ApiDefinition.cs
@@ -0,0 +1,30 @@
+using System;
+using CoreAnimation;
+using Foundation;
+using ObjCRuntime;
+
+namespace Microsoft.Maui.Platform
+{
+ ///
+ /// Behavior that automatically resizes a CALayer to match its superlayer's bounds
+ ///
+ [BaseType(typeof(NSObject), Name = "MauiCALayerAutosizeToSuperLayerBehavior")]
+ [Internal]
+ interface MauiCALayerAutosizeToSuperLayerBehavior
+ {
+ ///
+ /// Attaches this behavior to the given layer.
+ /// The layer must have a superlayer when this method is called.
+ /// The layer's frame will be kept in sync with the superlayer's bounds.
+ ///
+ /// The layer that needs to be resized to match the superlayer's bounds.
+ [Export("attachWithLayer:")]
+ MauiCALayerAutosizeToSuperLayerResult Attach(CALayer layer);
+
+ ///
+ /// Detaches this behavior from the current layer and stops observing
+ ///
+ [Export("detach")]
+ void Detach();
+ }
+}
\ No newline at end of file
diff --git a/src/Core/AppleNative/PlatformInterop/.gitignore b/src/Core/AppleNative/PlatformInterop/.gitignore
new file mode 100644
index 000000000000..ffaa87ce641f
--- /dev/null
+++ b/src/Core/AppleNative/PlatformInterop/.gitignore
@@ -0,0 +1,5 @@
+Framework/catalyst.xcarchive
+Framework/ios_devices.xcarchive
+Framework/ios_simulator.xcarchive
+MauiPlatformInterop.xcodeproj/xcuserdata
+MauiPlatformInterop.xcodeproj/project.xcworkspace/xcuserdata
diff --git a/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcframework.zip b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcframework.zip
new file mode 100644
index 000000000000..6e472ab6d25f
Binary files /dev/null and b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcframework.zip differ
diff --git a/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.pbxproj b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.pbxproj
new file mode 100644
index 000000000000..184265e35449
--- /dev/null
+++ b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.pbxproj
@@ -0,0 +1,358 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXFileReference section */
+ AB0B2BC82E3529AF00B2B77C /* MauiPlatformInterop.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MauiPlatformInterop.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ AB0B2BCA2E3529AF00B2B77C /* MauiPlatformInterop */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = MauiPlatformInterop;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ AB0B2BC52E3529AE00B2B77C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ AB0B2BBE2E3529AE00B2B77C = {
+ isa = PBXGroup;
+ children = (
+ AB0B2BCA2E3529AF00B2B77C /* MauiPlatformInterop */,
+ AB0B2BC92E3529AF00B2B77C /* Products */,
+ );
+ sourceTree = "";
+ };
+ AB0B2BC92E3529AF00B2B77C /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ AB0B2BC82E3529AF00B2B77C /* MauiPlatformInterop.framework */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ AB0B2BC32E3529AE00B2B77C /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ AB0B2BC72E3529AE00B2B77C /* MauiPlatformInterop */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = AB0B2BCE2E3529AF00B2B77C /* Build configuration list for PBXNativeTarget "MauiPlatformInterop" */;
+ buildPhases = (
+ AB0B2BC32E3529AE00B2B77C /* Headers */,
+ AB0B2BC42E3529AE00B2B77C /* Sources */,
+ AB0B2BC52E3529AE00B2B77C /* Frameworks */,
+ AB0B2BC62E3529AE00B2B77C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ AB0B2BCA2E3529AF00B2B77C /* MauiPlatformInterop */,
+ );
+ name = MauiPlatformInterop;
+ packageProductDependencies = (
+ );
+ productName = MauiPlatformInterop;
+ productReference = AB0B2BC82E3529AF00B2B77C /* MauiPlatformInterop.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ AB0B2BBF2E3529AE00B2B77C /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastUpgradeCheck = 1640;
+ TargetAttributes = {
+ AB0B2BC72E3529AE00B2B77C = {
+ CreatedOnToolsVersion = 16.4;
+ };
+ };
+ };
+ buildConfigurationList = AB0B2BC22E3529AE00B2B77C /* Build configuration list for PBXProject "MauiPlatformInterop" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = AB0B2BBE2E3529AE00B2B77C;
+ minimizedProjectReferenceProxies = 1;
+ preferredProjectObjectVersion = 77;
+ productRefGroup = AB0B2BC92E3529AF00B2B77C /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ AB0B2BC72E3529AE00B2B77C /* MauiPlatformInterop */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ AB0B2BC62E3529AE00B2B77C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ AB0B2BC42E3529AE00B2B77C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ AB0B2BCF2E3529AF00B2B77C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = NO;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_MODULE_VERIFIER = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=iphoneos*]" = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+ PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.maui.MauiPlatformInterop;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ AB0B2BD02E3529AF00B2B77C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = NO;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_MODULE_VERIFIER = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=iphoneos*]" = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+ PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.maui.MauiPlatformInterop;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ AB0B2BD12E3529AF00B2B77C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ AB0B2BD22E3529AF00B2B77C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ AB0B2BC22E3529AE00B2B77C /* Build configuration list for PBXProject "MauiPlatformInterop" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ AB0B2BD12E3529AF00B2B77C /* Debug */,
+ AB0B2BD22E3529AF00B2B77C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ AB0B2BCE2E3529AF00B2B77C /* Build configuration list for PBXNativeTarget "MauiPlatformInterop" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ AB0B2BCF2E3529AF00B2B77C /* Debug */,
+ AB0B2BD02E3529AF00B2B77C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = AB0B2BBF2E3529AE00B2B77C /* Project object */;
+}
diff --git a/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000000..919434a6254f
--- /dev/null
+++ b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000000..0c67376ebacb
--- /dev/null
+++ b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/xcshareddata/xcschemes/MauiPlatformInterop.xcscheme b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/xcshareddata/xcschemes/MauiPlatformInterop.xcscheme
new file mode 100644
index 000000000000..ea4aa4a88c73
--- /dev/null
+++ b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop.xcodeproj/xcshareddata/xcschemes/MauiPlatformInterop.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop/CALayerAutosizeToSuperLayerBehavior.swift b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop/CALayerAutosizeToSuperLayerBehavior.swift
new file mode 100644
index 000000000000..f10374f9c77c
--- /dev/null
+++ b/src/Core/AppleNative/PlatformInterop/MauiPlatformInterop/CALayerAutosizeToSuperLayerBehavior.swift
@@ -0,0 +1,58 @@
+import Foundation
+import QuartzCore
+
+/// Result codes for MauiCALayerAutosizeToSuperLayerBehavior
+@objc
+public enum MauiCALayerAutosizeToSuperLayerResult: Int {
+ case success = 0
+ case missingSuperlayer = 1
+}
+
+/// Behavior that automatically resizes a CALayer to match its superlayer's bounds
+@objc(MauiCALayerAutosizeToSuperLayerBehavior)
+public class MauiCALayerAutosizeToSuperLayerBehavior: NSObject {
+
+ private var observation: NSKeyValueObservation?
+
+ /// Attaches this behavior to the given layer
+ /// The layer must have a superlayer when this method is called
+ /// The layer's frame will be kept in sync with the superlayer's bounds.
+ /// - Parameter layer: The layer that needs to be resized to match the superlayer's bounds.
+ @objc
+ @discardableResult
+ public func attach(layer: CALayer) -> MauiCALayerAutosizeToSuperLayerResult {
+ // Detach from any previous layer
+ detach()
+
+ guard let superLayer = layer.superlayer else {
+ return .missingSuperlayer
+ }
+
+ // Set initial frame to match superlayer bounds
+ layer.frame = superLayer.bounds
+
+ // Observe superlayer's bounds using modern block-based KVO
+ // Note: CALayer is not Sendable in Swift 6, which triggers a compiler warning.
+ // This is safe because:
+ // 1. KVO change handlers for UI objects are delivered on the main thread
+ // 2. The weak capture prevents retain cycles
+ // 3. CALayer operations are inherently main-thread bound
+ observation = superLayer.observe(\.bounds, options: .new) { [weak layer] observedLayer, _ in
+ guard let layer = layer else { return }
+ layer.frame = observedLayer.bounds
+ }
+
+ return .success
+ }
+
+ /// Detaches this behavior from the current layer, stopping the automatic resizing.
+ @objc
+ public func detach() {
+ observation?.invalidate()
+ observation = nil
+ }
+
+ deinit {
+ detach()
+ }
+}
diff --git a/src/Core/AppleNative/PlatformInterop/build-xcframework.sh b/src/Core/AppleNative/PlatformInterop/build-xcframework.sh
new file mode 100755
index 000000000000..bf50cfa75801
--- /dev/null
+++ b/src/Core/AppleNative/PlatformInterop/build-xcframework.sh
@@ -0,0 +1,63 @@
+#!/bin/bash -e
+
+PROJECT="MauiPlatformInterop"
+CONFIGURATION="Release"
+SCHEME=$PROJECT
+FRAMEWORK_NAME=$PROJECT
+OUTPUT_DIR="Framework"
+XCFRAMEWORK_DIR="${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework"
+
+rm -rf "$OUTPUT_DIR"
+rm -rf "${FRAMEWORK_NAME}.xcframework.zip"
+mkdir -p "$OUTPUT_DIR"
+
+# iOS Device
+xcodebuild archive \
+ -project "${PROJECT}.xcodeproj" \
+ -scheme "$SCHEME" \
+ -configuration "$CONFIGURATION" \
+ -destination "generic/platform=iOS" \
+ -archivePath "$OUTPUT_DIR/ios_devices.xcarchive" \
+ -sdk iphoneos \
+ SKIP_INSTALL=NO \
+ BUILD_LIBRARY_FOR_DISTRIBUTION=YES
+
+# iOS Simulator
+xcodebuild archive \
+ -project "${PROJECT}.xcodeproj" \
+ -scheme "$SCHEME" \
+ -configuration "$CONFIGURATION" \
+ -destination "generic/platform=iOS Simulator" \
+ -archivePath "$OUTPUT_DIR/ios_simulator.xcarchive" \
+ -sdk iphonesimulator \
+ SKIP_INSTALL=NO \
+ BUILD_LIBRARY_FOR_DISTRIBUTION=YES
+
+# Catalyst
+xcodebuild archive \
+ -project "${PROJECT}.xcodeproj" \
+ -scheme "$SCHEME" \
+ -configuration "$CONFIGURATION" \
+ -destination "platform=macOS,variant=Mac Catalyst" \
+ -archivePath "$OUTPUT_DIR/catalyst.xcarchive" \
+ -sdk macosx \
+ SKIP_INSTALL=NO \
+ BUILD_LIBRARY_FOR_DISTRIBUTION=YES
+
+# Combine into an XCFramework
+xcodebuild -create-xcframework \
+ -framework "$OUTPUT_DIR/ios_devices.xcarchive/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \
+ -framework "$OUTPUT_DIR/ios_simulator.xcarchive/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \
+ -framework "$OUTPUT_DIR/catalyst.xcarchive/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \
+ -output "$XCFRAMEWORK_DIR"
+
+# Zip the $XCFRAMEWORK_DIR directory
+# Create zip using ditto (preserves metadata better than zip for frameworks)
+(
+ cd "$OUTPUT_DIR"
+ ditto -c -k --sequesterRsrc --keepParent "${FRAMEWORK_NAME}.xcframework" "../${FRAMEWORK_NAME}.xcframework.zip"
+)
+
+rm -rf "$OUTPUT_DIR"
+
+
diff --git a/src/Core/AppleNative/StructsAndEnums.cs b/src/Core/AppleNative/StructsAndEnums.cs
new file mode 100644
index 000000000000..b09a8bf15315
--- /dev/null
+++ b/src/Core/AppleNative/StructsAndEnums.cs
@@ -0,0 +1,14 @@
+using ObjCRuntime;
+
+namespace Microsoft.Maui.Platform
+{
+ ///
+ /// Result codes for MauiCALayerAutosizeToSuperLayerBehavior
+ ///
+ [Native]
+ enum MauiCALayerAutosizeToSuperLayerResult : long
+ {
+ Success = 0,
+ MissingSuperlayer = 1
+ }
+}
\ No newline at end of file
diff --git a/src/Core/src/Core.csproj b/src/Core/src/Core.csproj
index 9c3b44e4fdb1..43c5de929746 100644
--- a/src/Core/src/Core.csproj
+++ b/src/Core/src/Core.csproj
@@ -23,6 +23,10 @@
true
+
+ true
+
+
true
@@ -61,6 +65,13 @@
+
+
+ Static
+
+
+
+
@@ -87,6 +98,9 @@
+
+
+
diff --git a/src/Core/src/Platform/iOS/CALayerAutosizeObserver.cs b/src/Core/src/Platform/iOS/CALayerAutosizeObserver.cs
deleted file mode 100644
index 905946867b66..000000000000
--- a/src/Core/src/Platform/iOS/CALayerAutosizeObserver.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using CoreAnimation;
-using CoreGraphics;
-using Foundation;
-
-namespace Microsoft.Maui.Platform;
-
-[Register("MauiCALayerAutosizeObserver")]
-class CALayerAutosizeObserver : NSObject
-{
- static readonly NSString _boundsKey = new("bounds");
-
- readonly WeakReference _layerReference;
- bool _disposed;
-
- public static CALayerAutosizeObserver Attach(CALayer layer)
- {
- _ = layer ?? throw new ArgumentNullException(nameof(layer));
-
- var superLayer = layer.SuperLayer ?? throw new InvalidOperationException("SuperLayer should be set before creating CALayerAutosizeObserver");
- var observer = new CALayerAutosizeObserver(layer);
- superLayer.AddObserver(observer, _boundsKey, NSKeyValueObservingOptions.New, observer.Handle);
- layer.Frame = superLayer.Bounds;
- return observer;
- }
-
- private CALayerAutosizeObserver(CALayer layer)
- {
- _layerReference = new WeakReference(layer);
- IsDirectBinding = false;
- }
-
- [Preserve(Conditional = true)]
- public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
- {
- if (!_disposed && keyPath == _boundsKey && context == Handle && _layerReference.TryGetTarget(out var layer))
- {
- layer.Frame = layer.SuperLayer?.Bounds ?? CGRect.Empty;
- }
- }
-
- protected override void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- _disposed = true;
-
- if (_layerReference.TryGetTarget(out var layer))
- {
- layer?.SuperLayer?.RemoveObserver(this, _boundsKey);
- }
- }
-
- base.Dispose(disposing);
- }
-}
diff --git a/src/Core/src/Platform/iOS/MauiCALayer.cs b/src/Core/src/Platform/iOS/MauiCALayer.cs
index 75472b966efa..f2d6ead0e4a0 100644
--- a/src/Core/src/Platform/iOS/MauiCALayer.cs
+++ b/src/Core/src/Platform/iOS/MauiCALayer.cs
@@ -11,8 +11,12 @@ namespace Microsoft.Maui.Platform
{
public class MauiCALayer : CALayer, IAutoSizableCALayer
{
+ [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in MauiCALayerAutosizeToSuperLayerBehavior_DoesNotLeak test.")]
+ readonly MauiCALayerAutosizeToSuperLayerBehavior _autosizeToSuperLayerBehavior = new();
+
CGRect _bounds;
WeakReference _shape;
+
UIColor? _backgroundColor;
Paint? _background;
@@ -29,9 +33,6 @@ public class MauiCALayer : CALayer, IAutoSizableCALayer
nfloat _strokeMiterLimit;
- [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in CALayerAutosizeObserver_DoesNotLeak test.")]
- CALayerAutosizeObserver? _boundsObserver;
-
public MauiCALayer()
{
_bounds = new CGRect();
@@ -41,22 +42,19 @@ public MauiCALayer()
protected override void Dispose(bool disposing)
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.Dispose(disposing);
}
public override void RemoveFromSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.RemoveFromSuperLayer();
}
void IAutoSizableCALayer.AutoSizeToSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = CALayerAutosizeObserver.Attach(this);
+ _autosizeToSuperLayerBehavior.AttachOrThrow(this);
}
public override void AddAnimation(CAAnimation animation, string? key)
diff --git a/src/Core/src/Platform/iOS/MauiCALayerAutosizeToSuperLayerBehaviorExtensions.cs b/src/Core/src/Platform/iOS/MauiCALayerAutosizeToSuperLayerBehaviorExtensions.cs
new file mode 100644
index 000000000000..75706ed24b84
--- /dev/null
+++ b/src/Core/src/Platform/iOS/MauiCALayerAutosizeToSuperLayerBehaviorExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+using CoreAnimation;
+
+namespace Microsoft.Maui.Platform;
+
+internal static class MauiCALayerAutosizeToSuperLayerBehaviorExtensions
+{
+ public static void AttachOrThrow(this MauiCALayerAutosizeToSuperLayerBehavior behavior, CALayer layer)
+ {
+ var result = behavior.Attach(layer);
+
+ if (result != MauiCALayerAutosizeToSuperLayerResult.Success)
+ {
+ throw new InvalidOperationException($"Failed to attach MauiCALayerAutosizeToSuperLayerBehavior: {result}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/src/Platform/iOS/StaticCAGradientLayer.cs b/src/Core/src/Platform/iOS/StaticCAGradientLayer.cs
index 0d8775e1c236..65e7311e87a0 100644
--- a/src/Core/src/Platform/iOS/StaticCAGradientLayer.cs
+++ b/src/Core/src/Platform/iOS/StaticCAGradientLayer.cs
@@ -5,27 +5,24 @@ namespace Microsoft.Maui.Platform;
class StaticCAGradientLayer : CAGradientLayer, IAutoSizableCALayer
{
- [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in CALayerAutosizeObserver_DoesNotLeak test.")]
- CALayerAutosizeObserver? _boundsObserver;
+ [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in MauiCALayerAutosizeToSuperLayerBehavior_DoesNotLeak test.")]
+ readonly MauiCALayerAutosizeToSuperLayerBehavior _autosizeToSuperLayerBehavior = new();
protected override void Dispose(bool disposing)
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.Dispose(disposing);
}
public override void RemoveFromSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.RemoveFromSuperLayer();
}
void IAutoSizableCALayer.AutoSizeToSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = CALayerAutosizeObserver.Attach(this);
+ _autosizeToSuperLayerBehavior.AttachOrThrow(this);
}
public override void AddAnimation(CAAnimation animation, string? key)
diff --git a/src/Core/src/Platform/iOS/StaticCALayer.cs b/src/Core/src/Platform/iOS/StaticCALayer.cs
index f5eeeeddc287..f08a59ce877b 100644
--- a/src/Core/src/Platform/iOS/StaticCALayer.cs
+++ b/src/Core/src/Platform/iOS/StaticCALayer.cs
@@ -5,27 +5,24 @@ namespace Microsoft.Maui.Platform;
class StaticCALayer : CALayer, IAutoSizableCALayer
{
- [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in CALayerAutosizeObserver_DoesNotLeak test.")]
- CALayerAutosizeObserver? _boundsObserver;
+ [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in MauiCALayerAutosizeToSuperLayerBehavior_DoesNotLeak test.")]
+ readonly MauiCALayerAutosizeToSuperLayerBehavior _autosizeToSuperLayerBehavior = new();
protected override void Dispose(bool disposing)
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.Dispose(disposing);
}
public override void RemoveFromSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.RemoveFromSuperLayer();
}
void IAutoSizableCALayer.AutoSizeToSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = CALayerAutosizeObserver.Attach(this);
+ _autosizeToSuperLayerBehavior.AttachOrThrow(this);
}
public override void AddAnimation(CAAnimation animation, string? key)
diff --git a/src/Core/src/Platform/iOS/StaticCAShapeLayer.cs b/src/Core/src/Platform/iOS/StaticCAShapeLayer.cs
index 6d753e641b2f..5c767e8b3e72 100644
--- a/src/Core/src/Platform/iOS/StaticCAShapeLayer.cs
+++ b/src/Core/src/Platform/iOS/StaticCAShapeLayer.cs
@@ -5,27 +5,24 @@ namespace Microsoft.Maui.Platform;
class StaticCAShapeLayer : CAShapeLayer, IAutoSizableCALayer
{
- [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in CALayerAutosizeObserver_DoesNotLeak test.")]
- CALayerAutosizeObserver? _boundsObserver;
+ [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in MauiCALayerAutosizeToSuperLayerBehavior_DoesNotLeak test.")]
+ readonly MauiCALayerAutosizeToSuperLayerBehavior _autosizeToSuperLayerBehavior = new();
protected override void Dispose(bool disposing)
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.Dispose(disposing);
}
public override void RemoveFromSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = null;
+ _autosizeToSuperLayerBehavior.Detach();
base.RemoveFromSuperLayer();
}
void IAutoSizableCALayer.AutoSizeToSuperLayer()
{
- _boundsObserver?.Dispose();
- _boundsObserver = CALayerAutosizeObserver.Attach(this);
+ _autosizeToSuperLayerBehavior.AttachOrThrow(this);
}
public override void AddAnimation(CAAnimation animation, string? key)
diff --git a/src/Core/tests/DeviceTests/Memory/CALayerAutosizeObserverTests.cs b/src/Core/tests/DeviceTests/Memory/CALayerAutosizeToSuperLayerBehaviorTests.cs
similarity index 77%
rename from src/Core/tests/DeviceTests/Memory/CALayerAutosizeObserverTests.cs
rename to src/Core/tests/DeviceTests/Memory/CALayerAutosizeToSuperLayerBehaviorTests.cs
index c1e7a67b1f7b..66113a7ed6bf 100644
--- a/src/Core/tests/DeviceTests/Memory/CALayerAutosizeObserverTests.cs
+++ b/src/Core/tests/DeviceTests/Memory/CALayerAutosizeToSuperLayerBehaviorTests.cs
@@ -12,14 +12,14 @@ namespace Microsoft.Maui.DeviceTests.Memory
{
// Set of tests to verify auto-sizing layers do not leak
[Category(TestCategory.Memory)]
- public class CALayerAutosizeObserverTests : TestBase
+ public class MauiCALayerAutosizeToSuperLayerBehaviorTests : TestBase
{
[Theory]
[InlineData(typeof(MauiCALayer))]
[InlineData(typeof(StaticCALayer))]
[InlineData(typeof(StaticCAGradientLayer))]
[InlineData(typeof(StaticCAShapeLayer))]
- public async Task CALayerAutosizeObserver_DoesNotLeak([DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type sublayerType)
+ public async Task MauiCALayerAutosizeToSuperLayerBehavior_DoesNotLeak([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type sublayerType)
{
WeakReference viewReference = null;
WeakReference layerReference = null;
@@ -39,6 +39,8 @@ await InvokeOnMainThreadAsync(() =>
sublayer.AutoSizeToSuperLayer();
view.Frame = new CoreGraphics.CGRect(0, 0, 100, 100);
+
+ view.AttachAndRun(() => view.Frame = new CoreGraphics.CGRect(0, 0, 200, 200));
});
await AssertionExtensions.WaitForGC(viewReference, layerReference, sublayerReference);