Setting Refresh Rates on ProMotion and Traditional Displays
In this document we will provide detailed information on how to set the display refresh rate property on various view classes such as MTKView, SCNView and SKView. Setting refresh rate is supported in iOS 10, tvOS 10, and macOS 10.12 and later with Xcode 8.3.3.
The sections that follow describe the steps required to determine a device's maximum refresh rate, how to use an alternate refresh rate and situations for which you would want to change the refresh rate from a default value.
How to enable an alternate refresh rate
To enable alternate refresh rates, add the
CADisableMinimumFrameDuration key, set as true (YES), to your App's Info.plist. Note that this will not be required in later releases of iOS, tvOS and macOS.
Listing 1 Info.plist feature flag
Discussion in Introduction to Metal 2
Querying the maximum supported refresh rate
Once you opt in to supporting alternate refresh rates, you'll want to query your device for it's maximum supported refresh rate. This done via the
maximumFramesPerSecond property of
Listing 2 Querying the maximumFramesPerSecond property
let maxFPS = UIScreen.maximumFramesPerSecond
See the API documentation for UIScreen's maximumFramesPerSecond property for more information.
Setting a custom refresh rate
After determining your device's maximum frames per second, you can set your preferred rate for the view class that you're using in your App.
GLKViewController is an exception as the property is on a view controller instead of a view. There are several choices depending on the framework:
CADisplaylink's preferredFramesPerSecond for UIKit if you are driving client side animations on your own
MTKView's preferredFramesPerSecond for MetalKit
SCNView's preferredFramesPerSecond for SceneKit
SKView's preferredFramesPerSecond for SpriteKit
GLKViewController's preferredFramesPerSecond for GLKit
The actual frame rate chosen by the system is usually a factor of the maximum refresh rate of the screen to provide a consistent frame rate. For example, if the maximum refresh rate of the screen is 60 frames per second, that is also the highest frame rate the display link sets as the actual frame rate; however, if you ask for a lower frame rate, the display link might choose 30, 20, or 15 frames per second (or another rate) as the actual frame rate. Consequently, it's important to consider
preferredFramesPerSecond as a hint or heuristic and not a guarantee.
Your application should choose a frame rate that it can consistently maintain. The default value is 60 FPS. This means that
preferredFramesPerSecond must be explicitly set to 120 FPS on a ProMotion display if that is desired.
Setting a refresh rate higher than necessary will increase overall system power consumption which can lead to a poor user experience.
You can verify your App's FPS using Xcode's GPU report in the Debug navigator.
Use cases and considerations for custom refresh rates
The typical motivation for choosing a custom frame rate, lower than the device's
maximumFramesPerSecond, is to prevent frame stuttering where some frames appear on display longer than others. This stuttering makes it difficult to render animations smoothly. On devices with ProMotion displays the time between refresh intervals can be much smaller thereby helping to prevent stutter.
For UIKit based Apps this is handled automatically by presenting immediately to display as soon as content is ready to render. The same is true for SceneKit, SpriteKit and GLKit if you choose to drive the update based on your own pacing or event sources with
To get the best performance in UIKit make sure you invalidate your drawing based on the your incoming event changes such as touches, network events, etc. as quickly as possible. The resulting Core Animation transaction will be picked up with the next possible refresh of the display. Take care to invalidate only when necessary to reduce power consumption. See WWDC 2015 session 233, WWDC 2016 session 220, Getting High Fidelity Input with Coalesced Touches, and Minimizing Latency with Predicted Touches.
For Metal based Apps there are three choices for how to work with custom refresh rates:
MTLDrawable's present() method
MTLDrawable's present(afterMinimumDuration:) method
MTLDrawable's present(at:) method
The first method
present() is the default and behaves as discussed above. See WWDC 2017 session 601.
present(afterMinimumDuration:) keeps a rendered image on display for a fixed amount of time. This time constraint provides the consistent frame rate that is desired, and is a noteworthy use case for 30FPS on traditional displays with a
maximumFramesPerSecond value of 60. See WWDC 2017 session 601.
present(at:) allows for fully customized presentation timing which can render for exactly the time when the user is going to see it. Scheduling must happen accordingly using MTLDrawable's presentedTime property to get the system feedback required to adjust the timing. See WWDC 2017 session 601.
Listing 3 Supporting custom animation timing
let targetTime = // project when intend to display this drawable
// render your scene into a command buffer for 'targetTime'
let drawable = metalLayer.nextDrawable()
commandBuffer.present(drawable, atTime: targetTime)
// after a frame or two...
let presentationDelay = drawable.presentedTime - targetTime
// examine presentationDelay and adjust future frame timing
Document Revision History
New document that shows how to detect and enable different refresh rates and the situations in which you'd want to do this.