2013-03-27 3 views
30

GCD를 사용하여 uitableview 용 이미지를 비동기식으로 다운로드하지만 이미지 스크롤이 깜박 거리고 항상 변경 될 때 문제가 발생합니다. 모든 셀에 이미지를 고정 시키려고했지만 많은 도움이되지 않습니다. 빠르게 위로 스크롤하면 모든 이미지가 잘못됩니다. 그것에 대해 무엇을 할 수 있습니까?GCD를 사용하여 UITableView를 비동기로 다운로드

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 

       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 

       UIImage* image = [[UIImage alloc] initWithData:imageData]; 

       dispatch_async(dispatch_get_main_queue(), ^{ 
        cell.imageView.image = image; 
        [cell setNeedsLayout]; 
        }); 
      }); 

    cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 
    return cell; 
} 

답변

89

여기에 문제가 이미지 가져 오는 블록이있는 tableview 세포에 대한 참조를 유지한다는 것이다 : 다음은 세포 내 방법입니다. 다운로드가 완료되면 다른 행을 표시하도록 셀을 재활용 했더라도 imageView.image 속성이 설정됩니다.

이미지를 설정하기 전에 이미지가 셀과 관련성이 있는지 테스트하려면 다운로드 완료 블록이 필요합니다.

셀 이외의 위치에 이미지를 저장하지 않으므로 화면에서 행을 스크롤 할 때마다 이미지를 다시 다운로드하게됩니다. 다운로드를 시작하기 전에 어딘가에 캐시하고 로컬 캐시 된 이미지를 찾고 싶을 것입니다.

편집 :

- (UITableViewCell *)tableView:(UITableView *)tableView 
     cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

    cell.tag = indexPath.row; 
    NSDictionary *parsedData = self.loader.parsedData[indexPath.row]; 
    if (parsedData) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
     dispatch_async(queue, ^(void) { 

      NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]]; 

      UIImage* image = [[UIImage alloc] initWithData:imageData]; 
      if (image) { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        if (cell.tag == indexPath.row) { 
         cell.imageView.image = image; 
         [cell setNeedsLayout]; 
        } 
       }); 
      } 
     }); 

     cell.textLabel.text = parsedData[@"id"]; 
    } 
    return cell; 
} 
+0

@SeamusCampbell로 변경해야'재사용 된 셀에 대한 동일 indexPath.row' 없습니다? 이 경우 태그 검사가 부적절하게 통과합니다. –

+0

예, 위의 코드는 한 섹션 테이블을 가정합니다. –

+0

@SeamusCampbell, 다중 섹션 테이블에 대한 생각 :) –

7

요점은 완전히 셀 재사용 개념을 이해하지 않았다입니다 : 여기에 테스트 할 수있는 간단한 방법은 세포의 tag 속성을 사용합니다. 이는 비동기 다운로드와 잘 일치하지 않습니다.

요청이 완료되고 모든 데이터가로드 될 때

^{ 
    cell.imageView.image = image; 
    [cell setNeedsLayout]; 
} 

실행 블록 얻는다. 그러나 셀은 블록을 만들 때 그 가치를 얻습니다.

블록이 실행될 때까지 셀 스틸은 기존 셀 중 하나를 가리 킵니다. 그러나 사용자가 계속 스크롤 할 가능성이 큽니다. 그 동안 셀 객체가 다시 사용되고 이미지는 'old'셀과 연관되어 재사용되고 할당되어 표시됩니다. 그 직후에 사용자가 더 이상 스크롤하지 않으면 정확한 이미지가로드되고 지정되고 표시됩니다. 등등.

더 똑똑한 방법을 찾아야합니다. 많은 turorials가있다. 게으른 이미지 로딩을위한 Google

6

셀을 가져 오는 데 인덱스 경로를 사용하십시오. 표시되지 않으면 셀이 nil이고 문제가되지 않습니다. 물론 이미지를 이미 가지고있을 때 셀의 이미지를 설정하기 위해 다운로드 할 때 데이터를 캐시하고 싶을 것입니다.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

    if (self.loader.parsedData[indexPath.row] != nil) 
    { 
     cell.imageView.image = nil; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
      dispatch_async(queue, ^(void) { 
       // You may want to cache this explicitly instead of reloading every time. 
       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.loader.parsedData[indexPath.row] objectForKey:@"imageLR"]]]; 
       UIImage* image = [[UIImage alloc] initWithData:imageData]; 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        // Capture the indexPath variable, not the cell variable, and use that 
        UITableViewCell *blockCell = [tableView cellForRowAtIndexPath:indexPath]; 
        blockCell.imageView.image = image; 
        [blockCell setNeedsLayout]; 
       }); 
      }); 
     cell.textLabel.text = [self.loader.parsedData[indexPath.row] objectForKey:@"id"]; 
    } 

    return cell; 
} 
+0

참고로, 매우 빠른 스크롤에서는이 기능이 작동하지 않습니다. 이미지가 잘못된 셀에 추가 된 것을 볼 수 있습니다. 셀이 재사용되기 직전에'[tableView cellForRowAtIndexPath : indexPath]'를 호출하면 재사용 된 후에'blockCell.imageView.image = image'에 대한 호출이 셀에 대해 매우 잘 호출 될 수 있습니다. –

+0

@AndrewRobinson 빠른 스크롤로 작동하지 않으면 작동하지 않습니다. 그러나 당신이 묘사하는 방식으로이 접근법이 실패하는 것을 보지 못했습니다. 블록을 실행하는 동안 다른 인덱스 경로에 대해 셀을 재사용하는 방법을 자세히 설명 할 수 있습니까? –

