2017-05-19 20 views
1

참고 : 비슷한 문제에 대해 스택 오버플로를 검색했으며이 특정 문제를 해결하는 것으로 보이는 질문이 없습니다.NSTableView reloadData 메서드를 사용하면 모든 행의 NSProgressIndicator가 업데이트되고 깜박입니다.

내가 (여기에 해당 소스 코드를 완료 Xcode 프로젝트 : http://jollyroger.kicks-ass.org/stackoverflow/FlickeringTableView.zip를) 작은 샘플 응용 프로그램을 작성했습니다

/System/Library/Sounds/ 순차적으로 모든 사운드를 재생하고 그들이 문제를 보여주기 위해 재생과 같은 창에서 소리를 표시 I 보고 있습니다. MainMenu.xib의 창은 세 항목 셀 템플릿으로 정의 된 하나의 행으로 단일 열 NSTableView이 있습니다

NSTextField
  • 가 개최하는 또 다른 NSTextField 사운드 정보를 보유 할
  • 소리 이름
  • 소리가 나는 NSTableCellView이 (SoundsTableCellView.h)는 셀의 각 항목을 경쟁 정의하는 서브 클래스했다

를 재생하는 동안 NSProgressIndicator 플레이 진행률을 표시합니다 시간이 생길 때 나는 그들을 액세스하고 설정할 수 있습니다.

AVAudioPlayer API를 통해 사운드 파일 재생을 처리하는 데 필요한 속성과 메서드를 캡슐화하는 MySound 클래스를 정의했습니다. 이 클래스는 MySoundDelegate 프로토콜을 정의하여 사운드가 시작되거나 끝날 때마다 앱 델리게이트가 이벤트를 수신 할 수있게합니다.

애플리케이션 대리인 필요할 때 그 관련 정보 테이블 MySound 객체 배열의 테이블 데이터를 저장 및 업데이트 할 수 있도록하고 NSTableViewDelegateNSTableViewDataSource 프로토콜을 준수. 또한 소리가 시작되거나 끝날 때 이벤트를 수신하기 위해 MySoundDelegate 프로토콜을 준수합니다. 대리자에는 refreshWindow 메서드를 주기적으로 호출하여 현재 재생중인 사운드의 진행률 표시기를 업데이트하는 NSTimer 작업도 있습니다.

앱 위임자의 refreshWindow 메서드는 목록의 사운드 수에 따라 필요한 경우 창을 표시하고 크기를 조정하고 재생되는 사운드에 대한 저장된 참조를 연결된 NSProgressIndicator으로 업데이트합니다.

앱 델리게이트의 tableView: viewForTableColumn (NSTableViewDelegate 프로토콜) 메소드가 호출되어 표 셀을 채 웁니다.

  1. 체크 나는 테이블 컬럼에 대한 인터페이스 빌더 (엑스 코드)에 설정된는 식별자 (sound column)과 일치 보장하기 위해 테이블 ​​열 식별자 : 그것은, 나는 조언 "테이블보기 프로그래밍을 채우기"애플의 표준을 사용 ,
  2. thisTableView makeViewWithIdentifier를 호출 식별자 (sound cell)와 해당 테이블 셀을 얻을
  3. 후,
  4. 을 데이터 소스 (APP 대리자 sounds 어레이)의 정합 어레이 소자 를 찾기 위해 수신 row 매개 변수를 사용하여
  5. NSTextFields의 스트링 값을 설정하고 maxValue과 관련된 사운드 객체의 대응하는 세부 셀 내의 NSProgressIndicatordoubleValue,
  6. 저장소 나중에
  7. 업데이트 관련된 사운드 객체에 연관된 NSProgressIndicator 제어 기준을 설정할 여기

는 방법입니다 :

- (NSView *)tableView:(NSTableView *)thisTableView viewForTableColumn:(NSTableColumn *)thisTableColumn row:(NSInteger)thisRow 
{ 
    SoundsTableCellView *cellView = nil; 

    // get the table column identifier 

    NSString *columnID = [thisTableColumn identifier]; 
    if ([columnID isEqualToString:@"sound column"]) 
    { 
     // get the sound corresponding to the specified row (sounds array index) 

     MySound *sound = [sounds objectAtIndex:thisRow]; 

     // get an existing cell from IB with our hard-coded identifier 

     cellView = [thisTableView makeViewWithIdentifier:@"sound cell" owner:self]; 

     // display sound name 

     [cellView.soundName setStringValue:[sound name]]; 
     [cellView.soundName setLineBreakMode:NSLineBreakByTruncatingMiddle]; 

     // display sound details (source URL) 

     NSString *details = [NSString stringWithFormat:@"%@", [sound sourceURL]]; 
     [cellView.soundDetails setStringValue:details]; 
     [cellView.soundDetails setLineBreakMode:NSLineBreakByTruncatingMiddle]; 

     // update progress indicators 

     switch ([sound state]) 
     { 
      case kMySoundStateQueued: 
       break; 
      case kMySoundStateReadyToPlay: 
       break; 
      case kMySoundStatePlaying: 
       if (sound.playProgress == nil) 
       { 
        sound.playProgress = cellView.playProgress; 
       } 

       NSTimeInterval duration = [sound duration]; 
       NSTimeInterval position = [sound position]; 

       NSLog(@"row %ld: %@ (%f/%f)", (long)thisRow, [sound name], position, duration); 
       NSLog(@"   %@: %@", [sound name], sound.playProgress); 

       [cellView.playProgress setMaxValue:duration]; 
       [cellView.playProgress setDoubleValue:position]; 

       break; 
      case kMySoundStatePaused: 
       break; 
      case kMySoundStateFinishedPlaying: 
       break; 
      default: 
       break; 
     } 
    } 

    return cellView; 
} 

그리고 여기 refreshWindow 방법입니다 :

- (void) refreshWindow 
{ 
    if ([sounds count] > 0) 
    { 
     // show window if needed 

     if ([window isVisible] == false) 
     { 
      [window makeKeyAndOrderFront:self]; 
     } 

     // resize window to fit all sounds in the list if needed 

     NSRect frame = [self.window frame]; 

     int screenHeight = self.window.screen.frame.size.height; 

     long maxRows = ((screenHeight - 22)/82) - 1; 
     long displayedRows = ([sounds count] > maxRows ? maxRows : [sounds count]); 

     long actualHeight = frame.size.height; 
     long desiredHeight = 22 + (82 * displayedRows); 
     long delta = desiredHeight - actualHeight; 

     if (delta != 0) 
     { 
      frame.size.height += delta; 
      frame.origin.y -= delta; 

      [self.window setFrame:frame display:YES]; 
     } 

     // update play position of progress indicator for all sounds in the list 

     for (MySound *nextSound in sounds) 
     { 
      switch ([nextSound state]) 
      { 
       case kMySoundStatePlaying: 
        if (nextSound.playProgress != nil) 
        { 
         [nextSound.playProgress setDoubleValue:[nextSound position]]; 
         NSLog(@"   %@: %@ position: %f", [nextSound name], nextSound.playProgress, [nextSound position]); 
        } 
        break; 
       case kMySoundStateQueued: 
       case kMySoundStateReadyToPlay: 
       case kMySoundStatePaused: 
       case kMySoundStateFinishedPlaying: 
       default: 
        break; 
      } 
     } 
    } 
    else 
    { 
     // hide window 

     if ([window isVisible]) 
     { 
      [window orderOut:self]; 
     } 
    } 

    // reload window table view 

    [tableView reloadData]; 
} 

init 동안, 응용 프로그램 대리인이 해당 폴더에 AIFF 사운드 파일의 목록을 얻을 수있는 /System/Library/Sounds/ 폴더를 검색하고 해당 폴더에있는 소리의 각 사운드 객체를 들고 sounds 배열을 만듭니다. 그런 다음 applicationDidFinishLaunching 메서드는 목록에서 첫 번째 사운드를 순차적으로 재생하기 시작합니다.

문제 (샘플 프로젝트를 실행하여 볼 수 있음)는 현재 재생중인 사운드의 맨 위 테이블 행을 업데이트하는 것이 아니라 의 모든 진행률 표시자인 다음 행 중 모두이 업데이트되고 플리커도. 표시 방식이 다소 일치하지 않습니다 (때로는 모두 깜박이고 때로는 예상대로 비어 있습니다). 하지만 업데이트 및 깜박일 때 진행 표시기는 현재 재생중인 사운드와 대략 일치하는 것처럼 보입니다. 그래서 나는이 문제가 어떻게 든 테이블을 업데이트하는 방식과 관련이 있어야한다는 것을 확신합니다. 나는 그 문제가 어디 있는지 어떻게 해결할 지 모를 뿐이다.

Table View Screen Shot

어떤 아이디어 또는 지침을 크게 감상 할 수있다 :

여기에 윈도우가 당신에게 아이디어를주고 같은 모습의 스크린 샷입니다!

+0

FYI : Apple의 "프로그래밍 방식으로 테이블보기 채우기"설명서는 https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TableView/PopulatingView-TablesProgrammatically/PopulatingView-TablesProgrammatically.html –

+0

' makeViewWithIdentifier : owner :' "지정된 식별자를 가진 새로운 **보기 **를 반환합니다." 모든 사운드는 동일한 컨트롤을 가리킬 수 있습니다. – Willeke

+0

고마워요, @Willeke. 확인해 보니 진행 표시기가 실제로 각 행마다 다릅니다. 나는'makeViewWithIdentifier' 메쏘드의'NSLog' 문을 추가하여 주어진 사운드 객체에 대한 저장된 참조를'NSProgressIndicator'에 출력했습니다. 다음은 몇 가지 샘플 출력입니다 : 바소 [] 불어 [] 병 [] 개구리 [] –

답변

0

다음은 내가 변경 한 것입니다.

tableView:viewForTableColumn:row: 복귀 "도면 지정된 행과 열을 표시한다." 진행률 표시 줄의 값은 항상 설정됩니다.

- (NSView *)tableView:(NSTableView *)thisTableView viewForTableColumn:(NSTableColumn *)thisTableColumn row:(NSInteger)thisRow 
{ 
    SoundsTableCellView *cellView = nil; 

    // get the table column identifier 

    NSString *columnID = [thisTableColumn identifier]; 
    if ([columnID isEqualToString:@"sound column"]) 
    { 
     // get the sound corresponding to the specified row (sounds array index) 

     MySound *sound = [sounds objectAtIndex:thisRow]; 

     // get an existing cell from IB with our hard-coded identifier 

     cellView = [thisTableView makeViewWithIdentifier:@"sound cell" owner:self]; 

     // display sound name 

     [cellView.soundName setStringValue:[sound name]]; 

     // display sound details (source URL) 

     NSString *details = [NSString stringWithFormat:@"%@", [sound sourceURL]]; 
     [cellView.soundDetails setStringValue:details]; 

     // update progress indicators 

     // [cellView.playProgress setUsesThreadedAnimation:NO]; 

     NSTimeInterval duration = [sound duration]; 
     NSTimeInterval position = [sound position]; 
     [cellView.playProgress setMaxValue:duration]; 
     [cellView.playProgress setDoubleValue:position]; 
    } 

    // end updates 

    // [thisTableView endUpdates]; 

    return cellView; 
} 

refreshWindowrefreshProgressrefreshWindow로 분할된다. refreshProgress은 재생 사운드의 행을 새로 고치며 타이머에서 호출됩니다.

- (void)refreshProgress 
{ 
    if ([sounds count] > 0) 
    { 
     [sounds enumerateObjectsUsingBlock:^(MySound *nextSound, NSUInteger rowNr, BOOL *stop) 
     { 
      switch ([nextSound state]) 
      { 
       case kMySoundStatePlaying: 
        // refresh row 
        [tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:rowNr] 
         columnIndexes:[NSIndexSet indexSetWithIndex:0]]; 
        break; 
       case kMySoundStateQueued: 
       case kMySoundStateReadyToPlay: 
       case kMySoundStatePaused: 
       case kMySoundStateFinishedPlaying: 
       default: 
        break; 
      } 
     }]; 
    } 
} 

