在iOS5.1上运行较低帧速率的CADisplayLink
我正在iPhone应用程序中使用在iOS5.1上运行较低帧速率的CADisplayLink,ios,objective-c,cadisplaylink,Ios,Objective C,Cadisplaylink,我正在iPhone应用程序中使用CADisplayLink 以下是相关代码: SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onTick)]; SMPTELink.frameInterval = 2;//30fps 60/n = fps [SMPTELink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDef
CADisplayLink
以下是相关代码:
SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onTick)];
SMPTELink.frameInterval = 2;//30fps 60/n = fps
[SMPTELink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
因此,每帧30FPS
(1/30秒)称为onTick。这在iOS6
+上非常有效-完全满足我的需要。然而,当我在运行iOS5.1的iphone4s上运行我的应用程序时,onTick方法的运行速度略慢于iOS6。就像它在运行它一样29FPS
。过了一会儿,它与iOS6和iPhone5就不同步了
onTick方法中的代码并不耗时(这是我的想法之一…),而且它不是iPhone,因为该应用程序在运行iOS6的iPhone 4s上运行良好
CADisplayLink
在iOS5.1
中的功能是否不同?任何可能的解决方法/解决方案?我不能谈论iOS 5.x v 6.x的差异,但当我使用CADisplayLink
时,我从不在每次迭代中硬编码“移动x像素/点”,而是查看时间戳(或者更准确地说,是我的初始时间戳
和当前时间戳
之间的增量)并根据经过的时间计算位置,而不是根据经过的帧数。这样,帧速率不会影响运动的速度,而只是影响运动的平滑度。(30和29之间的差异可能无法区分。)
引自:
显示链接与运行循环关联后,当需要更新屏幕内容时,将调用目标上的选择器。目标可以读取显示链接的属性以检索上一帧显示的时间。例如,显示电影的应用程序可能使用时间戳计算哪一视频接下来将显示o帧。执行自己动画的应用程序可能会使用时间戳来确定显示对象在下一帧中的位置和显示方式。该属性提供帧之间的时间量。您可以在应用程序中使用此值来计算显示的帧速率,即显示的大致时间将显示下一帧,并调整图形行为,以便及时准备显示下一帧
作为一个随机示例,我正在使用经过的秒数作为参数设置UIBezierPath
的动画
或者,如果您正在处理一系列UIImage
帧,则可以按如下方式计算帧编号:
@property (nonatomic) CFTimeInterval firstTimestamp;
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames;
// now do whatever you want with this frame number
}
或者,更好的是,为了避免丢失帧的风险,继续以60 fps的速度运行,然后确定帧是否需要更新,这样可以降低丢失帧的风险
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames;
if (frameNumber != self.lastFrame)
{
// do whatever you want with this frame number
...
// now update the "lastFrame" number property
self.lastFrame = frameNumber;
}
}
但通常情况下,根本不需要帧号。例如,要在圆中移动ui视图
,可以执行以下操作:
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
self.animatedView.center = [self centerAtElapsed:elapsed];
}
- (CGPoint)centerAtElapsed:(CFTimeInterval)elapsed
{
CGFloat radius = self.view.bounds.size.width / 2.0;
return CGPointMake(radius + sin(elapsed) * radius,
radius + cos(elapsed) * radius);
}
顺便说一句,如果你使用仪器来测量帧速率,它看起来可能比设备上的实际帧速率要慢。马特评论说,为了获得准确的帧速率,你应该在一台具有发布版本的实际设备上以编程方式测量它。Rob的回答完全正确。你不必担心CADisplayLink;的帧速率事实上,你甚至不能期望计时器会有规律地启动。你的工作是根据所需的时间尺度划分所需的动画,并通过累计时间戳绘制每次计时器启动时实际得到的帧
以下是我书中的示例代码:
if (self->_timestamp < 0.01) { // pick up and store first timestamp
self->_timestamp = sender.timestamp;
self->_frame = 0.0;
} else { // calculate frame
self->_frame = sender.timestamp - self->_timestamp;
}
sender.paused = YES; // defend against frame loss
[_tran setValue:@(self->_frame) forKey:@"inputTime"];
CGImageRef moi3 = [self->_con createCGImage:_tran.outputImage
fromRect:_moiextent];
self->_iv.image = [UIImage imageWithCGImage:moi3];
CGImageRelease(moi3);
if (self->_frame > 1.0) {
[sender invalidate];
self->_frame = 0.0;
self->_timestamp = 0.0;
}
sender.paused = NO;
if(self->\u timestamp<0.01){//拾取并存储第一个时间戳
self->_timestamp=sender.timestamp;
self->_帧=0.0;
}else{//计算帧
self->\u frame=sender.timestamp-self->\u timestamp;
}
sender.paused=YES;//防止帧丢失
[_transetvalue:@(self->_frame)forKey:@“inputTime”];
CGImageRef moi3=[self->\u con createCGImage:\u tran.outputImage
fromRect:_moident];
self->_iv.image=[UIImage-imageWithCGImage:moi3];
CGImageRelease(moi3);
如果(自->帧>1.0){
[发送方失效];
self->_帧=0.0;
self->_时间戳=0.0;
}
sender.paused=否;
在该代码中,\u frame
值在0(我们刚刚开始动画)和1(我们已经完成动画)之间运行在中间,我只是做任何特定的情况需要画出这个框架。为了使动画更长或更短,在设置<代码>帧> <代码> IVAR。时,只需乘以比例因子。
另外请注意,您绝对不能在模拟器中进行测试,因为测试结果完全没有意义。只有设备正确运行CADisplayLink
(示例如下:)其他答案的基本Swift版本(减去动画代码)
用法
class BasicStopwatch {
var timer: CADisplayLink!
var firstTimestamp: CFTimeInterval!
var elapsedTime: TimeInterval = 0
let formatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "mm:ss.SS"
return df
}()
func begin() {
timer = CADisplayLink(target: self, selector: #selector(tick))
timer.preferredFramesPerSecond = 10 // adjust as needed
timer.add(to: .main, forMode: .common)
}
@objc func tick() {
if (self.firstTimestamp == nil) {
print("Set first timestamp")
self.firstTimestamp = timer!.timestamp
return
}
elapsedTime = timer.timestamp - firstTimestamp
/// Print raw elapsed time
// print(elapsedTime)
/// print elapsed time
print(elapsedTimeAsString())
/// If need to track frames
// let totalFrames: Double = 20
// let frameNumber = (elapsedTime * Double(timer!.preferredFramesPerSecond)).truncatingRemainder(dividingBy: totalFrames)
// print("Frame ", frameNumber)
}
func elapsedTimeAsString() -> String {
return formatter.string(from: Date(timeIntervalSinceReferenceDate: elapsedTime))
}
}
let watch = BasicStopwatch()
watch.begin()
好主意!在onTick方法上,我加时间戳,然后在下一个方法上,我再加时间戳-然后比较看是否有0.03333秒的差异?如果没有,怎么办?你能详细说明你的概念吗?@ObjectiveCoder001如果有什么原因你绝对需要30 fps,那么,是的,你可以这样做。看看我的代码示例,可以根据经过的时间确定帧数。虽然许多动画不需要这样做,但您可以根据经过的时间计算新位置,并且没有理由将其限制为30 fps(29 vs 59 vs等)。太棒了!非常感谢。kMaxNumberOfFrames是什么?动画中的最大值?如果没有最大值怎么办?太棒了。我使用了你的第二个代码示例(60fps)。效果非常好。修复了我在iOS5中遇到的所有问题。我很感激!)