6

CADisplayLinkiPhoneアプリで使用しています。

関連するコードは次のとおりです。

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

したがって、onTickは30FPS(1/30秒)のフレームごとに呼び出されます。これはiOS6+でうまく機能します-私が必要としていることを正確に実行します。ただし、iOS5.1を実行しているiPhone 4sでアプリを実行した場合、onTickメソッドの実行速度はiOS6のメソッドよりもわずかに遅くなりました。まるでそれを実行していたように29FPS。少し経つと、iOS6iPhone5と同期しなくなりました。

onTickメソッドのコードは時間のかかるものではなく(それは私の考えの1つでした...)、iOS6を実行しているiPhone 4sでアプリが正常に動作するため、iPhoneではありません。

CADisplayLinkで機能が異なりiOS5.1ますか?考えられる回避策/解決策はありますか?

4

3 に答える 3

19

iOS 5.xv 6.xの違いについて話すことはできませんが、を使用するときはCADisplayLink、反復ごとに「xピクセル/ポイントを移動する」などのハードコーディングを行うことはありませんtimestamp。私の初期timestampと現在の間timestamp)そして、経過したフレーム数ではなく、経過した時間に基づいて位置を計算します。このように、フレームレートはモーションの速度に影響を与えるのではなく、モーションの滑らかさに影響を与えます。(そして、30と29の違いは見分けがつかない可能性があります。)

CADisplayLinkクラスリファレンスから引用するには:

表示リンクが実行ループに関連付けられると、画面のコンテンツを更新する必要があるときに、ターゲットのセレクターが呼び出されます。ターゲットは、表示リンクのタイムスタンププロパティを読み取って、前のフレームが表示された時刻を取得できます。たとえば、映画を表示するアプリケーションは、タイムスタンプを使用して、次に表示されるビデオフレームを計算する場合があります。独自のアニメーションを実行するアプリケーションは、タイムスタンプを使用して、表示されるオブジェクトが次のフレームのどこにどのように表示されるかを決定する場合があります。期間_プロパティは、フレーム間の時間を提供します。アプリケーションでこの値を使用して、表示のフレームレート、次のフレームが表示されるおおよその時間を計算し、次のフレームが表示に間に合うように描画動作を調整できます。


ランダムな例として、ここ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;
    }
}

しかし、多くの場合、フレーム番号はまったく必要ありません。たとえば、UIViewaを円で移動するには、次のようにします。

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

ちなみに、計測器を使用してフレームレートを測定すると、実際のデバイスよりも遅く見える場合があります。マットのコメントとして、正確なフレームレートを得るには、リリースビルドを使用して実際のデバイスでプログラムで測定する必要があります。

于 2013-02-19T16:28:45.497 に答える
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アニメーションの所要時間を長くしたり短くしたりするには、 ivarを設定するときに倍率を掛けるだけです。

また、結果はまったく意味がないため、シミュレータでテストしてはならないことにも注意してください。デバイスのみがCADisplayLinkを正しく実行します。

(例はここから来ています:http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions

于 2013-02-19T19:14:06.357 に答える
0

他の回答の基本的な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()
于 2018-12-14T07:47:34.480 に答える