2013-02-19 3 views
6

Я использую CADisplayLink в своем приложении для iPhone.CADisplayLink работает с более низкой частотой кадров на iOS5.1

Вот соответствующий код:

SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onTick)]; 
SMPTELink.frameInterval = 2;//30fps 60/n = fps 
[SMPTELink addToRunLoop:[NSRunLoop mainRunLoop] 
        forMode:NSDefaultRunLoopMode]; 

OnTick, таким образом, называется каждый кадр 30FPS (1/30-й секунды). Это работает GREAT на iOS6 + - делает именно то, что мне нужно. Однако, когда я запускал свое приложение на iPhone 4 под управлением iOS5.1, метод onTick работал немного медленнее, чем с iOS6. Почти так, как будто это бегало 29FPS. После немного, он не синхронизировался с iOS6 iPhone 5.

Код в методе onTick не занимает много времени (это была одна из моих мыслей ...), и это не iPhone, потому что приложение отлично работает на iPhone 4s под управлением iOS6.

CADisplayLink Функция по-разному в iOS5.1? Любые возможные обходные пути/решения?

ответ

16

Я не могу говорить с различиями iOS 5.xv 6.x, но когда я использую CADisplayLink, я никогда не записываю такие вещи, как «move x pixels/points» на каждой итерации, но я смотрю на timestamp (или, точнее, дельта между моим начальным timestamp и текущим timestamp) и вычислить местоположение в зависимости от того, сколько времени прошло, а не от того, сколько кадров прошло. Таким образом, частота кадров не влияет на скорость движения, а скорее на гладкость. (А разница между 30 и 29, вероятно, будет неразличимы.)

Цитаты из CADisplayLink Class Reference:

После того, как связь дисплея связана с циклом выполнения, селектор на цели вызывается, когда необходимо обновить содержимое экрана. Целевой объект может считывать свойство timestamp ссылочной ссылки для отображения времени отображения предыдущего кадра. Например, приложение, которое отображает фильмы, может использовать временную метку для вычисления следующего кадра видео. Приложение, которое выполняет собственные анимации, может использовать временную метку, чтобы определить, где и как отображаемые объекты появляются в предстоящем кадре. Свойство duration предоставляет время между кадрами. Вы можете использовать это значение в своем приложении, чтобы рассчитать частоту кадров дисплея, приблизительное время отображения следующего кадра и настроить поведение чертежа, чтобы следующий кадр был подготовлен вовремя для отображения.


В качестве случайного примера here я анимировать 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 кадров в секунду и просто определяет, нуждается ли кадр в обновлении, и таким образом вы уменьшите риск падения кадра.

- (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; 
    } 
} 

Но часто, номера кадров не нужны вообще.Например, чтобы переместить UIView в круг, вы могли бы сделать что-то вроде:

- (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); 
} 

Кстати, если вы используете инструменты для измерения частоты кадров, это может показаться медленнее, чем на самом деле будет на Устройство. Для комментария мата, для точной частоты кадров, вы должны измерять его программно на реальном устройстве с помощью сборки релиза.

+0

Отличная идея! Итак, по методу onTick, мне присваивается метка времени, а затем на следующем, я снова отметю время, а затем сравните, чтобы узнать, есть ли разница в 0.03333 секунды? Что, если я этого не сделаю? Можете ли вы рассказать о своей концепции? – objectiveccoder001

+0

@ objectiveccoder001 Если есть какая-то причина, по которой вам абсолютно необходимо 30 кадров в секунду, тогда да, вы могли бы сделать что-то подобное. См. Мой пример кода, который может определять номер кадра в зависимости от прошедшего времени. Однако многие анимации вам не нужно делать, но вы можете просто вычислить новую позицию на основе прошедшего времени, и нет причин, по которым она ограничена 30 кадрами в секунду (против 29 против 59 против и т. Д.). – Rob

+0

УДИВИТЕЛЬНЫЙ! Огромное спасибо. Что такое kMaxNumberOfFrames? – objectiveccoder001

3

Ответ Роба в точности прав. Вы не беспокоитесь о частоте кадров 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; 

В этом коде значение _frame работает в диапазоне от 0 (мы только начинаем анимацию) и 1 (мы закончили анимацию), и в средний Я просто делаю то, что требуется этой конкретной ситуации, чтобы нарисовать эту рамку. Чтобы сделать анимацию длиннее или короче, просто умножьте масштабный коэффициент при настройке ивара _frame.

Также обратите внимание, что вы никогда не должны тестировать в симуляторе, так как результаты совершенно бессмысленны. Только устройство работает CADisplayLink.

(пример приходит отсюда: http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions)

Смежные вопросы