feat(ios): allow dynamic ProMotion frame refresh rate changes (#9775)

see: https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro
This commit is contained in:
Nathan Walker
2022-02-16 19:19:23 -08:00
parent 27492219e6
commit b292495506
5 changed files with 60 additions and 8 deletions

View File

@@ -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;

View File

@@ -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:
* <key>CADisableMinimumFrameDurationOnPhone</key>
* <true/>
* @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.
*/

View File

@@ -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 = <CADisplayLink>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

View File

@@ -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;

View File

@@ -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,