programing

UITableView 셀 내부의 URL에서 비동기 이미지 로드 - 스크롤하는 동안 이미지가 잘못된 이미지로 변경됨

closeapi 2023. 4. 26. 23:22
반응형

UITableView 셀 내부의 URL에서 비동기 이미지 로드 - 스크롤하는 동안 이미지가 잘못된 이미지로 변경됨

저는 제 UITableView 셀 안에서 사진을 동기화하는 두 가지 방법을 썼습니다.두 경우 모두 이미지가 로드되지만 테이블을 스크롤하면 스크롤이 종료되고 이미지가 올바른 이미지로 돌아갈 때까지 이미지가 몇 번 변경됩니다.저는 왜 이런 일이 일어나는지 전혀 모르겠습니다.

#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

- (void)viewDidLoad
{
    [super viewDidLoad];
    dispatch_async(kBgQueue, ^{
        NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
                                                       @"http://myurl.com/getMovies.php"]];
        [self performSelectorOnMainThread:@selector(fetchedData:)
                               withObject:data waitUntilDone:YES];
    });
}

-(void)fetchedData:(NSData *)data
{
    NSError* error;
    myJson = [NSJSONSerialization
              JSONObjectWithData:data
              options:kNilOptions
              error:&error];
    [_myTableView reloadData];
}    

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    // Return the number of rows in the section.
    // Usually the number of items in your array (the one that holds your list)
    NSLog(@"myJson count: %d",[myJson count]);
    return [myJson count];
}
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

        myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        if (cell == nil) {
            cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
        }

        dispatch_async(kBgQueue, ^{
        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];

            dispatch_async(dispatch_get_main_queue(), ^{
        cell.poster.image = [UIImage imageWithData:imgData];
            });
        });
         return cell;
}

... ...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

            myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
            if (cell == nil) {
                cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
            }
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];


    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse * response,
                                               NSData * data,
                                               NSError * error) {
                               if (!error){
                                   cell.poster.image = [UIImage imageWithData:data];
                                   // do whatever you want with image
                               }

                           }];
     return cell;
}

빠른 전술적 해결책을 찾고 있다고 가정하면 셀 이미지가 초기화되고 셀 행이 계속 표시되는지 확인해야 합니다. 예를 들어 다음과 같습니다.

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

    cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            if (image) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)
                        updateCell.poster.image = image;
                });
            }
        }
    }];
    [task resume];

    return cell;
}

