From eb6df4fbeb38d278b45ecb70fe59582a1913c864 Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 14:27:44 +0200 Subject: [PATCH 1/2] Fix CAFrameRateRangeDefault unavailable on iOS 14 --- .../CMPUIKitUtils.xcodeproj/project.pbxproj | 6 +++++ .../CMPUIKitUtils/CMPFrameRateRange.h | 24 +++++++++++++++++ .../CMPUIKitUtils/CMPFrameRateRange.m | 27 +++++++++++++++++++ .../CMPUIKitUtils/CMPUIKitUtils.h | 1 + .../ui/window/DisplayLinkFrameRate.ios.kt | 6 ++--- 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h create mode 100644 compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m diff --git a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj index ae6f7b272af9d..cc39669877135 100644 --- a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj +++ b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 55F0AA132F70000100ABC123 /* CMPFrameRateRange.m in Sources */ = {isa = PBXBuildFile; fileRef = 55F0AA122F70000100ABC123 /* CMPFrameRateRange.m */; }; 99009B7A2F322B4700518C1F /* CMPMetalLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 99009B792F322B4700518C1F /* CMPMetalLayer.m */; }; 99009B7B2F322B4700518C1F /* CMPMetalLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 99009B792F322B4700518C1F /* CMPMetalLayer.m */; }; 991A97F72E1FB99300B47130 /* CMPScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 991A97F62E1FB99300B47130 /* CMPScrollView.m */; }; @@ -79,6 +80,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 55F0AA112F70000100ABC123 /* CMPFrameRateRange.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPFrameRateRange.h; sourceTree = ""; }; + 55F0AA122F70000100ABC123 /* CMPFrameRateRange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMPFrameRateRange.m; sourceTree = ""; }; 99009B782F322B4700518C1F /* CMPMetalLayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPMetalLayer.h; sourceTree = ""; }; 99009B792F322B4700518C1F /* CMPMetalLayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMPMetalLayer.m; sourceTree = ""; }; 991A97F52E1FB99300B47130 /* CMPScrollView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPScrollView.h; sourceTree = ""; }; @@ -176,6 +179,8 @@ children = ( C4C07E832F57037300A9DC94 /* CMPEditMenuCustomAction.h */, C4C07E842F57037300A9DC94 /* CMPEditMenuCustomAction.m */, + 55F0AA112F70000100ABC123 /* CMPFrameRateRange.h */, + 55F0AA122F70000100ABC123 /* CMPFrameRateRange.m */, C4C07E852F57037300A9DC94 /* CMPTextInputStringTokenizer.h */, C4C07E862F57037300A9DC94 /* CMPTextInputStringTokenizer.m */, C4C07E872F57037300A9DC94 /* CMPTextInputView.h */, @@ -402,6 +407,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 55F0AA132F70000100ABC123 /* CMPFrameRateRange.m in Sources */, 997DFCDE2B18D135000B56B5 /* CMPViewController.m in Sources */, 9968C38B2D7892DF005E8DE4 /* CMPScreenEdgePanGestureRecognizer.m in Sources */, EAB33E182C12E746002CFF44 /* CMPMetalDrawablesHandler.m in Sources */, diff --git a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h new file mode 100644 index 0000000000000..3d5a8496b5fc6 --- /dev/null +++ b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h @@ -0,0 +1,24 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Returns the default preferred frame rate for CADisplayLink on the current OS version. +float CMPDisplayLinkDefaultPreferredFrameRate(void); + +NS_ASSUME_NONNULL_END diff --git a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m new file mode 100644 index 0000000000000..a46aad8ee946a --- /dev/null +++ b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m @@ -0,0 +1,27 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "CMPFrameRateRange.h" +#import + +float CMPDisplayLinkDefaultPreferredFrameRate(void) { + if (@available(iOS 15.0, *)) { + return CAFrameRateRangeDefault.preferred; + } else { + // `preferredFramesPerSecond = 0` is CADisplayLink's documented default + return 0.0f; + } +} diff --git a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h index 7ece2b7487a3c..60c2cbeb4a3bc 100644 --- a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h +++ b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h @@ -24,6 +24,7 @@ FOUNDATION_EXPORT const unsigned char CMPUIKitUtilsVersionString[]; #import "CMPAccessibilityElement.h" #import "CMPComposeContainerLifecycleDelegate.h" +#import "CMPFrameRateRange.h" #import "CMPDragInteractionProxy.h" #import "CMPDrawable.h" #import "CMPDropInteractionProxy.h" diff --git a/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt b/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt index fd1d14e252e10..2ba679ccc6891 100644 --- a/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt +++ b/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt @@ -17,8 +17,8 @@ package androidx.compose.ui.window import androidx.compose.ui.FrameRateCategory +import androidx.compose.ui.uikit.utils.CMPDisplayLinkDefaultPreferredFrameRate import platform.QuartzCore.CADisplayLink -import platform.QuartzCore.CAFrameRateRangeDefault import platform.darwin.NSInteger /** @@ -45,7 +45,7 @@ internal class DisplayLinkFrameRate( fun voteFrameRate(frameRate: Float, frameRateCategory: Float) { val frameRateCategoryValue = when (frameRateCategory) { - FrameRateCategory.Default.value -> CAFrameRateRangeDefault.preferred + FrameRateCategory.Default.value -> CMPDisplayLinkDefaultPreferredFrameRate() FrameRateCategory.Normal.value -> 60f FrameRateCategory.High.value -> maximumFramesPerSecond.toFloat() else -> Float.NaN @@ -69,4 +69,4 @@ internal class DisplayLinkFrameRate( frameRateVote = Float.NaN } } -} \ No newline at end of file +} From fce2d30f06bcc1121c1c79055e04c3d7e15be4c8 Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 14:40:22 +0200 Subject: [PATCH 2/2] Fix iOS 14 frame rate range bridge --- .../objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h | 9 ++++++++- .../objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m | 6 +++++- .../compose/ui/window/DisplayLinkFrameRate.ios.kt | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h index 3d5a8496b5fc6..5f1cd364e8b80 100644 --- a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h +++ b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.h @@ -18,7 +18,14 @@ NS_ASSUME_NONNULL_BEGIN +@interface CMPFrameRateRangeDefault : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + /// Returns the default preferred frame rate for CADisplayLink on the current OS version. -float CMPDisplayLinkDefaultPreferredFrameRate(void); +@property (class, nonatomic, readonly) float preferred; + +@end NS_ASSUME_NONNULL_END diff --git a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m index a46aad8ee946a..8c27ce4af1c73 100644 --- a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m +++ b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPFrameRateRange.m @@ -17,7 +17,9 @@ #import "CMPFrameRateRange.h" #import -float CMPDisplayLinkDefaultPreferredFrameRate(void) { +@implementation CMPFrameRateRangeDefault + ++ (float)preferred { if (@available(iOS 15.0, *)) { return CAFrameRateRangeDefault.preferred; } else { @@ -25,3 +27,5 @@ float CMPDisplayLinkDefaultPreferredFrameRate(void) { return 0.0f; } } + +@end diff --git a/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt b/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt index 2ba679ccc6891..ea793aa0947e6 100644 --- a/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt +++ b/compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/DisplayLinkFrameRate.ios.kt @@ -17,7 +17,7 @@ package androidx.compose.ui.window import androidx.compose.ui.FrameRateCategory -import androidx.compose.ui.uikit.utils.CMPDisplayLinkDefaultPreferredFrameRate +import androidx.compose.ui.uikit.utils.CMPFrameRateRangeDefault import platform.QuartzCore.CADisplayLink import platform.darwin.NSInteger @@ -45,7 +45,7 @@ internal class DisplayLinkFrameRate( fun voteFrameRate(frameRate: Float, frameRateCategory: Float) { val frameRateCategoryValue = when (frameRateCategory) { - FrameRateCategory.Default.value -> CMPDisplayLinkDefaultPreferredFrameRate() + FrameRateCategory.Default.value -> CMPFrameRateRangeDefault.preferred FrameRateCategory.Normal.value -> 60f FrameRateCategory.High.value -> maximumFramesPerSecond.toFloat() else -> Float.NaN