refreshWindow 윈도우의 크기와 가시성을 갱신하고 호출 할 때 소리의 변화의 수.

- (void) refreshWindow 
{ 
    if ([sounds count] > 0) 
    { 
     // show window if needed 

     if ([window isVisible] == false) 
     { 
      [window makeKeyAndOrderFront:self]; 
     } 

     // resize window to fit all sounds in the list if needed 

     ... calculate new window frame 

     } 
    else 
    { 
     // hide window 

     if ([window isVisible]) 
     { 
      [window orderOut:self]; 
     } 
    } 
} 

소리가 제거되면 행도 제거되므로 다른 행은 여전히 ​​동일한 사운드를 표시하므로 업데이트 할 필요가 없습니다.

- (void) soundFinishedPlaying:(MySound *)sound encounteredError:(NSError *)error 
{ 
    if (error != NULL) 
    { 
     // display an error dialog box to the user 

     [NSApp presentError:error]; 
    } 
    else 
    { 
     // remove sound from array 

     NSLog(@"deleting: [%@|%@]", [sound truncatedID], [sound name]); 

     NSUInteger index = [sounds indexOfObject:sound]; 
     [sounds removeObject:sound]; 
     [tableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:index] withAnimation:NSTableViewAnimationEffectNone]; 
    } 

    // refresh window 

    [self refreshWindow]; 

    // play the next sound in the queue 

    [self play]; 
} 