위의 코드는 셀이 재사용된다는 사실에서 발생하는 몇 가지 문제를 해결합니다.

  1. 배경 요청을 시작하기 전에 셀 이미지를 초기화하지 않습니다. 즉, 새 이미지를 다운로드하는 동안 대기열에서 해제된 셀의 마지막 이미지가 계속 표시됩니다.!nil그자리의 image이미지 보기의 속성을 표시하지 않으면 이미지가 깜박거립니다.

  2. 더 미묘한 문제는 매우 느린 네트워크에서 셀이 화면에서 스크롤되기 전에 비동기 요청이 완료되지 않을 수 있다는 것입니다.당신은 할 수 .UITableViewcellForRowAtIndexPath:(비슷한 이름과 혼동하지 말 것.UITableViewDataSourcetableView:cellForRowAtIndexPath:해당 행의 셀이 계속 표시되는지 확인합니다.는 이메드반니다됩환는을 반환합니다.nil셀이 보이지 않는 경우.

    문제는 비동기 메서드가 완료될 때까지 셀이 스크롤오프되었으며, 더 나쁜 것은 셀이 테이블의 다른 행에 재사용되었다는 것입니다.행이 계속 표시되는지 확인하면 이후 화면에서 스크롤된 행에 대한 이미지로 실수로 이미지를 업데이트하지 않습니다.

  3. 당면한 질문과 다소 관련이 없지만, 저는 여전히 현대적인 관습과 API를 활용하기 위해 이것을 업데이트해야 한다고 느꼈습니다.

    • 사용하다NSURLSession하는 것보다-[NSData contentsOfURL:]백그라운드 대기열로;

    • 사용하다dequeueReusableCellWithIdentifier:forIndexPath:dequeueReusableCellWithIdentifier:, 해당 에는 셀 (단, 해식별자에를대또클는레래지입터또스는합다니야용사해스타로당해토프셀)를▁(,▁or▁prototype

    • 코코아 명명 규칙(예: 대문자로 시작)을 준수하는 클래스 이름을 사용했습니다.

이러한 수정에도 다음과 같은 문제가 있습니다.

  1. 위 코드는 다운로드한 이미지를 캐시하지 않습니다.즉, 이미지를 화면 밖으로 스크롤했다가 다시 화면으로 이동하면 앱이 이미지 검색을 다시 시도할 수 있습니다.가 아마도신당은당신서버응헤제답상더투공당는캐명다히싱에서 하는 상당히 투명한 은 운이 좋을 것입니다.NSURLSession그리고.NSURLCache하지만 그렇지 않으면 불필요한 서버 요청을 하고 훨씬 더 느린 UX를 제공하게 됩니다.

  2. 화면 밖으로 스크롤하는 셀에 대한 요청은 취소하지 않습니다.따라서 100번째 행으로 빠르게 스크롤하면 해당 행의 이미지가 더 이상 볼 수 없는 이전 99개 행에 대한 요청 뒤에 다시 기록될 수 있습니다.최상의 UX를 위해 항상 보이는 셀에 대한 요청의 우선순위를 지정해야 합니다.

하는 가장 은 이한문해가간방은해법결단을 입니다.UIImageView카테고리(예: SDWebImage 또는 AFNetworking과 함께 제공됨)만약 당신이 원한다면, 위의 문제들을 다루기 위해 당신 자신의 코드를 작성할 수 있지만, 그것은 많은 일이고, 위의 것들입니다.UIImageView카테고리는 이미 이 작업을 수행했습니다.

저는 이런 식으로 그것을 해왔고 또한 그것을 시험했습니다 */

1단계 = viewDidLoad 메서드에서 다음과 같은 테이블에 사용자 정의 셀 클래스(테이블에 프로토타입 셀인 경우) 또는 닙(사용자 정의 셀용 사용자 정의 닙인 경우)을 등록합니다.

[self.yourTableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"CustomCell"];

OR

[self.yourTableView registerNib:[UINib nibWithNibName:@"CustomTableViewCell" bundle:nil] forCellReuseIdentifier:@"CustomCell"];

2단계 = UITableView의 "decueReusableCellWithIdentifier: forIndexPath:" 메서드를 사용합니다(이 경우 클래스 또는 nib를 등록해야 함).

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

            cell.imageViewCustom.image = nil; // [UIImage imageNamed:@"default.png"];
            cell.textLabelCustom.text = @"Hello";

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // retrive image on global queue
                UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL:     [NSURL URLWithString:kImgLink]]];

                dispatch_async(dispatch_get_main_queue(), ^{

                    CustomTableViewCell * cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
                  // assign cell image on main thread
                    cell.imageViewCustom.image = img;
                });
            });

            return cell;
        }

이 문제를 해결하는 여러 프레임워크가 있습니다.몇 가지 예를 들자면:

스위프트:

목표-C:

스위프트 3

NSCache를 사용하여 이미지 로더용 조명 구현을 직접 작성합니다.셀 이미지 깜박임 금지!

ImageCacheLoader.swift

typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())

class ImageCacheLoader {
    
    var task: URLSessionDownloadTask!
    var session: URLSession!
    var cache: NSCache<NSString, UIImage>!
    
    init() {
        session = URLSession.shared
        task = URLSessionDownloadTask()
        self.cache = NSCache()
    }
    
    func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) {
        if let image = self.cache.object(forKey: imagePath as NSString) {
            DispatchQueue.main.async {
                completionHandler(image)
            }
        } else {
            /* You need placeholder image in your assets, 
               if you want to display a placeholder to user */
            let placeholder = #imageLiteral(resourceName: "placeholder")
            DispatchQueue.main.async {
                completionHandler(placeholder)
            }
            let url: URL! = URL(string: imagePath)
            task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
                if let data = try? Data(contentsOf: url) {
                    let img: UIImage! = UIImage(data: data)
                    self.cache.setObject(img, forKey: imagePath as NSString)
                    DispatchQueue.main.async {
                        completionHandler(img)
                    }
                }
            })
            task.resume()
        }
    }
}

