2013-05-25 21 views
7

Właśnie utworzyłem UICollectionView, w którym użytkownik może dodawać zdjęcia ze swojego telefonu do funkcji albumu ze zdjęciami w aplikacji. Mam obrazy zapisać w podkatalogu w katalogu dokumentów, aby można było dodawać i usuwać więcej. Jednak gdy przewijam widok kolekcji w górę iw dół, jest on bardzo opóźniony.Przewijanie UICollectionView jest powolne

Jak zrobić przewijanie ładne i gładkie?

mój kod: Pierwsze 16 obrazy są ustawione obrazy wszystko po to są z podkatalogu w dokumentach katalogu

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Custom" forIndexPath:indexPath]; 
    //Current index number 
    int index=indexPath.section * noOfSection + indexPath.row; 
    //Check if its the preset photos 
    if(index<16){ 
     NSString *name=[recipePhotos objectAtIndex:indexPath.section * noOfSection + indexPath.row]; 
     cell.imageView.image=[UIImage imageNamed:name]; 
    } 

//not preset photos, so retrieve the photos the user added 
    else { 
     NSData *data= [NSData dataWithContentsOfFile:[recipePhotos objectAtIndex:index]]; 
     UIImage *theImage=[UIImage imageWithData:data]; 

     cell.imageView.image=theImage; 
     data=nil; 
    } 

    return cell; 
} 

Czas Profiler dał mi ten

Running Time Self  Symbol Name 
568.0ms 63.1% 0.0  Main Thread 0x4048 
320.0ms 35.5% 0.0  _pthread_start 0x405e 
320.0ms 35.5% 0.0  thread_start 
320.0ms 35.5% 0.0  _pthread_start 
320.0ms 35.5% 0.0  0x1084be960 
310.0ms 34.4% 1.0   0x1084be6f0 
7.0ms 0.7% 0.0   mach_msg 
2.0ms 0.2% 2.0   objc_msgSend 
1.0ms 0.1% 1.0   -[NSAutoreleasePool release] 
4.0ms 0.4% 0.0  _dispatch_mgr_thread 0x4052 
4.0ms 0.4% 0.0  _dispatch_mgr_thread 
4.0ms 0.4% 0.0  _dispatch_mgr_invoke 
4.0ms 0.4% 4.0  kevent 
3.0ms 0.3% 0.0  _dispatch_worker_thread2 0x62b24 
3.0ms 0.3% 1.0  start_wqthread 
3.0ms 0.3% 0.0  _dispatch_worker_thread2 0x62a84 
3.0ms 0.3% 0.0  start_wqthread 
3.0ms 0.3% 0.0  _pthread_wqthread 
3.0ms 0.3% 0.0  _dispatch_worker_thread2 
3.0ms 0.3% 0.0   _dispatch_queue_invoke 
3.0ms 0.3% 0.0   _dispatch_queue_drain 
3.0ms 0.3% 0.0   _dispatch_client_callout 
2.0ms 0.2% 0.0   my_io_execute_passive_block 
1.0ms 0.1% 0.0    __86-[NSPersistentUIManager writePublicPlistWithOpenWindowIDs:optionallyWaitingUntilDone:]_block_invoke_0835 
1.0ms 0.1% 0.0    -[NSPersistentUIManager writePublicPlistData:] 
1.0ms 0.1% 0.0    -[NSURL(NSURLPathUtilities) URLByAppendingPathComponent:] 
1.0ms 0.1% 0.0    -[NSURL getResourceValue:forKey:error:] 
1.0ms 0.1% 0.0     CFURLCopyResourcePropertyForKey 
1.0ms 0.1% 0.0    __block_global_2 
1.0ms 0.1% 0.0    -[NSPersistentUIManager writeRecords:withWindowInfos:flushingStaleData:] 
1.0ms 0.1% 0.0   _dispatch_call_block_and_release 
1.0ms 0.1% 0.0    0x1084b8580 
1.0ms 0.1% 0.0    mach_msg_send 
1.0ms 0.1% 0.0    mach_msg 
1.0ms 0.1% 1.0    mach_msg_trap 
1.0ms 0.1% 0.0  _pthread_struct_init 0x62a83 
1.0ms 0.1% 0.0  start_wqthread 
1.0ms 0.1% 0.0  _pthread_wqthread 
1.0ms 0.1% 1.0  _pthread_struct_init 
1.0ms 0.1% 0.0  start_wqthread 0x62a7f 
+1

ilu obrazy są w widoku? Z mojego doświadczenia wynika, że ​​zbyt wiele obrazów może spowalniać szybkość klatek. – LAMBORGHINI

+1

Dzieje się tak tylko z komórkami ładującymi obrazy z katalogu. Sprawdź mój kod powyżej .. Właśnie zaktualizowałem go @CodeBandits –

+0

Czy robisz dowolny niestandardowy rysunek w 'CollectionCell'? – rog

Odpowiedz

2

Po pewnym zamieszaniu odkryłem, że problem opiera się na kilku czynnikach.

Jeden- Obrazy do miniatur były zbyt duże, więc zrobiłem oddzielną tablicę obrazów o mniejszych rozmiarach obrazu, które pasowałyby do komórki. Dwa - Dzięki pomocy @ggrana otwarcie oddzielnej nitki przyspieszyło proces i sprawiło, że stało się ono mniej lagularne. Trzy- Stwierdziłem również, że posiadanie szeregu obrazów zamiast lokalizacji obrazów jest szybsze - tylko problem polega na tym, że zajmuje więcej pamięci.