[tableView reloadData]이 호출되지 않습니다. sound.playProgress은 사용되지 않습니다.

+0

아하. 그래서 대답은'[tableView reloadData]'를 호출하지 말고'[tableView removeRowsAtIndexes ...]와 함께'[tableView reloadDataForRowIndexes ...]'를 대신 호출하여 삭제 된 행을 수동으로 제거하는 것입니다 데이터 소스 배열의 항목 여전히 완전히 이해하지 못한다. reloadData'는 테이블 뷰의 _all_ 진행 표시기가 업데이트/깜빡임을 유발하지만, 더 좋은 방법으로 업데이트 할 수있다. 저에게 길을 보여 주셔서 고마워요! :) –

+0

'makeViewWithIdentifier'는 뷰가 더 이상 사용되지 않을 때 뷰를 재사용합니다. 뷰가 재사용 가능한 뷰 풀에 저장됩니다. 첫 번째 사운드가 제거되면 깜박임이 시작됩니다. 10 개의 뷰와 9 개의 사운드가있을 때, 첫 번째 9 개 뷰의 모든 reloadData가 재사용되고 다음 뷰의 reloadData에 10 번째 뷰가 먼저 사용됩니다. 뷰가 순환하고'viewForTableColumn'는 모든 뷰에서 진행 표시기의 값을 설정하지 않았습니다. 'sound.name'과'cellView.playProgress'의 포인터를'viewForTableColumn'에 기록하면 이런 일이 일어나는 것을 볼 수 있습니다. – Willeke

+0

나는 그것을 볼 수있다. 고마워, @ 빌어! :) –