사용 예

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")
    
    cell.title = "Cool title"

    imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
        // Before assigning the image, check whether the current cell is visible
        if let updateCell = tableView.cellForRow(at: indexPath) {
            updateCell.imageView.image = image
        }
    }    
    return cell
}

다음은 빠른 버전입니다(@NiteshBorad 목적 C 코드 사용).

   if let img: UIImage = UIImage(data: previewImg[indexPath.row]) {
                cell.cardPreview.image = img
            } else {
                // The image isn't cached, download the img data
                // We should perform this in a background thread
                let imgURL = NSURL(string: "webLink URL")
                let request: NSURLRequest = NSURLRequest(URL: imgURL!)
                let session = NSURLSession.sharedSession()
                let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
                    let error = error
                    let data = data
                    if error == nil {
                        // Convert the downloaded data in to a UIImage object
                        let image = UIImage(data: data!)
                        // Store the image in to our cache
                        self.previewImg[indexPath.row] = data!
                        // Update the cell
                        dispatch_async(dispatch_get_main_queue(), {
                            if let cell: YourTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as? YourTableViewCell {
                                cell.cardPreview.image = image
                            }
                        })
                    } else {
                        cell.cardPreview.image = UIImage(named: "defaultImage")
                    }
                })
                task.resume()
            }

가장 좋은 대답은 이를 수행하는 올바른 방법이 아닙니다. :(실제로 indexPath를 모델로 바인딩했지만 항상 좋은 것은 아닙니다.이미지를 로드하는 동안 일부 행이 추가되었다고 가정합니다.이제 지정된 indexPath에 대한 셀이 화면에 있지만 이미지가 더 이상 올바르지 않습니다!상황은 그럴 것 같지도 않고 재현하기도 어렵지만 가능합니다.

MVVM 접근 방식을 사용하여 컨트롤러에서 viewModel로 셀을 바인딩하고 viewModel에서 이미지를 로드한 다음(switchToLatest 메서드로 ReactiveCocoa 신호 할당) 이 신호를 구독하고 이미지를 셀에 할당하는 것이 좋습니다! ;)

MVVM을 남용하지 않도록 기억해야 합니다.보기는 아주 단순해야 합니다!View 모델은 재사용 가능해야 합니다!컨트롤러에서 View(UITableViewCell)와 ViewModel을 바인딩하는 것이 매우 중요한 이유입니다.

저의 경우 이미지 캐싱(Used SD Web Image) 때문이 아니었습니다.사용자 지정 셀의 태그가 indexPath.row와 일치하지 않기 때문입니다.

행에 대한 셀:인덱스 경로:

사용자 지정 셀에 인덱스 값을 할당합니다.예를 들어.

cell.tag = indexPath.row

메인 스레드에서 이미지를 할당하기 전에 이미지가 해당 셀에 속하는지 태그와 일치하는지 확인합니다.

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

"Rob" 감사합니다...UICollectionView에도 동일한 문제가 있었고 당신의 답변은 제 문제를 해결하는 데 도움이 됩니다.내 코드는 다음과 같습니다.

 if ([Dict valueForKey:@"ImageURL"] != [NSNull null])
    {
        cell.coverImageView.image = nil;
        cell.coverImageView.imageURL=nil;

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            if ([Dict valueForKey:@"ImageURL"] != [NSNull null] )
            {
                dispatch_async(dispatch_get_main_queue(), ^{

                    myCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];

                    if (updateCell)
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;

                        cell.coverImageView.imageURL=[NSURL URLWithString:[Dict valueForKey:@"ImageURL"]];

                    }
                    else
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;
                    }


                });
            }
        });

    }
    else
    {
        cell.coverImageView.image=[UIImage imageNamed:@"default_cover.png"];
    }
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
        MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

        cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

        NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (data) {
                UIImage *image = [UIImage imageWithData:data];
                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                        if (updateCell)
                            updateCell.poster.image = image;
                    });
                }
            }
        }];
        [task resume];

        return cell;
    }