+0

Jeśli nie masz wielu obrazów, mam na myśli przeciętny użytkownik przewija cały widok kolekcji, na końcu użyje tej samej pamięci. więc jeśli tablica zdjęć jest szybsza, idź tą drogą. –

+0

potrzebujesz tego http://stackoverflow.com/a/26034382/294884 i tego http://stackoverflow.com/a/25604378/294884 – Fattie

3

Trzeba będzie zrobić podejście podobne do tego, które musisz wykonać w widokach stołów, będziesz musiał ponownie wykorzystać widoki, np. ponownie wykorzystasz swoje komórki w widoku tabeli.

Naprawdę dobry poradnik jest to jeden z Ray Wenderlich:

W pierwszej części masz podstawowe, w drugim Mówią o wielorazowych poglądów, ty, spójrz na link:

http://www.raywenderlich.com/22417/beginning-uicollectionview-in-ios-6-part-22

Edycja

przykład w celu załadowania obrazów asynchronicznie:

Tworzenie w swojej celi metoda loadImageFromFile na przykład, że odbiera ścieżkę będzie to CAL w ten sposób:

[cell loadImageFromFile:[recipePhotos objectAtIndex:index]]; 

I wtedy będzie wyglądać (być może trzeba dostosować coś ...):

- (void) loadImageFromFile:(NSString*)path{ 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{ 
     NSData *data= [NSData dataWithContentsOfFile:path]; 
     UIImage *theImage=[UIImage imageWithData:data]; 

     dispatch_async(dispatch_get_main_queue(), ^{ 
      cell.imageView.image=theImage; 
    }); 
}); 
+0

Używam komórek wielokrotnego użytku ... Oto mój kod powyżej @ggrana –

+0

Będziesz musiał załadować obraz w tle, a po załadowaniu będziesz musiał zaktualizować obraz w komórce, poza tym twój scroll będzie czekał na powrót obrazu, aby kontynuować powolny. – ggrana

+0

Okay, jakie jest najlepsze podejście do dbania o to w tle. Czy muszę oddzielić się od głównego wątku? jeśli tak, to jak @ggrana –

3

@ggrana ma dobry pomysł. Ładowanie async zdecydowanie pomoże. Jednak nadal wykonujesz nadmiarową pracę, jeśli ładujesz z pliku za każdym razem. Jedną z rzeczy do rozważenia byłoby zwiększenie asynchronicznego ładowania za pomocą NSCache. Jest to w zasadzie NSDictionary, ale samo zarządzanie pamięcią i zrzuca dane, gdy istnieje presja pamięci.

Więc jeśli masz budżet na pamięć, możesz rzeczywiście tworzyć miniatury w locie (aby nie musieć twardo kodować rozmiaru) i przechowywać je w pamięci podręcznej. W ten sposób twoje zdjęcia pojawią się tylko za pierwszym razem. Za każdym razem ładują się natychmiast.

Można go używać tak:

@implementation ... 
{ 
    NSCache * _johnny; // get it? 
} 

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    [cell setImage:nil]; // since they are reused, prevents showing an old image 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     UIImage * sticker = [self thumbnailOfSize:CGSizeMake(desiredImageWidth, desiredImageHeight) 
              forIndex:[indexPath row]]; 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      [cell setImage:sticker]; 
     }); 
    }); 
} 

// load image from disk and cache thumbnail version 
- (UIImage*) thumbnailOfSize:(CGSize)size forIndex:(NSInteger)index 
{ 
    NSString * cacheKey = [NSString stringWithFormat:@"%@ %d", NSStringFromCGSize(size), index]; 
    UIImage * image = [_johnny objectForKey:cacheKey]; 

    if (!image) { 
     image = [UIImage imageWithContentsOfFile:_imagePaths[index]]; 

     float desiredWidth = size.width; 
     float desiredHeight = size.height; 
     float actualHeight = image.size.height; 
     float actualWidth = image.size.width; 
     float imgRatio = actualWidth/actualHeight; 
     float maxRatio = desiredWidth/desiredHeight; 

     if(imgRatio != maxRatio) { 
      if(imgRatio < maxRatio) { 
       imgRatio = desiredHeight/actualHeight; 
       actualWidth = imgRatio * actualWidth; 
       actualHeight = desiredHeight; 
      } else { 
       imgRatio = desiredWidth/actualWidth; 
       actualHeight = imgRatio * actualHeight; 
       actualWidth = desiredWidth; 
      } 
     } 

     CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight); 
     UIGraphicsBeginImageContextWithOptions(rect.size, FALSE, 0); // do right thing for retina 
     [image drawInRect:rect]; 
     image = UIGraphicsGetImageFromCurrentImageContext(); 
     UIGraphicsEndImageContext(); 

     // here's johnny 
     [_johnny setObject:image forKey:cacheKey]; 
    } 

    return image; 
} 
+0

To podejście działa dobrze. Kilka punktów: 1) brakuje linii tworzenia/usuwania kresek w komórce 'cellForItem ...' 2), jeśli użytkownik przewinie się bardzo szybko na dole widoku z długim zbiorem, komórki na dole mogą wyglądać na cykliczne seria obrazów jako grupa asynchronicznych wywołań dla tej ponownie użytej komórki w końcu aktualizuje obraz. Sprawdzanie w ostatniej chwili 'indexPathsForVisibleCells 'rozwiązuje to (w zasadzie" czy ta ścieżka indeksu jest nadal widoczna? Jeśli nie, nie aktualizuj obrazu). –

Powiązane problemy