+0

@AndrewRobinson이 github 또는 내가 조사 할 수있는 것과 함께 할 수있는 샘플 프로젝트를 게시 할 수 있다면, 불행히도 내 머리 꼭대기에서 통찰력을 얻지는 못했지만 프로젝트를 보지 못했습니다. –

1

SDWebImage를 사용 해본 적 있으십니까? 지정된 URL의 이미지도 비동기 적으로 다운로드합니다. 전체 라이브러리 (UIImageView + WebCache.h 만 가져 오기)를 사용하지 않았습니다. 수입되면, 당신이 할 일은 같은 메서드를 호출 할 수 있습니다 : 당신이 AFNetworking 2.0을 사용하는 경우 그것은 과잉 수 있습니다

[UIImageXYZ sd_setImageWithURL:["url you're retrieving from"] placeholderImage:[UIImage imageNamed:@"defaultProfile.jpeg"]]; 

을하지만 날 위해 일했습니다.

Here is the link to the github if you want to give it a try

3

나는이 문제에 대해 공부했고 나는있는 UITableViewCell을 사용자 정의하여 우수한 방법을 발견했다.

#import <UIKit/UIKit.h> 

@interface MyCustomCell : UITableViewCell 

@property (nonatomic, strong) NSURLSessionDataTask *imageDownloadTask; 
@property (nonatomic, weak) IBOutlet UIImageView *myImageView; 
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *activityIndicator; 

@end 

지금, 당신의 TableViewController에, NSURLSessionConfiguration 및 NSURLSession에 대한 두 가지 속성을 선언하고있는 viewDidLoad에서 그들을 초기화 : 당신의 데이터 소스가 NSMutableDictionary의 배열 (또는 NSManagedObjectContext)이다

@interface MyTableViewController() 

@property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; 
@property (nonatomic, strong) NSURLSession *session; 
. 
. 
. 
@end 

@implementation TimesVC 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 
    _session = [NSURLSession sessionWithConfiguration:_sessionConfig]; 
} 

. 
. 
. 

하자 supose. 다음과 같이 캐싱을 사용하여 각 셀의 이미지를 쉽게 다운로드 할 수 있습니다.

. 
. 
. 
- (MyCustomCell *)tableView:(UITableView *)tableView 
    cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    MyCustomCell *cell = (MyCustomCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 

    if (!cell) 
    { 
     cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault 
           reuseIdentifier:@"cell"]; 
    } 

    NSMutableDictionary *myDictionary = [_myArrayDataSource objectAtIndex:indexPath.row];  

    if (cell.imageDownloadTask) 
    { 
     [cell.imageDownloadTask cancel]; 
    } 

    [cell.activityIndicator startAnimating]; 
    cell.myImageView.image = nil; 

    if (![myDictionary valueForKey:@"image"]) 
    { 
     NSString *urlString = [myDictionary valueForKey:@"imageURL"]; 
     NSURL *imageURL = [NSURL URLWithString:urlString]; 
     if (imageURL) 
     { 
      cell.imageDownloadTask = [_session dataTaskWithURL:imageURL 
       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
      { 
       if (error) 
       { 
        NSLog(@"ERROR: %@", error); 
       } 
       else 
       { 
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 

        if (httpResponse.statusCode == 200) 
        { 
         UIImage *image = [UIImage imageWithData:data]; 

         dispatch_async(dispatch_get_main_queue(), ^{ 
          [myDictionary setValue:data forKey:@"image"]; 
          [cell.myImageView setImage:image]; 
          [cell.activityIndicator stopAnimating]; 
         }); 
        } 
        else 
        { 
         NSLog(@"Couldn't load image at URL: %@", imageURL); 
         NSLog(@"HTTP %d", httpResponse.statusCode); 
        } 
       } 
      }]; 

      [cell.imageDownloadTask resume]; 
     } 
    } 
    else 
    { 
     [cell.myImageView setImage:[UIImage imageWithData:[myDictionary valueForKey:@"image"]]]; 
     [cell.activityIndicator stopAnimating]; 
    } 

    return cell; 
} 

일부 개발자의 도움이 되었기를 바랍니다. 건배.

크레딧 : Table View Images in iOS 7

2

단지 멋진 포드의 커플 사용, 바퀴를 재발견하지 마십시오

https://github.com/rs/SDWebImage

https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage

으로 간단하게 :

[self.eventImage setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [SchemeConfiguration APIEndpoint] , _event.imageUrl]] 
          placeholderImage:nil 
           completed:^(UIImage *image, 
              NSError *error, 
              SDImageCacheType cacheType, 
              NSURL *imageURL) 
    { 
     event.image = UIImagePNGRepresentation(image); 
    } usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 
+0

제 3 자 라이브러리없이이를 구현하기위한 인터뷰 제한이있었습니다. – Dvole

1

허용 옆에 Seamus Campbell 당신은 또한 이것이 언젠가는 작동하지 않는다는 것을 알아야합니다. 이 경우 특정 셀을 다시로드해야합니다. 그것은 다른 부분에 있다면 그래서

if (image) { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
      if (cell.tag == indexPath.row) { 
       cell.imageView.image = image; 
       [cell setNeedsLayout]; 
      } 
     }); 
} 

if (image) { 
     dispatch_async(dispatch_get_main_queue(), ^{ 
       if (cell.tag == indexPath.row) { 
        cell.imageView.image = image; 
        self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None) 
       } 
      }); 
    } 
관련 문제