배경에 있는 셀에 대한 이미지 로딩 시 셀 로딩 속도를 높이고 싶은 것 같습니다.이를 위해 다음 단계를 수행했습니다.

  1. 파일이 문서 디렉토리에 있는지 확인합니다.

  2. 그렇지 않은 경우 이미지를 처음 로드하고 전화 문서 디렉토리에 저장합니다.전화기에 이미지를 저장하지 않으려면 배경에 직접 셀 이미지를 로드할 수 있습니다.

  3. 이제 로드 프로세스:

다음을 포함합니다.#import "ManabImageOperations.h"

셀의 코드는 다음과 같습니다.

NSString *imagestr=[NSString stringWithFormat:@"http://www.yourlink.com/%@",[dictn objectForKey:@"member_image"]];

        NSString *docDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
        NSLog(@"Doc Dir: %@",docDir);

        NSString  *pngFilePath = [NSString stringWithFormat:@"%@/%@",docDir,[dictn objectForKey:@"member_image"]];

        BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pngFilePath];
        if (fileExists)
        {
            [cell1.memberimage setImage:[UIImage imageWithContentsOfFile:pngFilePath] forState:UIControlStateNormal];
        }
        else
        {
            [ManabImageOperations processImageDataWithURLString:imagestr andBlock:^(NSData *imageData)
             {
                 [cell1.memberimage setImage:[[UIImage alloc]initWithData: imageData] forState:UIControlStateNormal];
                [imageData writeToFile:pngFilePath atomically:YES];
             }];
}

마나브 이미지 작업.h:

#import <Foundation/Foundation.h>

    @interface ManabImageOperations : NSObject
    {
    }
    + (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
    @end

ManabImageOperations.m:

#import "ManabImageOperations.h"
#import <QuartzCore/QuartzCore.h>
@implementation ManabImageOperations

+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_main_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
  //  downloadQueue=nil;
    dispatch_release(downloadQueue);

}
@end

답변을 확인하시고 문제가 발생하면 댓글을 달아주시기 바랍니다….

간단히 말하자면,

dispatch_async(kBgQueue, ^{
     NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
     dispatch_async(dispatch_get_main_queue(), ^{
        cell.poster.image = [UIImage imageWithData:imgData];
     });
 });

안으로

    dispatch_async(kBgQueue, ^{
         NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
         cell.poster.image = [UIImage imageWithData:imgData];
         dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
         });
     });

당신의 URL을 전달하면 됩니다.

NSURL *url = [NSURL URLWithString:@"http://www.myurl.com/1.png"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data,    NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                    yourimageview.image = image;
            });
        }
    }
}];
[task resume];
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    Static NSString *CellIdentifier = @"Cell";
    QTStaffViewCell *cell = (QTStaffViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    If (cell == nil)
    {

        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"QTStaffViewCell" owner:self options:nil];
        cell = [nib objectAtIndex: 0];

    }

    StaffData = [self.staffArray objectAtIndex:indexPath.row];
    NSString *title = StaffData.title;
    NSString *fName = StaffData.firstname;
    NSString *lName = StaffData.lastname;

    UIFont *FedSanDemi = [UIFont fontWithName:@"Aller" size:18];
    cell.drName.text = [NSString stringWithFormat:@"%@ %@ %@", title,fName,lName];
    [cell.drName setFont:FedSanDemi];

    UIFont *aller = [UIFont fontWithName:@"Aller" size:14];
    cell.drJob.text = StaffData.job;
    [cell.drJob setFont:aller];

    if ([StaffData.title isEqualToString:@"Dr"])
    {
        cell.drJob.frame = CGRectMake(83, 26, 227, 40);
    }
    else
    {
        cell.drJob.frame = CGRectMake(90, 26, 227, 40);

    }

    if ([StaffData.staffPhoto isKindOfClass:[NSString class]])
    {
        NSURL *url = [NSURL URLWithString:StaffData.staffPhoto];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
                completionHandler:^(NSURL *location,NSURLResponse *response, NSError *error) {

      NSData *imageData = [NSData dataWithContentsOfURL:location];
      UIImage *image = [UIImage imageWithData:imageData];

      dispatch_sync(dispatch_get_main_queue(),
             ^{
                    cell.imageView.image = image;
              });
    }];
        [task resume];
    }
       return cell;}

언급URL : https://stackoverflow.com/questions/16663618/async-image-loading-from-url-inside-a-uitableview-cell-image-changes-to-wrong

반응형