Pregunta

My app

The purpose of my app is to download subtitles files for WWDC videos. The subtitles are available as WebVTT files named “fileSequence0.webvtt”, “fileSequence1.webvtt”, etc., in a grand-subdirectory of the directory where the video and slides files are.

If you have any WWDC video (or slides) file, you can get the URL you downloaded it from, strip the filename, and add the requisite extra path components to make a subtitles URL, and then download that subtitles file—and that's what my app does.

My model

Although I initially envisioned a two-tiered model, I eventually arrived at a single flat list, so I'm just using the outline view as a plain table view at the moment—no items have any children.

My items are Download Sources, and each one has the following properties:

  • Video file name
  • Current subtitles file index (starts from 0)
  • Current download progress fraction (proceeds from 0 to 1 for each subtitles file)
  • All subtitles files downloaded (bool), henceforth referred to as “Done”

When I feed a video file into my app, it creates a Download Source that yields each subtitles file's remote URL in turn. My app downloads each subtitles file, updating state of the Download Source as it goes.

My app has no way to know how many subtitles files there are; all it knows is that they are numbered. So, when it gets a 404, it knows that it has downloaded all of the subtitles files for that video file, so it marks that Download Source as Done and proceeds to the next Download Source (if there is one).

How the outline view is populated

My window controller is my outline view's data source and delegate.

The outline view is view-based. Within the row view, three of the four cell views are bound to a property of their object value:

  • The text field in the first column is bound to the video filename.
  • The text field in the second column is bound to the current subtitles file index.
  • The progress bar in the third column is bound to the progress fraction.

The fourth column holds an image view, and that's where the difficulty comes in.

Because I don't want to pollute my model with a “doneImage” property (I feel that that is controller business), I have the controller setting the image view's image when it returns the cell view:

- (NSView *) outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    NSTableCellView *cellView = [outlineView makeViewWithIdentifier:tableColumn.identifier owner:self];
    PRHVideoFileDownloadSource *downloadSource = item;
    cellView.objectValue = downloadSource;
    if ([tableColumn.identifier isEqualToString:@"done"]) {
        NSLog(@"Source %@ is done: %@", downloadSource, downloadSource.complete ? @"true" : @"false");
        cellView.imageView.image = downloadSource.complete ? [NSImage imageNamed:@"Done"] : nil;
    }
    return cellView;
}

The problem

Initially, this works fine. If I take out the condition that tests whether a model object is done, the Done image appears fine (so I know that the image is being copied, it's a valid icon, the column's identifier is set correctly, etc.).

But, with that condition in place, the image never appears, even after a Download Source becomes Done.

You can see that I added an NSLog. This only appears once (per row):

2013-07-22 15:46:18.028 WWDC Subtitles Fetcher[7980:1307] Source <PRHVideoFileDownloadSource 0x7fd6f533d340 "404-Advances in Objective-C.pdf"> is done: false
2013-07-22 15:46:18.035 WWDC Subtitles Fetcher[7980:1307] Source <PRHVideoFileDownloadSource 0x10b372190 "405-Interface Builder Core Concepts-SD.mov"> is done: false

The cells that are populated by binding to properties of the cell view's objectValue all work fine. Only this cell, whose content view is not bound, does not update.

What I've tried

As you see above, I've tried logging when outlineView:viewForTableColumn:item: is called. It is called exactly once for each row; never again.

So, maybe I need to cue it to reload those rows, right? I added a reloadItem: message immediately after the line that marks a Download source as done. No dice—it is definitely reached (I had another bug, since fixed, that caused an exception when I did that), but the outline view does not take the hint and ask me to recreate/update this view.

I also tried binding the image view to the cell view's objectValue and implementing outlineView:objectValueForTableColumn:byItem::

//Revised outlineView:viewForTableColumn:item: that doesn't set the cell view's objectValue
- (NSView *) outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    NSTableCellView *cellView = [outlineView makeViewWithIdentifier:tableColumn.identifier owner:self];
    return cellView;
}

//Return the Done image for the “done” column; return the Download Source for all other columns (whose cells' content views are bound to properties of the Download Source)
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    PRHVideoFileDownloadSource *downloadSource = item;
    if ([tableColumn.identifier isEqualToString:@"done"]) {
        NSLog(@"Source %@ is done: %@", downloadSource, downloadSource.complete ? @"true" : @"false");
        return downloadSource.complete ? [NSImage imageNamed:@"Done"] : nil;
}
    return downloadSource;
}

Although this is cleaner, to the point that I think I'll keep it, it does not solve the problem—the objectValueForTableColumn:byItem: method is likewise only ever called once, despite reloadItem:, and consequently does not get a chance to dispense the image as that cell's new objectValue.

What I need

I need a way (that works) to tell the outline view “this particular row has changed; please update its objectValues”—or, even better, a way to tell it that a particular cell in that row has changed.

¿Fue útil?

Solución

Rather than returning the checkmark image for the "done" column and the download source for the other columns, return the download source for all columns. Then bind the image view's "hidden" binding to the download source's "complete" property with an NSNegateBoolean value transformer. This will allow the image to show or hide itself when your model fires KVO notifications, so you don't have to refresh the item and hope that will update things the way you want.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top