簡體   English   中英

使用UIScrollView基於計時器和線程進行平滑滾動(基於iOS Metronome)

[英]Smooth scrolling with UIScrollView based on a timer, threads (based on iOS Metronome)

我正在開發一個使用UIScrollView在屏幕上滾動樂譜的應用程序。 由於我需要定期執行此操作,並且還需要以均勻的間隔播放短聲音,因此我的代碼基於Apple提供的現已不推薦使用的“ Metronome”示例。

問題是滾動無法順利進行-相當生澀。 我的運行日志表明,我正在使用的NSTimer並不是以正確的間隔觸發的(或者部分代碼執行所需的時間太長)。

門票:我是音樂家,而不是專業程序員。 我閱讀了GCD上的Apple文檔(這似乎比Metronome示例中的線程執行同步事件更好的方法),但是我真的不知道如何將其應用於我的項目。

我沒有使用分頁。 內容大小比屏幕大小大得多:ScrollView滾動,只是鋸齒狀。

我的代碼執行得很好,但是滾動非常混亂。 任何幫助將不勝感激,尤其是如果遵循KSS原則!

//  PlayView.m
#import "PlayView.h"
#include <stdlib.h>

NSInteger xWidth;
int xChange = 0;
float timeInterval;
AVAudioPlayer *audioPlayer1;
AVAudioPlayer *audioPlayer2;
float tempo;
int subdivisions;
int timesPlayed = 1;
int actualTimesPlayed = 0;

// ...

//RUN WHEN THE USER PRESSES THE PLAY BUTTON
-(void)start {
  // Used in calculating the speed of timer firing.
  // xWidth is the spacing between images (pixels)
  subdivisions = (int)(xWidth);

  // Keeps track of where we are in the measure
  beatNumber = 0;

  // Keeps track of how many measures we already played
  timesPlayed = 1;
  actualTimesPlayed = 0;

  // Let the device idle without dimming the screen
  UIApplication *myApp=[UIApplication sharedApplication];
  myApp.idleTimerDisabled=YES;  

  [self startDriverThread];    
}

// Taken straight from 'Metronome'
- (void)startDriverThread {
  if (soundPlayerThread != nil) {
    [soundPlayerThread cancel];
    [self waitForSoundDriverThreadToFinish];
  }

  NSThread *driverThread = [[NSThread alloc] initWithTarget:self  selector:@selector(startDriverTimer:) object:nil];
  self.soundPlayerThread = driverThread;
  [driverThread release];

  [self.soundPlayerThread start];
}

// Taken straight from 'Metronome'
- (void)waitForSoundDriverThreadToFinish {
  while (soundPlayerThread && ![soundPlayerThread isFinished]) { 
    // Wait for the thread to finish. I've experimented with different values
    [NSThread sleepForTimeInterval:timeInterval];
  }
}

// Taken straight from 'Metronome'
- (void)stopDriverThread {
  [self.soundPlayerThread cancel];
  [self waitForSoundDriverThreadToFinish];
  self.soundPlayerThread = nil;
}

