Używam UICollectionView
w aplikacji, która wyświetla dość dużo zdjęć (50-200) i mam problemy z jej pobieraniem (tak efektowne, jak aplikacja Zdjęcia na przykład).Jak uzyskać efektowny obraz UICollectionView z dużą ilością obrazów (50-200)?
Mam niestandardowy UICollectionViewCell
z UIImageView
jako jego wydział. Ładuję obrazy z systemu plików, z UIImage.imageWithContentsOfFile:
, do UIImageViews wewnątrz komórek.
Próbowałem już kilku podejść, ale były albo błędne albo miały problemy z wydajnością.
UWAGA: Używam RubyMotion, więc napiszę wszystkie fragmenty kodu w stylu Ruby.
Przede wszystkim, tu jest mój zwyczaj klasa UICollectionViewCell odsyłające ...
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
Podejście nr 1
Utrzymanie rzeczy proste ..
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
Przewijanie w górę/dół jest straszny przy użyciu tego. Jest szybki i powolny.
Podejście nr 2
dodać trochę buforowanie poprzez 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
Znowu jumpy i powolny w pierwszym pełnym zwoju, ale od tej pory to gładko.
Podejście nr 3
Załaduj obrazy asynchronicznie ... (i zachować buforowanie)
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
Ten wyglądał obiecująco, ale nie jest to problem, że nie mogę wyśledzić. Po załadowaniu początkowego widoku wszystkie obrazy są tym samym obrazem, a podczas przewijania całe bloki ładują się z innym obrazem, ale wszystkie mają ten obraz. Próbowałem debugowania go, ale nie mogę tego rozgryźć.
Próbowałem również podstawiania NSOperation z Dispatch::Queue.concurrent.async { ... }
, ale wydaje się, że to samo.
Myślę, że to prawdopodobnie prawidłowe podejście, gdybym mógł sprawić, by działał prawidłowo.
Podejście # 4
W frustracji postanowiłem właśnie pre-load wszystkie obrazy jako UIImages ...
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
ten okazał się być nieco powolna i nerwowy w pierwszym pełne przewijanie, ale wszystko dobrze po tym.
Podsumowanie
Tak więc, jeśli ktoś może wskazać mi w dobrym kierunku byłbym rzeczywiście najbardziej wdzięczny. Jak działa aplikacja Photos tak dobrze?
Uwaga dla nikogo innego: [Przyjęty] odpowiedź rozwiązać mój problem obrazów pojawiających się w niewłaściwych komórkach, ale nadal był coraz niepewny przewijanie nawet po zmianie rozmiaru moich obrazów w celu skorygowania wielkości. Po zepchnięciu mojego kodu do najpotrzebniejszych rzeczy w końcu stwierdziłem, że winowajcą jest UIImage # imageWithContentsOfFile. Mimo że wcześniej podgrzewałem pamięć podręczną obiektów UII za pomocą tej metody, UIImage wydaje się leniwym buforowaniem wewnętrznie. Po aktualizacji kodu ocieplania pamięci podręcznej, aby skorzystać z rozwiązania opisanego tutaj - stackoverflow.com/a/10664707/71810 - W końcu miałem super płynne przewijanie ~ 60FPS.
Tak, dziękuję! Nie mogę uwierzyć, że tego nie widziałem.W tej chwili wydajność wciąż się zacina, ale z moich testów wynika, że moje obrazy są zbyt duże dla komórek. – alistairholt
Uwaga dla kogokolwiek innego: ta odpowiedź rozwiązała problem z obrazami pojawiającymi się w niewłaściwych komórkach, ale wciąż otrzymywałem przerywane przewijanie nawet po zmianie rozmiaru obrazów do prawidłowych rozmiarów. Po zepchnięciu mojego kodu do najpotrzebniejszych rzeczy w końcu stwierdziłem, że winowajcą jest UIImage # imageWithContentsOfFile. Mimo że wcześniej podgrzewałem pamięć podręczną obiektów UII za pomocą tej metody, UIImage wydaje się leniwym buforowaniem wewnętrznie. Po aktualizacji kodu ocieplania pamięci podręcznej, aby skorzystać z rozwiązania opisanego tutaj - http://stackoverflow.com/a/10664707/71810 - W końcu miałem super płynne przewijanie ~ 60FPS. – alistairholt