2012-12-13 2 views
12

저는 꽤 많은 사진 (50-200)을 표시하는 응용 프로그램에서 UICollectionView을 사용하고 있습니다. 예를 들어 사진 응용 프로그램처럼 멋지게 보이게하는 데 문제가 있습니다.많은 이미지 (50-200)로 멋진 UICollectionView를 얻는 방법?

UICollectionViewCell은 서브 뷰로 UIImageView입니다. UIImage.imageWithContentsOfFile:으로 파일 시스템의 이미지를 셀 내부의 UIImageViews에로드하고 있습니다.

지금까지 꽤 많은 접근법을 시도했지만 버그가 있거나 성능 문제가있었습니다.

참고 : RubyMotion을 사용하고 있으므로 Ruby 스타일로 코드 스 니펫을 작성합니다. 모든

먼저, 여기

def collectionView(collection_view, cellForItemAtIndexPath: index_path) 
    cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path) 
    image_path = @image_paths[index_path.row] 
    cell.image = UIImage.imageWithContentsOfFile(image_path) 
end 

는/최대 스크롤 .. 참조 내 사용자 지정 UICollectionViewCell 클래스 ...

class CustomCell < UICollectionViewCell 
    def initWithFrame(rect) 
    super 

    @image_view = UIImageView.alloc.initWithFrame(self.bounds).tap do |iv| 
     iv.contentMode = UIViewContentModeScaleAspectFill 
     iv.clipsToBounds = true 
     iv.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth 
     self.contentView.addSubview(iv) 
    end 

    self 
    end 

    def prepareForReuse 
    super 
    @image_view.image = nil 
    end 

    def image=(image) 
    @image_view.image = image 
    end 
end 

접근 # 1

간단한 일을 유지입니다 아래로 이것을 사용하여 끔찍한입니다. 그것은 불안정하고 느리다.

접근 # 2

은 ... NSCache를 통해 불안해하고 첫 번째 전체 스크롤 느린 다시

def viewDidLoad 
    ... 
    @images_cache = NSCache.alloc.init 
    ... 
end 

def collectionView(collection_view, cellForItemAtIndexPath: index_path) 
    cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path) 
    image_path = @image_paths[index_path.row] 

    if cached_image = @images_cache.objectForKey(image_path) 
    cell.image = cached_image 
    else 
    image = UIImage.imageWithContentsOfFile(image_path) 
    @images_cache.setObject(image, forKey: image_path) 
    cell.image = image 
    end 
end 

, 캐싱의 비트를 추가하지만, 다음에서 매끄러운 항해이다.

접근 # 3

로드 이미지 비동기 (및 캐싱을 유지)

def viewDidLoad 
    ... 
    @images_cache = NSCache.alloc.init 
    @image_loading_queue = NSOperationQueue.alloc.init 
    @image_loading_queue.maxConcurrentOperationCount = 3 
    ... 
end 

def collectionView(collection_view, cellForItemAtIndexPath: index_path) 
    cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path) 
    image_path = @image_paths[index_path.row] 

    if cached_image = @images_cache.objectForKey(image_path) 
    cell.image = cached_image 
    else 
    @operation = NSBlockOperation.blockOperationWithBlock lambda { 
     @image = UIImage.imageWithContentsOfFile(image_path) 
     Dispatch::Queue.main.async do 
     return unless collectionView.indexPathsForVisibleItems.containsObject(index_path) 
     @images_cache.setObject(@image, forKey: image_path) 
     cell = collectionView.cellForItemAtIndexPath(index_path) 
     cell.image = @image 
     end 
    } 
    @image_loading_queue.addOperation(@operation) 
    end 
end 

이 유망 보았다하지만 난 추적 할 수없는 버그가 있습니다. 초기 뷰로드에서는 모든 이미지가 동일한 이미지이고 전체 블록을 다른 이미지로로드 할 때 모든 이미지가로드됩니다. 디버깅을 시도했지만 이해할 수는 없습니다.

나는 또한 Dispatch::Queue.concurrent.async { ... }와 함께 NSOperation을 대체 해봤지만 같은 것으로 보인다.

제대로 작동하려면 올바른 방법이라고 생각합니다. 좌절에서

접근 # 4

나는이 처음에 조금 느리고 불안해 밝혀졌다

def viewDidLoad 
    ... 
    @preloaded_images = @image_paths.map do |path| 
    UIImage.imageWithContentsOfFile(path) 
    end 
    ... 
end 

def collectionView(collection_view, cellForItemAtIndexPath: index_path) 
    cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path) 
    cell.image = @preloaded_images[index_path.row] 
end 

... UIImages처럼 사전로드 이미지를 모두하기로 결정 풀 스크롤하지만 그 후 모두 좋다.

요약

사람이 올바른 방향으로 날 지점 수 있다면 그래서, 실제로 가장 감사하게 될 거라고. 사진 앱은 어떻게합니까? 이렇게 잘? 다른 사람에 대한


참고 : [허용 된] 대답은 잘못된 세포에 나타나는 이미지의 내 문제를 해결하지만, 난 여전히도 크기를 해결하기 위해 내 이미지 크기를 조정 한 후 고르지 스크롤을 얻고 있었다. 맨손으로 본질에 내 코드를 스트립 후 나는 UIImage # imageWithContentsOfFile이 범인임을 결국 발견했다. 이 메서드를 사용하여 UIImages 캐시를 미리 예열 했더라도 UIImage는 내부적으로 어떤 종류의 게으른 캐싱을 수행하는 것 같습니다. 여기에 설명 된 솔루션 - stackoverflow.com/a/10664707/71810을 사용하기 위해 캐시 온난화 코드를 업데이트 한 후 마침내 최고 60FPS의 부드러운 스크롤을 보았습니다.

