UIProgressView 指示器未使用正确的“进度”更新

Posted

技术标签:

【中文标题】UIProgressView 指示器未使用正确的“进度”更新【英文标题】:UIProgressView indicator not updating with the correct "progress" 【发布时间】:2012-11-29 20:30:12 【问题描述】:

我正在异步下载图像并在 UITableView 中显示它们。在下载图像时,UIProgressView 应显示在相应的表格行中。下载完成后,进度视图应替换为实际图像。

在我的表格视图中,我使用了一个名为 ProgressTableViewCell 的自定义单元格,它是 UITableViewCell 的子类。它有一个 UIProgressView IBOutlet。

我已经从 NSURLConnection 创建了一个 NSOperation 并将它们添加到一个 NSOperationQueue。作为代表的

接收数据

方法被调用,通知被发布到我的表格视图控制器以更新相应的表格行

reloadRowsAtIndexPaths

表格视图的方法。我的 cellForRowAtIndexPath 对重新加载的行执行以下操作:

   ProgressTableViewCell *cell = (ProgressTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"ProgressCell"];

    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    NSNumber* percentage= [NSNumber numberWithFloat:received/total];
    NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
    NSLog(@"percentage %f", percentage.floatValue);
    [userInfo setObject:cell forKey:@"cell"]; 
    [userInfo setObject:percentage forKey:@"percentage"];  

    [self performSelectorOnMainThread:@selector(updateProgressView:) withObject:userInfo waitUntilDone:NO];
    NSLog(@"received: %@", [downloadInfo objectForKey:@"receivedBytes"]);

    NSLog(@"Progress: %f", cell.progressView.progress);
    return cell;

updateProgressView 方法看起来像

- (void)updateProgressView :(NSMutableDictionary *)userInfo

    ProgressTableViewCell* cell = [userInfo valueForKey:@"cell"];

    NSNumber* progress = [userInfo valueForKey:@"percentage"];

   [cell.progressView setProgress:progress.floatValue ];
    NSLog(@"Progress after update: %f", cell.progressView.progress);

我正在更新主线程上的进度视图,我什至尝试将 waitUntilDone 设置为 YES 但无济于事。我的进度视图停留在零点。有时,当我在调试时,我会看到进度指示器发生了一些变化,这让我认为这可能是时间问题。但是如何解决呢?

编辑:这是 NSURLConnection 委托的 didReceiveData 方法:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 
    [_responseData appendData:data];
    NSNumber* bytes = [NSNumber numberWithUnsignedInt:[data length]];

    NSLog(@"received bytes:%d", [bytes intValue] );
    NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
    [userInfo setObject:_responseId forKey:@"responseId"];  
    [userInfo setObject:bytes forKey:@"receivedBytes"];

    [self fireNotification: [NSNotification
                                   notificationWithName:@"DidReceiveData"
                                   object:self userInfo:userInfo]];




- (void)fireNotification :(NSNotification *)aNotification

    [[NSNotificationCenter defaultCenter] postNotification:aNotification];

这是我的视图控制器获取通知的方法:

-(void) dataReceived:(NSNotification *)notification 

    NSNumber* responseId = [[notification userInfo] objectForKey:@"responseId"];
    NSNumber*  bytes = [[notification userInfo] objectForKey:@"receivedBytes"];

    NSMutableDictionary* downloadInfo = [self getConnectionInfoForId:responseId];

    NSLog(@"received bytes:%ld for response %@", [bytes longValue], responseId );
    NSNumber* totalBytes = [NSNumber numberWithInt:([bytes longValue] + [[downloadInfo objectForKey:@"receivedBytes"] longValue]) ];
    [downloadInfo setObject:totalBytes forKey:@"receivedBytes"];

    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    [downloadInfo setObject:[NSNumber numberWithFloat:received/total] forKey:@"progress"];

    [self reloadRowForResponse:responseId];


我还按照建议在我的 cellForRowAtIndexpath 方法中添加了一个 nil 检查:

ProgressTableViewCell *cell = (ProgressTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"ProgressCell"];
    if (cell == nil)
    
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ProgressCell" owner:self options:nil];
        cell = [nib objectAtIndex:0];
    
    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    NSNumber* percentage= [NSNumber numberWithFloat:received/total];
    NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
    NSLog(@"cell:%@", cell);
    NSLog(@"percentage %f", percentage.floatValue);
    [userInfo setObject:cell forKey:@"cell"];  
    [userInfo setObject:percentage forKey:@"percentage"]; 

    [self performSelectorOnMainThread:@selector(updateProgressView:) withObject:userInfo waitUntilDone:NO];

    return cell;

【问题讨论】:

你能发布你的委托方法和你用来处理通知的方法吗?我的猜测是,如果您在通知处理程序中有可用的索引路径和下载信息,您应该在那里更新进度视图,而不是每次都重新加载表格视图。 我已经发布了这些方法乔。您可以在上方的 EDIT 下看到它们。问题是我对正在下载的每个图像都有一个进度视图,这就是为什么我在表格视图的数据源委托方法中进行更新。 【参考方案1】:

我认为每次调用委托方法时都重新加载表格单元格,这是一种错误的方法。您可以直接抓取可见单元格并直接更新进度指示器,而不是通过数据源。

我假设您有某种方法可以将responseId 转换为您要更新的行的索引路径——假设在您的控制器中称为indexPathForResponseID:。无需重新加载单元格,您只需抓取可见的单元格并更新其进度指示器:

- (void)dataReceived:(NSNotification *)notification 

    ...

    float received = [[downloadInfo objectForKey:@"receivedBytes"] floatValue];
    float total = [[downloadInfo objectForKey:@"totalFileSize"] floatValue];

    NSIndexPath *cellPath = [self indexPathForResponseID:responseId];
    ProgressTableViewCell *cell = (ProgressTableviewCell *)[self.tableView cellForRowAtIndexPath:cellPath];
    if (cell) 
        // make sure you're on the main thread to update UI
        dispatch_async(dispatch_get_main_queue(), ^ 
            [cell.progressView setProgress: (received / total)];
        
    

但是,您应该知道,如果您的下载量超过可见单元格,则此解决方案将不够用 - 您还应该将每次下载的进度存储在数据源中的某个位置,以便在表格视图确实需要时要重新加载单元格(由于滚动),它知道如何设置进度指示器。

【讨论】:

终于找到了解决方案(见我自己的答案)。但我今天确实尝试了你的建议,它似乎有效。不记得为什么我不能让它在 2 个月前工作。我已经采取了另一种方式来做这件事,因为由于你提到的原因,我不得不做更多的工作。还是谢谢。【参考方案2】:

我发现在很多这样的情况下,您认为自己拥有的单元格并不是正在更新的单元格。当您重新加载时,您的代码会从可重复使用的单元格中弹出一个单元格,这基本上是一个旧单元格。如果您重新加载,该单元格将被另一个单元格替换。 (如果可重复使用的单元格返回 nil,您也没有包括分配新单元格)如果您的单元格滚动,它将重新加载,因此您必须确保将进度条放在那里的单元格上,而不是一个曾经在那里。您可能希望 NSLog 记录您开始传输的单元格的地址,然后在每次更新进度时再次记录。您需要使用该索引路径处的当前单元格更新进度条。它可能不是您最初启动该过程的那个。

【讨论】:

确实是欧文。当我 NSLog 单元格地址时,我每次都会得到一个不同的地址。但是为什么即使重复使用细胞也会发生这种情况呢?另外,当我只有一个图像下载时,如何更新不同的进度视图。我怎样才能“确保我将进度条放在那里的单元格上,而不是曾经在那里的那个”。 ? 当您重复使用单元格时,这就是您正在做的事情。滚动时,曾经位于第 0 行的单元格将在第 12 行中重复使用。因此,用于启动下载的单元对象不在同一个位置。我使用自定义单元格对象,以便我可以确定哪个单元格有我的下载对象,并通过它们搜索找到它。另请注意,您的下载单元格可能会滚动到屏幕外。重要的是,当您加载/重用新单元格时,您会更新任何文件下载的引用。【参考方案3】:

差不多 2 个月后回到这个问题,我似乎找到了解决方案。不确定这是否是一种好习惯,但每次更新进度时创建一个新的 UIProgressView 似乎可以解决我的问题。方法如下:

-(void)updateProgressView :(NSMutableDictionary *)userInfo

    ProgressTableViewCell* cell = [userInfo objectForKey:@"cell"];
    cell.backgroundColor =[UIColor darkGrayColor];
    NSNumber* progress = [userInfo objectForKey:@"percentage"];
    NSLog(@"Progress before update: %f", cell.progressView.progress);

    UIProgressView *pView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
    pView.frame = CGRectMake(150, 200, 150, 9);
    pView.progress = progress.floatValue;

    [cell.contentView addSubview:pView];

非常感谢大家的帮助。

【讨论】:

以上是关于UIProgressView 指示器未使用正确的“进度”更新的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 CGAffine 正确缩小 UIProgressView?

UIProgressView 未处理的颜色

NSTimer 触发 UIProgressView 失败

带有 Jetpack Navigation 的 BottomNavigationView 未正确显示活动菜单指示器

如何拦截 StoreKit“购买对话框”以使 MBProgressHUD / UIProgressView 在正确的时间正确显示?

如何增加 UIProgressView 的高度