2012-12-13 34 views
12

मैं एक ऐसे ऐप में UICollectionView का उपयोग कर रहा हूं जो बहुत सारी तस्वीरें (50-200) प्रदर्शित कर रहा है और मुझे इसे स्नैपी होने में समस्याएं आ रही हैं (उदाहरण के लिए फ़ोटो ऐप के रूप में स्नैपी)।बहुत सारी छवियों (50-200) के साथ एक snappy UICollectionView कैसे प्राप्त करें?

मेरे पास UIImageView के साथ एक कस्टम UICollectionViewCell है जो इसके सबव्यूव के रूप में है। मैं UIImage.imageWithContentsOfFile: के साथ, कोशिकाओं के अंदर UIImageViews में, फाइल सिस्टम से छवियों को लोड कर रहा हूं।

मैंने अभी कुछ दृष्टिकोणों की कोशिश की है लेकिन वे या तो छोटी हैं या प्रदर्शन के मुद्दे हैं।

नोट: मैं रूबीमोशन का उपयोग कर रहा हूं इसलिए मैं रूबी शैली में कोई भी कोड स्निपेट लिखूंगा।

सबसे पहले, यहाँ संदर्भ के लिए अपने कस्टम 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

चीजों को सरल रखते हुए है ..

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 

ऊपर स्क्रॉल/इसका उपयोग करके भयानक है। यह अजीब और धीमी है।

कार्य # 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 { ... } के साथ प्रतिस्थापित करने का भी प्रयास किया है, लेकिन ऐसा लगता है।

मुझे लगता है कि यह संभवतः सही दृष्टिकोण है यदि मैं इसे ठीक से काम करने के लिए प्राप्त कर सकता हूं।

कार्य # 4

हताशा में मैं 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 

यह थोड़ा धीमा और पहली में उछल साबित हुआ पूर्ण स्क्रॉल लेकिन उसके बाद सब अच्छा है।

सारांश

तो, अगर किसी को भी मुझे सही दिशा में बात कर सकते हैं मैं वास्तव में आभारी होगी। फ़ोटो ऐप यह कैसे करता है इतना अच्छा? किसी और के लिए


ध्यान दें: [स्वीकार किए जाते हैं] जवाब गलत कोशिकाओं में प्रदर्शित होने छवियों की मेरी समस्या का समाधान हो लेकिन मैं अभी भी आकार सही करने के लिए अपनी छवियों का आकार बदलने के बाद भी अस्थिर स्क्रॉल हो रही थी। मेरे कोड को नंगे जरूरी चीजों को अलग करने के बाद मुझे अंततः पता चला कि 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

हाँ, धन्यवाद! मुझे विश्वास नहीं है कि मैंने उसे नहीं देखा।प्रदर्शन अभी भी इस समय स्क्रॉल पर stuttering है लेकिन मेरे परीक्षण से मुझे विश्वास है कि सिर्फ इसलिए है कि मेरी छवियों कोशिकाओं के लिए बहुत बड़ी हैं। – 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 बजाय एनएसटीएचएड का, लेकिन मैंने एनएसटीएचएड का चयन किया, क्योंकि इनमें से केवल एक ही समय में चलना चाहिए, इसलिए कोई कतार आवश्यक नहीं है। साथ ही, यदि आपके पास अनिवार्य रूप से असीमित छवियां हैं, तो आपको I से अधिक सावधान रहना होगा। आपको उन सभी की बजाय, उपयोगकर्ता के स्क्रॉल स्थान के आस-पास की छवियों को लोड करने के बारे में और अधिक चालाक होना होगा, जैसा कि मैं यहां करता हूं। मैं ऐसा इसलिए कर सकता हूं क्योंकि मेरे लिए छवियों की संख्या तय की गई है।

मैं भी जोड़ना चाहिए कि आपके collectionView: cellForItemAtIndexPath: तो की तरह, अपने कैश का उपयोग करना चाहिए:

- (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 की तरह कुछ का उपयोग करें। मैं निंबस नामक एक ढांचे का उपयोग कर रहा हूं, जिसमें एनआईएममेज मेमरी कैश नामक कुछ है, जो मुझे कैश में अधिकतम पिक्सेल सेट करने देता है। काफी आसान

इसके अलावा, एक गॉचा नोट लेने के लिए है कि UIImage की "imageNamed" विधि का उपयोग न करें, जो कि यह स्वयं का कैशिंग है, और आपको स्मृति समस्याएं देगा। इसके बजाए "imageWithContentsOfFile" का प्रयोग करें।

+0

सुझावों के लिए धन्यवाद। – alistairholt