답변

7

# 3 방법이 가장 좋은 방법이라고 생각합니다. 버그를 발견했을 수도 있습니다.

조작 블록에서 전체 콜렉션 뷰 클래스의 전용 변수 @image에 지정합니다.

@image = UIImage.imageWithContentsOfFile(image_path) 

image = UIImage.imageWithContentsOfFile(image_path) 

에 그리고 @image 로컬 변수를 사용하는 모든 참조를 변경 : 당신은 아마 변경해야합니다. 문제가 인스턴스 블록에 생성되고 인스턴스 변수에 할당 될 때마다 이미 존재하는 것을 대체한다는 것이 기꺼이겠다. 작업 블록이 대기열에서 해제되는 방식의 임의성 때문에 대기열 비동기 콜백은 @image이 할당 된 마지막 시간에 액세스하기 때문에 동일한 이미지를 다시 가져옵니다.

본질적으로 @image은 작업 및 비동기 콜백 블록에 대한 전역 변수와 같은 역할을합니다.

+0

예, 감사합니다. 나는 그것을 보지 못했다고 나는 믿을 수 없다.성능은 아직 스크롤에서 막혀 있지만, 테스트에서 내 이미지가 셀에 비해 너무 크기 때문이라고 생각합니다. – alistairholt

+4

다른 사람을위한 참고 사항 :이 답변은 이미지가 잘못된 셀에 나타나는 문제를 해결했지만 크기를 맞추기 위해 이미지의 크기를 조정 한 후에도 고르지 못한 스크롤이 계속 발생했습니다. 맨손으로 본질에 내 코드를 스트립 후 나는 UIImage # imageWithContentsOfFile이 범인임을 결국 발견했다. 이 메서드를 사용하여 UIImages 캐시를 미리 예열 했더라도 UIImage는 내부적으로 어떤 종류의 게으른 캐싱을 수행하는 것 같습니다. 여기에 설명 된 솔루션을 사용하기 위해 캐시 온난화 코드를 업데이트 한 후 - http://stackoverflow.com/a/10664707/71810 - 마침내 부드러운 ~ 60FPS 스크롤이 발생했습니다. – alistairholt

1

나는 이미지 내 자신의 캐시를 유지하여이었다이 문제를 해결 발견하는 가장 좋은 방법 등과 같은 백그라운드 스레드에서 viewWillAppear에 사전 온난화 :

- (void) warmThubnailCache 
{ 
    if (self.isWarmingThumbCache) { 
     return; 
    } 

    if ([NSThread isMainThread]) 
    { 
     // do it on a background thread 
     [NSThread detachNewThreadSelector:@selector(warmThubnailCache) toTarget:self withObject:nil]; 
    } 
    else 
    { 
     self.isWarmingThumbCache = YES; 
     NIImageMemoryCache *thumbCache = self.thumbnailImageCache; 
     for (GalleryImage *galleryImage in _galleryImages) { 
      NSString *cacheKey = galleryImage.thumbImageName; 
      UIImage *thumbnail = [thumbCache objectWithName:cacheKey]; 

      if (thumbnail == nil) { 
       // populate cache 
       thumbnail = [UIImage imageWithContentsOfFile:galleryImage.thumbImageName]; 

       [thumbCache storeObject:thumbnail withName:cacheKey]; 
      } 
     } 
     self.isWarmingThumbCache = NO; 
    } 
} 

또한 사용할 수 있습니다 GCD 대신 NSThread에 대한 설명은 없지만 NSThread는 한 번에 하나만 실행해야하므로 대기열이 필요하지 않습니다. 또한 본질적으로 무제한 이미지 수가있는 경우 I보다주의해야합니다. 여기에서와 같이 사용자의 스크롤 위치 주변에서 이미지를로드하는 것이 더 바람직 할 것입니다. 나를 위해 이미지 수가 고정되어 있기 때문에이 작업을 수행 할 수 있습니다. 당신은 메모리 경고를받을 때 캐시를 지 웁니다

- (PSUICollectionViewCell *)collectionView:(PSUICollectionView *)collectionView 
        cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"GalleryCell"; 

    GalleryCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier 
                       forIndexPath:indexPath]; 

    GalleryImage *galleryImage = [_galleryManager.galleryImages objectAtIndex:indexPath.row]; 

    // use cached images first 
    NIImageMemoryCache *thumbCache = self.galleryManager.thumbnailImageCache; 
    NSString *cacheKey = galleryImage.thumbImageName; 
    UIImage *thumbnail = [thumbCache objectWithName:cacheKey]; 

    if (thumbnail != nil) { 
     cell.imageView.image = thumbnail; 
    } else { 
     UIImage *image = [UIImage imageWithContentsOfFile:galleryImage.thumbImageName]; 

     cell.imageView.image = image; 

     // store image in cache 
     [thumbCache storeObject:image withName:cacheKey]; 
    } 

    return cell; 
} 

이 있는지 확인하거나 NSCache 뭔가를 사용 cellForItemAtIndexPath :과 같이, 캐시를 사용한다

나는 또한 collectionView가 있음을 추가해야합니다. Nimbus라는 프레임 워크를 사용하고 있습니다. Nimbus는 NIImageMemoryCache라는 항목을 가지고 있는데,이를 통해 최대 픽셀 수를 캐시 할 수 있습니다. 아주 편리합니다.

그 외, 주목해야 할 것은 UIImage의 "imageNamed"메서드를 사용하지 않는 것입니다.이 메서드는 자체 캐싱을 사용하므로 메모리 문제가 발생합니다. 대신에 "imageWithContentsOfFile"을 사용하십시오.

+0

팁 주셔서 감사합니다. – alistairholt