NSTableView reloadData方法导致所有行中的NSProgressIndicator更新并闪烁

时间:2017-05-19 02:13:52

标签: xcode cocoa nstableview flicker

注意:我搜索过Stack Overflow是否存在类似的问题,我发现的任何问题似乎都没有解决这个问题。

我已经编写了一个小样本应用程序(完整的Xcode项目,源代码可在此处http://jollyroger.kicks-ass.org/stackoverflow/FlickeringTableView.zip),它可以按顺序播放/System/Library/Sounds/中的所有声音并在窗口中显示声音,因为它们是用来表明我所看到的问题。 MainMenu.xib中的窗口有一个单列NSTableView,其中一行定义为一个单元格模板,其中包含三个项目:

  • 一个NSTextField来保存声音名称
  • 另一个NSTextField来保存声音细节
  • a NSProgressIndicator以显示正在播放声音时的播放进度

我已经将NSTableCellViewSoundsTableCellView.h)子类化,以定义单元格视图中的每个项目,以便我可以在时间出现时访问和设置它们。

我已经定义了一个MySound类,它封装了通过AVAudioPlayer API处理声音文件播放所需的属性和方法。此类定义了MySoundDelegate协议,以允许应用程序委托在声音开始或结束播放时接收事件。

应用程序委托遵守NSTableViewDelegateNSTableViewDataSource协议,允许它将表数据存储为MySound个对象的数组,并在需要时使用相关信息更新表。它还遵循MySoundDelegate协议,以便在声音开始或结束播放时接收事件。该委托还有一个NSTimer任务,定期调用refreshWindow方法来更新当前播放声音的进度指示器。

app delegate的refreshWindow方法根据列表中的声音数量显示窗口并调整窗口大小,并将存储的引用更新为正在播放的声音的关联NSProgressIndicator。 / p>

调用app委托的tableView: viewForTableColumnNSTableViewDelegate协议)方法来填充表格单元格。在其中,我使用Apple的标准“以程序方式填充表格视图”建议:

  1. 检查表列标识符以确保它与 标识符(sound column)我在Interface Builder(Xcode)中为表列设置了
  2. 通过调用sound cell
  3. 获取具有标识符(thisTableView makeViewWithIdentifier)的相应表格单元格
  4. 使用传入的row参数来定位匹配的数组元素 数据源(app delegate sounds数组),然后
  5. 设置NSTextFields的字符串值,并将单元格中maxValue的{​​{1}}和doubleValue设置为相关声音对象的相应详细信息,
  6. 在关联的声音对象中存储对关联的NSProgressIndicator控件的引用,以便以后更新
  7. 以下是NSProgressIndicator方法:

    viewForTableColumn

    这是- (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方法按顺序开始播放列表中的第一个声音。

    问题(通过运行示例项目可以看到)不是仅更新当前正在播放的声音的顶部表行,而是在以下行的 all 中的进度指示器似乎也更新和闪烁。它显示的方式有些不一致(有时它们都闪烁,有时它们都是预期的空白);但是当他们确实更新并闪烁时,进度指示器似乎大致对应于当前正在播放的声音。所以我很确定这个问题必须与我更新表的方式有某种关系;我只是不确定问题出在哪里或如何解决。

    这是一个屏幕截图,显示了窗口的样子,可以给你一个想法:

    Table View Screen Shot

    非常感谢任何想法或指导!

1 个答案:

答案 0 :(得分: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]未被调用。 <{1}}未使用。