// Modification of 'Metronome'
- (void)startDriverTimer:(id)info {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // Give the sound thread high priority to keep the timing steady.
    [NSThread setThreadPriority:1.0];
    BOOL continuePlaying = YES;

    while (continuePlaying) {  // Loop until cancelled.

      // An autorelease pool to prevent the build-up of temporary objects.
      NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init]; 

      // Reset the beat number to 0 at the end of each musical measure
      if (beatNumber == (subdivisions*4)) {
        beatNumber = 0; }

      // Incrementation of where we are in the bar on each firing of the timer
      beatNumber++;

      // On each beat, play a sound
      if(beatNumber % subdivisions == 0) {
        [self playSound]; }

      // On each firing of the timer, run the 'animateScreen' function, which scrolls the UIScrollView and performs some other simple tasks
      [self performSelectorOnMainThread:@selector(animateScreen) withObject:nil waitUntilDone:NO];

      // xChange is the number of pixels scrolled, but only start scrolling two and a half beats into the first bar (in order to keep the main image event at the center of the screen in each measure
      if ((actualTimesPlayed == 0 && beatNumber >= 2.5*subdivisions) || xChange > 0) {
          xChange += 1; }    

      // The time interval at which the timer fires is calculated by dividing the tempo (beats per minute, entered by the user; between 60-94) and 60 (seconds) This alone would result in one firing of the timer per beat, but we need at double this speed for some of the calculations 'animateScreen' does in between the beats, and really many more so the scrolling is smooth.
      // 
      // EXAMPLE: (60s/92bpm)/17% (image spacing) of 320pixels (screen width) = 0.012077
      timeInterval = (60/tempo)/subdivisions; 

      NSDate *curtainTime = [[NSDate alloc] initWithTimeIntervalSinceNow:(timeInterval)];
      NSDate *currentTime = [[NSDate alloc] init];

    // Wake up periodically to see if we've been cancelled.
    while (continuePlaying && ([currentTime compare:curtainTime] != NSOrderedDescending)) { 
        if ([soundPlayerThread isCancelled] == YES) {
            continuePlaying = NO;                
        }

        // Don't fully understand this; I've tried changing it to various values with no luck
        [NSThread sleepForTimeInterval:timeInterval];
        [currentTime release];
        currentTime = [[NSDate alloc] init];
    }
    [curtainTime release];      
    [currentTime release];      
    [loopPool drain];
}
[pool drain];

}

- (void)playSound {        
  if(beatNumber % subdivisions == 0){
    if (beatNumber == subdivisions) {
        [audioPlayer1 play];
    }

    else {
        [audioPlayer2 play];
    }
  }
}

- (void)animateScreen {        
  // BEAT 1
  if (beatNumber == (subdivisions)) {   
    // do some stuff
    // ...
  }    

  // THE SECOND  EIGTH OF 1
  if (beatNumber == (int)(subdivisions*1.25) && actualTimesPlayed >0) {
    // do some more stuff
    // ..
  }

  // BEAT 2
  if (beatNumber == (2*subdivisions)) {
    // even more stuff
  }

  // BEAT 3
  if (beatNumber == (3*subdivisions)) {
    // ... more
  }

  // BEAT 4
  if (beatNumber == (4*subdivisions)) {
    // yet more stuff
    // ...

    actualTimesPlayed++;
    timesPlayed++;
    if (timesPlayed == 3) {
        timesPlayed = 1; }     
  }    

  // On the "And of 4"
  if (beatNumber == subdivisions/2 && actualTimesPlayed > 0) {
    // STUFF
  }

  //Scroll over
  [theScroller setContentOffset:CGPointMake(xChange, 0) animated:NO];    
}

// ...

也許這不是最優雅的代碼,但是除了滾動之外,其他所有功能都是正常的。 在很多地方我都省略了代碼,但是我知道東西並沒有妨礙您(當我將其注釋掉並使程序完全露骨時-只是計時器和滾動,沒有聲音-它仍然存在)不順利。)我確信計時器是問題所在。

非常感謝任何幫助/指導。

NSTimer不適合定期更新屏幕。
為此,請使用CADisplayLink並將其安排在主運行循環中。

另外,如果您要查看以不斷滾動,我不會選擇UIScrollView 只需將UIView子類化,並在每個顯示鏈接的回調中更新其邊界。

沒辦法,您仍然有蘋果的舊節拍器樣本!! 我在任何地方都找不到那個東西。 但是無論如何,節拍器樣本由於與您的問題存在完全相同的原因而被棄用:它的實現在音樂上是次充好。 節拍器依賴於對象的位置和許多不必要的選擇器,這使它成為不穩定的計時器。 此外,NSTimers和線程從來都不是為了提高音樂准確性。 我的建議是,廢棄它,但先將其放在Github上。

這是我過去使用過的。 請記住,它是受版權保護的作品,因此請小心使用其中的多少,並記住要給他們適當的信譽: 節拍器

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM