diff --git a/packages/core/application/index.android.ts b/packages/core/application/index.android.ts index 5184e2220..cd6617697 100644 --- a/packages/core/application/index.android.ts +++ b/packages/core/application/index.android.ts @@ -29,6 +29,10 @@ const ActivityBackPressed = 'activityBackPressed'; const ActivityNewIntent = 'activityNewIntent'; const ActivityRequestPermissions = 'activityRequestPermissions'; +export function setMaxRefreshRate(options?: { min?: number; max?: number; preferred?: number }): void { + // ignore on android, ios only +} + export class AndroidApplication extends Observable implements AndroidApplicationDefinition { public static activityCreatedEvent = ActivityCreated; public static activityDestroyedEvent = ActivityDestroyed; diff --git a/packages/core/application/index.d.ts b/packages/core/application/index.d.ts index 37c63cbba..066b0460e 100644 --- a/packages/core/application/index.d.ts +++ b/packages/core/application/index.d.ts @@ -76,6 +76,19 @@ export function setAutoSystemAppearanceChanged(value: boolean): void; */ export function systemAppearanceChanged(rootView: View, newSystemAppearance: 'dark' | 'light'): void; +/** + * iOS Only + * Dynamically change the preferred frame rate + * For devices (iOS 15+) which support min/max/preferred frame rate you can specify ranges + * For devices (iOS < 15), you can specify the max frame rate + * see: https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro + * To use, ensure your Info.plist has: + * CADisableMinimumFrameDurationOnPhone + * + * @param options { min?: number; max?: number; preferred?: number } + */ +export function setMaxRefreshRate(options?: { min?: number; max?: number; preferred?: number }): void; + /** * Event data containing information for the application events. */ diff --git a/packages/core/application/index.ios.ts b/packages/core/application/index.ios.ts index d06766c3a..1022a004e 100644 --- a/packages/core/application/index.ios.ts +++ b/packages/core/application/index.ios.ts @@ -88,6 +88,44 @@ class CADisplayLinkTarget extends NSObject { }; } +export function setMaxRefreshRate(options?: { min?: number; max?: number; preferred?: number }) { + const adjustRefreshRate = function () { + if (displayedLink) { + const minFrameRateDisabled = NSBundle.mainBundle.objectForInfoDictionaryKey('CADisableMinimumFrameDurationOnPhone'); + if (minFrameRateDisabled) { + let max = 120; + const deviceMaxFrames = UIScreen.mainScreen?.maximumFramesPerSecond; + if (options?.max) { + if (deviceMaxFrames) { + // iOS 10.3 + max = options.max <= deviceMaxFrames ? options.max : deviceMaxFrames; + } else if (displayedLink.preferredFramesPerSecond) { + // iOS 10.0 + max = options.max <= displayedLink.preferredFramesPerSecond ? options.max : displayedLink.preferredFramesPerSecond; + } + } + + if (iOSNativeHelper.MajorVersion >= 15) { + const min = options?.min || max / 2; + const preferred = options?.preferred || max; + displayedLink.preferredFrameRateRange = CAFrameRateRangeMake(min, max, preferred); + } else { + displayedLink.preferredFramesPerSecond = max; + } + } + } + }; + if (!displayedOnce) { + displayedLinkTarget = CADisplayLinkTarget.new(); + displayedLink = CADisplayLink.displayLinkWithTargetSelector(displayedLinkTarget, 'onDisplayed'); + adjustRefreshRate(); + displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode); + displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, UITrackingRunLoopMode); + } else { + adjustRefreshRate(); + } +} + /* tslint:disable */ export class iOSApplication implements iOSApplicationDefinition { /* tslint:enable */ @@ -179,12 +217,7 @@ export class iOSApplication implements iOSApplicationDefinition { @profile private didFinishLaunchingWithOptions(notification: NSNotification) { - if (!displayedOnce) { - displayedLinkTarget = CADisplayLinkTarget.new(); - displayedLink = CADisplayLink.displayLinkWithTargetSelector(displayedLinkTarget, 'onDisplayed'); - displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode); - displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, UITrackingRunLoopMode); - } + setMaxRefreshRate(); this._window = UIWindow.alloc().initWithFrame(UIScreen.mainScreen.bounds); // TODO: Expose Window module so that it can we styled from XML & CSS diff --git a/packages/core/index.d.ts b/packages/core/index.d.ts index 212b44862..4d3b0a309 100644 --- a/packages/core/index.d.ts +++ b/packages/core/index.d.ts @@ -8,7 +8,7 @@ export type { NativeScriptConfig } from './config'; export { iOSApplication, AndroidApplication } from './application'; export type { ApplicationEventData, LaunchEventData, OrientationChangedEventData, UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData, AndroidActivityEventData, AndroidActivityBundleEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData, AndroidActivityNewIntentEventData, AndroidActivityBackPressedEventData, SystemAppearanceChangedEventData } from './application'; -import { AndroidApplication, iOSApplication, systemAppearanceChanged, getMainEntry, getRootView, _resetRootView, getResources, setResources, setCssFileName, getCssFileName, loadAppCss, addCss, on, off, notify, hasListeners, run, orientation, getNativeApplication, hasLaunched, systemAppearance, setAutoSystemAppearanceChanged } from './application'; +import { AndroidApplication, iOSApplication, systemAppearanceChanged, getMainEntry, getRootView, _resetRootView, getResources, setResources, setCssFileName, getCssFileName, loadAppCss, addCss, on, off, notify, hasListeners, run, orientation, getNativeApplication, hasLaunched, systemAppearance, setAutoSystemAppearanceChanged, setMaxRefreshRate } from './application'; export declare const Application: { launchEvent: string; displayedEvent: string; @@ -22,6 +22,7 @@ export declare const Application: { systemAppearanceChangedEvent: string; fontScaleChangedEvent: string; systemAppearanceChanged: typeof systemAppearanceChanged; + setMaxRefreshRate: typeof setMaxRefreshRate; getMainEntry: typeof getMainEntry; getRootView: typeof getRootView; resetRootView: typeof _resetRootView; diff --git a/packages/core/index.ts b/packages/core/index.ts index c84bb8296..ac43c40c3 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -5,7 +5,7 @@ import './globals'; export { iOSApplication, AndroidApplication } from './application'; export type { ApplicationEventData, LaunchEventData, OrientationChangedEventData, UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData, AndroidActivityEventData, AndroidActivityBundleEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData, AndroidActivityNewIntentEventData, AndroidActivityBackPressedEventData, SystemAppearanceChangedEventData } from './application'; -import { fontScaleChangedEvent, launchEvent, displayedEvent, uncaughtErrorEvent, discardedErrorEvent, suspendEvent, resumeEvent, exitEvent, lowMemoryEvent, orientationChangedEvent, systemAppearanceChanged, systemAppearanceChangedEvent, getMainEntry, getRootView, _resetRootView, getResources, setResources, setCssFileName, getCssFileName, loadAppCss, addCss, on, off, notify, hasListeners, run, orientation, getNativeApplication, hasLaunched, android as appAndroid, ios as iosApp, systemAppearance, setAutoSystemAppearanceChanged, ensureNativeApplication } from './application'; +import { fontScaleChangedEvent, launchEvent, displayedEvent, uncaughtErrorEvent, discardedErrorEvent, suspendEvent, resumeEvent, exitEvent, lowMemoryEvent, orientationChangedEvent, systemAppearanceChanged, systemAppearanceChangedEvent, getMainEntry, getRootView, _resetRootView, getResources, setResources, setCssFileName, getCssFileName, loadAppCss, addCss, on, off, notify, hasListeners, run, orientation, getNativeApplication, hasLaunched, android as appAndroid, ios as iosApp, systemAppearance, setAutoSystemAppearanceChanged, ensureNativeApplication, setMaxRefreshRate } from './application'; export const Application = { launchEvent, displayedEvent, @@ -19,6 +19,7 @@ export const Application = { systemAppearanceChangedEvent, systemAppearanceChanged, fontScaleChangedEvent, + setMaxRefreshRate, getMainEntry, getRootView,