2017-05-19 36 views
1

注:我已搜查堆棧溢出類似的問題,並沒有我發現的問題似乎解決這方面的問題。NSTableView的reloadData方法使NSProgressIndicator中的所有行更新和閃爍

我寫了一個小樣本應用(完整的Xcode項目,可在這裏的源代碼:http://jollyroger.kicks-ass.org/stackoverflow/FlickeringTableView.zip)可以播放所有的聲音在/System/Library/Sounds/依次顯示的聲音在一個窗口在播放時顯示的問題我我正在看。在MainMenu.xib窗口有一個單柱NSTableView在它定義爲一個單元模板,三個項目的一行:

  • NSTextField持有聲音名
  • 另一個NSTextField保持聲音細節
  • 一個NSProgressIndicator顯示播放進度,同時播放聲音時

我有子類NSTableCellViewSoundsTableCellView.h)來定義每個項目在細胞VIE w以便我可以在時間到達時訪問和設置它們。

我已經定義了一個MySound類,它封裝了通過AVAudioPlayer API處理播放聲音文件所需的屬性和方法。該類定義了一個MySoundDelegate協議,以允許應用程序委託在聲音開始或結束播放時接收事件。

的應用代表附着在NSTableViewDelegateNSTableViewDataSource協議,以允許在需要時將表數據存儲爲MySound對象的數組,並更新表相關的信息。它還遵守MySoundDelegate協議,以在聲音開始或結束播放時接收事件。該代表還具有一個NSTimer的任務,定期調用一個refreshWindow方法來更新當前播放的聲音進度指示器。

應用程序代理的refreshWindow方法根據列表中的聲音數量顯示並調整窗口的大小,並將存儲的參考更新爲關聯的NSProgressIndicator以播放聲音。

調用應用程序代表的tableView: viewForTableColumnNSTableViewDelegate協議)方法來填充表格單元格。在這裏面,我使用蘋果的標準「填充表視圖編程」建議:

  1. 檢查表列標識,以確保它的 標識符(sound column)匹配我在Interface Builder(的Xcode)爲表列設置,
  2. 通過調用thisTableView makeViewWithIdentifier得到具有標識符(sound cell)對應的表格單元格,
  3. 使用傳入row參數來定位數據源(應用程序委託sounds陣列)的匹配陣列元件 ,然後
  4. 設定的NSTextFields的字符串值,並設置maxValue和在細胞中的相關聯的聲音對象的對應的細節的NSProgressIndicatordoubleValue
  5. 商店購買更新
  6. 到相關聯的 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,應用程序委託掃描/System/Library/Sounds/文件夾來獲得該文件夾中的AIFF聲音文件的列表,並創建一個sounds列保持爲每個文件夾中的聲音的聲音對象。然後applicationDidFinishLaunching方法開始順序播放列表中的第一個聲音。

的問題(你可以通過運行示例項目看)是,而不是隻更新頂級錶行對於當前播放的進度指標以下行的所有聲音似乎更新和閃爍。它顯示的方式有些不一致(有時它們全部閃爍,有時候它們全都是空白的);但是當他們更新和閃爍時,進度指示器似乎大致對應於當前正在播放的聲音。所以我很確定這個問題必須與我更新表的方式有關。我只是不確定問題出在哪裏或如何解決。

這裏是什麼窗口的樣子給你一個想法的屏幕截圖:

Table View Screen Shot

任何意見或指導意見,將不勝感激!

+0

僅供參考:Apple的「以編程方式填充表格視圖」文檔位於以下位置:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TableView/PopulatingView-TablesProgrammatically/PopulatingView-TablesProgrammatically.html –

+0

' makeViewWithIdentifier:owner:'「用指定的標識符返回一個新的或**現有的**視圖。」所有的聲音都可以指向相同的控制。 – Willeke

+0

謝謝,@Willeke。我檢查了一下,每行顯示進度指示器確實是_different_。我在我的'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; 
} 

refreshWindow被分成refreshProgressrefreshWindowrefreshProgress刷新播放聲音的行並被定時器調用。

- (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 reloadDataForRowIndexes ...]',而不是'[tableView removeRowsAtIndexes ...]'手動刪除要刪除的行數據源數組中的項目。我仍然不完全理解_why_'reloadData'會導致表視圖中的_all_進度指示器更新/閃爍,但它是好的,有更好的方法來更新它們。謝謝你給我展示的方式! :) –

+0

'makeViewWithIdentifier'重新使用視圖,當視圖不再使用時,它被放入可重用視圖的池中。當第一個聲音被移除時,閃爍開始。當有10個視圖和9個聲音時,每個'reloadData'前面的9個視圖被重用,並且第10個視圖在下一個'reloadData'上被首先使用。視圖循環並且'viewForTableColumn'沒有在每個視圖中設置進度指示器的值。如果您在'viewForTableColumn'中記錄'sound.name'和'cellView.playProgress'的指針,就可以看到這種情況。 – Willeke

+0

我現在看到 - 明白了。謝謝,@Willeke! :) –