UITableView सेल के अंदर url से लोड हो रही Async छवि - स्क्रॉल करते समय छवि गलत छवि में बदल जाती है


158

मैंने अपने यूआईटेबल व्यू सेल के अंदर चित्रों को async लोड करने के दो तरीके लिखे हैं। दोनों मामलों में छवि ठीक लोड होगी, लेकिन जब मैं तालिका को स्क्रॉल करूंगा तो छवियां कुछ समय में बदल जाएंगी जब तक कि स्क्रॉल समाप्त नहीं होगा और छवि सही छवि पर वापस जाएगी। ऐसा क्यों हो रहा है मुझे नहीं पता।

#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;
}

5
आप वास्तविक कक्षों में जानकारी संग्रहीत करने का प्रयास कर रहे हैं। यह बुरा है, बहुत बुरा है। आपको n सरणी (या कुछ समान) में जानकारी संग्रहीत करनी चाहिए और फिर इसे कक्षों में प्रदर्शित करना चाहिए। इस मामले में जानकारी वास्तविक UIImage है। हाँ इसे एसिंक्रोनस रूप से लोड करें लेकिन इसे एक सरणी में लोड करें।
फोगमिस्टर

1
@ फोगमिस्टर क्या आप जिक्र कर रहे हैं poster? यह संभवतः उनके कस्टम सेल में एक इमेजव्यू है, इसलिए EXEC_BAD_ACCESS जो कर रहा है वह पूरी तरह से सही है। आप सही हैं कि आपको मॉडल डेटा के लिए रिपॉजिटरी के रूप में सेल का उपयोग नहीं करना चाहिए, लेकिन मुझे नहीं लगता कि वह क्या कर रहा है। वह सिर्फ कस्टम सेल दे रहा है जिसे उसे खुद पेश करने की जरूरत है। इसके अलावा, और यह एक और अधिक सूक्ष्म मुद्दा है, मैं एक छवि संग्रहीत करने के बारे में सावधान रहूंगा, अपने मॉडल सरणी में अपने टेबलव्यू का समर्थन करते हुए। एक छवि कैशिंग तंत्र का उपयोग करना बेहतर है और आपके मॉडल ऑब्जेक्ट को उस कैश से पुनर्प्राप्त करना चाहिए।
रोब

1
हां, बिल्कुल मेरी बात। अनुरोध को देखते हुए (जो पूर्ण रूप में दिखाया गया है) वह छवि को अतुल्यकालिक रूप से डाउनलोड कर रहा है और इसे सीधे छवि दृश्य में सेल में डाल रहा है। (इस प्रकार डेटा को स्टोर करने के लिए सेल का उपयोग करना, अर्थात छवि)। वह जो कर रहा है वह एक ऑब्जेक्ट को संदर्भित कर रहा है और उस ऑब्जेक्ट से छवि का अनुरोध कर रहा है (एक सरणी या कहीं पर निहित)। यदि ऑब्जेक्ट में अभी तक छवि नहीं है, तो उसे एक प्लेसहोल्डर वापस करना चाहिए और छवि को डाउनलोड करना चाहिए। फिर जब छवि डाउनलोड हो जाती है और प्रदर्शित करने के लिए तैयार है तो तालिका को बताएं ताकि वह सेल को अपडेट कर सके (यदि यह दिखाई दे रहा है)।
फोगमिस्टर

1
वह जो भी कर रहा है वह डाउनलोड को हर बार तालिका में उस सेल तक स्क्रॉल करने पर मजबूर करेगा। चाहे छवियां लगातार संग्रहीत की जाती हैं या नहीं, लेकिन कम से कम उन्हें तालिका के जीवन काल के लिए संग्रहीत करें।
फोगमिस्टर

1
बिल्कुल सही: D इस तरह से आपको केवल एक बार URL से छवि प्राप्त करनी होगी। इसे आप फेसबुक फ्रेंड पिकर जैसी चीजों पर देखेंगे। जब आप इसे शुरू करते हैं तो सभी अवतार ग्रे प्लेसहोल्डर होते हैं। फिर जैसे-जैसे आप स्क्रॉल करते हैं, वैसे-वैसे वे आगे बढ़ते हैं। लेकिन फिर जब आप पहले दिखाए गए सेल पर वापस स्क्रॉल करते हैं तो यह तुरंत पहले से डाउनलोड की गई छवि दिखाएगा।
फोगमिस्टर

जवाबों:


230

यह मानते हुए कि आप एक त्वरित सामरिक सुधार की तलाश कर रहे हैं, आपको यह करने की ज़रूरत है कि यह सुनिश्चित करें कि सेल की छवि आरंभीकृत है और यह भी कि सेल की पंक्ति अभी भी दिखाई दे रही है, जैसे:

- (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. एक अधिक सूक्ष्म मुद्दा यह है कि वास्तव में धीमी गति से नेटवर्क पर, सेल बंद करने से पहले आपका अतुल्यकालिक अनुरोध समाप्त नहीं हो सकता है। आप UITableViewविधि का उपयोग कर सकते हैं cellForRowAtIndexPath:(समान रूप से नामित UITableViewDataSourceविधि से भ्रमित नहीं होने के लिए tableView:cellForRowAtIndexPath:) यह देखने के लिए कि क्या उस पंक्ति के लिए सेल अभी भी दिखाई दे रहा है। nilयदि कक्ष दिखाई नहीं देता है तो यह विधि वापस आ जाएगी ।

    मुद्दा यह है कि सेल को उस समय तक स्क्रॉल कर दिया जाता है जब तक आपकी एसिंक्रोस विधि पूरी हो चुकी होती है, और इससे भी बदतर, सेल को तालिका की दूसरी पंक्ति के लिए पुन: उपयोग किया जाता है। यह देखने के लिए जाँच करके कि क्या पंक्ति अभी भी दिखाई दे रही है, आप यह सुनिश्चित करेंगे कि आप गलती से छवि को उस पंक्ति के लिए अपडेट न करें जो स्क्रीन से दूर स्क्रॉल की गई है।

  3. हाथ में सवाल करने के लिए कुछ हद तक असंबंधित, मैं अभी भी आधुनिक सम्मेलनों और एपीआई का लाभ उठाने के लिए इसे अद्यतन करने के लिए मजबूर महसूस किया, विशेष रूप से:

    • पृष्ठभूमि कतार में NSURLSessionभेजने के बजाय उपयोग करें -[NSData contentsOfURL:];

    • dequeueReusableCellWithIdentifier:forIndexPath:बजाय उपयोग करें dequeueReusableCellWithIdentifier:(लेकिन उस पहचानकर्ता के लिए सेल प्रोटोटाइप या रजिस्टर क्लास या एनआईबी का उपयोग करना सुनिश्चित करें); तथा

    • मैंने एक वर्ग नाम का उपयोग किया जो कोको नामकरण परंपराओं के अनुरूप है (अर्थात अपरकेस अक्षर से शुरू)।

इन सुधारों के साथ भी, समस्याएँ हैं:

  1. उपरोक्त कोड डाउनलोड की गई छवियों को कैशिंग नहीं कर रहा है। इसका मतलब है कि अगर आप किसी इमेज को स्क्रीन पर और स्क्रीन पर वापस स्क्रॉल करते हैं, तो ऐप इमेज को फिर से प्राप्त करने की कोशिश कर सकता है। शायद आप काफी भाग्यशाली होंगे कि आपका सर्वर रिस्पांस हेडर द्वारा दी जाने वाली काफी पारदर्शी कैशिंग की अनुमति देगा NSURLSessionऔर NSURLCacheयदि नहीं, तो आप अनावश्यक सर्वर अनुरोध कर रहे हैं और बहुत धीमी UX की पेशकश कर रहे हैं।

  2. हम उन कोशिकाओं के अनुरोधों को रद्द नहीं कर रहे हैं जो स्क्रीन को स्क्रॉल करते हैं। इस प्रकार, यदि आप तेजी से 100 वीं पंक्ति पर स्क्रॉल करते हैं, तो उस पंक्ति के लिए छवि पिछली 99 पंक्तियों के अनुरोध के पीछे बैकलॉग हो सकती है जो अब भी दिखाई नहीं दे रही हैं। आप हमेशा यह सुनिश्चित करना चाहते हैं कि आप सबसे अच्छे यूएक्स के लिए दृश्यमान कोशिकाओं के लिए अनुरोधों को प्राथमिकता दें।

इन मुद्दों को संबोधित करने वाला सबसे सरल समाधान एक UIImageViewश्रेणी का उपयोग करना है , जैसे कि SDWebImage या AFNetworking के साथ प्रदान किया जाता है । यदि आप चाहते हैं, तो आप उपरोक्त मुद्दों से निपटने के लिए अपना कोड लिख सकते हैं, लेकिन यह बहुत काम का है, और उपरोक्त UIImageViewश्रेणियां आपके लिए पहले ही ऐसा कर चुकी हैं।


1
धन्यवाद। मेरा मानना ​​है कि आपको अपना उत्तर संपादित करने की आवश्यकता है। अपडेट updateCell.poster.image = nilकरने cell.poster.image = nil;से पहले इसे घोषित करने से पहले कॉल किया जाता है।
सेगेव

1
मेरा ऐप बहुत सारे जोंस का उपयोग करता है इसलिए AFNetworkingनिश्चित रूप से जाने का रास्ता है। मैं इसके बारे में जानता था लेकिन इसका इस्तेमाल करने के लिए आलसी था। मैं सिर्फ यह स्वीकार कर रहा हूं कि कैशिंग कोड की सरल रेखा के साथ कैसे काम करता है। [imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
सेगेव

2
उपरोक्त सभी और SDWebImage की कोशिश की (वास्तव में यहाँ रोका गया और यहां तक ​​कि AFNetworking की कोशिश करने की आवश्यकता नहीं थी) और यह अब तक का सबसे अच्छा विकल्प था। धन्यवाद @ रोब।
महाधमनी

1
जब आप छवि को लोड करना समाप्त करते हैं और छवि दृश्य को अपडेट करते हैं, तो गतिविधि संकेतक को हटा दें। एकमात्र चाल यह है कि आपको यह अनुमान लगाना होगा कि क्या होता है यदि सेल दृश्य से बाहर स्क्रॉल करता है जबकि छवि पुनर्प्राप्त की जा रही है और सेल को दूसरी पंक्ति के लिए पुन: उपयोग किया जाता है, आपको किसी भी मौजूदा गतिविधि संकेतक की उपस्थिति का पता लगाना होगा और हटा / अपडेट करना होगा यह नहीं, मान लें कि सेल एक मौजूदा संकेतक से मुक्त है।
रोब

1
मूल प्रश्न यह था कि " cellForRowAtIndexPathजब मैं जल्दी से स्क्रॉल करता हूं तो यह छवियों की चंचलता में क्यों होता है " और मैंने समझाया कि ऐसा क्यों हुआ और इसे कैसे ठीक किया जाए। लेकिन मैं यह बताने के लिए आगे गया कि क्यों अपर्याप्त था, कुछ गहरी समस्याओं का वर्णन किया, और तर्क दिया कि आप इसे बेहतर तरीके से संभालने के लिए उन पुस्तकालयों में से एक का उपयोग करने से बेहतर क्यों होंगे (दिखाई देने वाली कोशिकाओं के लिए अनुरोधों को प्राथमिकता दें, अनुप्रेषित नेटवर्क से बचने के लिए कैशिंग अनुरोध, आदि)। मैं स्पष्ट नहीं हूं कि "मैं अपनी तालिका दृश्य में झिलमिलाती छवियों को कैसे रोकूं" के सवाल के जवाब में आपको और क्या उम्मीद थी।
रोब

15

/ * मैंने इसे इस तरह से किया है, और इसका परीक्षण भी किया है * /

चरण 1 = कस्टम सेल क्लास रजिस्टर करें (तालिका में प्रोटोटाइप सेल के मामले में) या निब (कस्टम सेल के लिए कस्टम नीब के मामले में) इस तरह से देखने के लिए तालिका में देखें विधि:

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

या

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

चरण 2 = UITableView का उपयोग करें "dequeueReusableCellWithIdentifier: forIndexPath:" इस तरह की विधि (इसके लिए, आपको कक्षा या नीब पंजीकृत करना होगा):

   - (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;
        }

1
अंतिम बार के भीतर सेलफ़ोररॉव इंडियाटैक्सपैथ को कॉल नहीं करने से पूरी चीज़ दूसरी बार निकाल दी जाती है?
मार्क ब्रिजेस

@MarkBridges, नहीं। वास्तव में मैं यहां tableView की cellForRowAtIndexPath विधि कह रहा हूं। उसी नाम के साथ tableView के डेटा स्रोत विधि के साथ भ्रमित न हों। यह आवश्यक है, इसे [self tableView: tableView cellForRowAtIndexPath: indexPath] कहा जा सकता है; आशा है कि इससे आपका भ्रम दूर हो जाएगा।
नितेश बोरड़

14

इस समस्या को हल करने वाले कई ढांचे हैं। कुछ लोगों का नाम बताने के लिए:

स्विफ्ट:

उद्देश्य सी:


कृपया अपने सुझाव जोड़ें अगर विचार के लायक कोई अन्य ढांचा हो।
कीन

3
वास्तव में SDWebImageइस समस्या का समाधान नहीं है। छवि डाउनलोड होने पर आप इसे नियंत्रित करने में सक्षम हैं, लेकिन ऐसा करने की अनुमति के बारे में पूछे बिना SDWebImageछवि को असाइन UIImageViewकरें। मूल रूप से, प्रश्न से समस्या अभी भी इस पुस्तकालय के साथ हल नहीं हुई है।
बार्टालोमिएज सेमेकोस्की

सवाल से समस्या यह थी कि लेखक यह जांच नहीं कर रहा था कि सेल का पुन: उपयोग किया गया था या नहीं। यह एक बहुत ही मूल समस्या है जो उन चौखटों द्वारा संबोधित की जाती है, जिसमें SDWebImage शामिल है।
कीन

SDWebImage iOS 8 के बाद से बहुत पिछड़ रहा है, यह मेरे fav चौखटे में से एक था, लेकिन अब मैं PinRemoteImage का उपयोग करना शुरू कर रहा हूं, जो वास्तव में अच्छा काम करता है।
जोन कार्डोना

@ BartłomiejSemańczyk आप सही हैं, यह मुद्दा SDWebimage द्वारा हल नहीं किया गया
Jan

9

स्विफ्ट 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
}

3
मैं आपको धन्यवाद कहूंगा। लेकिन कोड में थोड़ी समस्या है। अगर डेटा = कोशिश करते हैं? डेटा (contentOf: url) {// कृपया url को स्थान पर बदलें। यह बहुत से लोगों की मदद करेगा।
कार्ल हुंग

2
इस कोड के साथ, आप नेटवर्क पर फ़ाइल को दो बार डाउनलोड करते हैं: एक बार डाउनलोडटैक्स में, एक बार डेटा के साथ (cntentsOf :)। आपको url के स्थान पर उपयोगकर्ता का स्थान होना चाहिए क्योंकि डाउनलोड कार्य वास्तव में नेटवर्क पर डाउनलोड होता है और डेटा को एक अस्थायी फ़ाइल में लिखता है और आपको localUrl (आपके मामले में स्थान) से गुजरता है। इसलिए डेटा को स्थानीय यूआरएल को इंगित करने की आवश्यकता है ताकि वह केवल फ़ाइल से पढ़े।
स्टीफन डे लुका

उपयोग उदाहरण में, क्या यह "ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) ......." माना जाता है?
टिम क्रूगर

बहुत तेजी से स्क्रॉल करने पर काम नहीं करेगा, सेल के पुन: उपयोग के कारण छवियां कई बार स्वैप होंगी।
जुआन बोईरो

5

यहाँ स्विफ्ट संस्करण है (@ नितेश बोरड़ उद्देश्य सी कोड का उपयोग करके): -

   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()
            }

3

सबसे अच्छा उत्तर यह करने का सही तरीका नहीं है :(। आप वास्तव में मॉडल के साथ indexPath को बाध्य करते हैं, जो हमेशा अच्छा नहीं होता है। कल्पना करें कि लोडिंग छवि के दौरान कुछ पंक्तियाँ जोड़ी गई हैं। अब दिए गए indexPath के लिए सेल स्क्रीन पर मौजूद है, लेकिन छवि यह अब सही नहीं है! स्थिति थोड़े संभावनाहीन है और इसे दोहराने में मुश्किल है लेकिन यह संभव है।

MVVM दृष्टिकोण का उपयोग करना बेहतर है, व्यूमालोड के साथ सेल को कंट्रोलर और लोड इमेज में व्यूमॉडल (स्विचटेक्लोस्ट विधि के साथ रिएक्टिवकोआ सिग्नल असाइन करना) के साथ बांधें, फिर इस सिग्नल को सब्सक्राइब करें और सेल को इमेज असाइन करें! ;)

आपको MVVM का दुरुपयोग न करने के लिए याद रखना होगा। दृश्य सरल मृत होना है! जबकि ViewModels पुन: प्रयोज्य होना चाहिए! इसलिए नियंत्रक में View (UITableViewCell) और ViewModel को बांधना बहुत महत्वपूर्ण है।


1
हां, मेरा अनुक्रमणिका पथ "सामरिक सुधार" (जिसकी मैं अनुशंसा नहीं कर रहा था, बल्कि ओपी की समस्या को हल करने के लिए सिर्फ सबसे विनम्र संपादन था) इस मुद्दे से ग्रस्त है (लेकिन केवल तब जब टेबलव्यू में पंक्तियों को जोड़ा / हटा दिया गया हो)। और यदि यह घटना स्वयं प्रकट होती है, तो मैं उस अन्य तरीकों को ठीक कर सकता हूं (बजाय एक ही सूचकांक पथ का उपयोग करने के, उचित पंक्ति के लिए केवल क्वेरी मॉडल)। लेकिन उस सामरिक सुधार में आपके द्वारा उठाए गए की तुलना में और भी अधिक गंभीर समस्याएं हैं (जो मैं ऊपर वर्णित करता हूं)। यदि आप UIImageViewमेरे द्वारा सुझाए गए श्रेणी समाधान का उपयोग करते हैं, तो सूचकांक पथ के बारे में ऐसा कोई मुद्दा नहीं है।
रॉब

2
मुझे यह थोड़ा अजीब लग सकता है, लेकिन VIEW की ओर से किसी भी तरह के तर्क को लागू करना इस वास्तुकला का दुरुपयोग है।
9

3

मेरे मामले में, यह इमेज कैशिंग (प्रयुक्त SDWebImage) के कारण नहीं था। ऐसा इसलिए था क्योंकि कस्टम सेल के टैग बेमेल के साथ indexPath.row था।

CellForRowAtIndexPath पर:

1) अपने कस्टम सेल में एक इंडेक्स वैल्यू असाइन करें। उदाहरण के लिए,

cell.tag = indexPath.row

2) मुख्य धागे पर, छवि को निर्दिष्ट करने से पहले, जांचें कि क्या छवि टैग के साथ मेल करके संबंधित सेल से संबंधित है।

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

2

धन्यवाद "रोब" .... मुझे 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"];
    }

मेरे लिए, mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];कभी शून्य नहीं है, इसलिए इसका कोई प्रभाव नहीं है।
कार्बोकेशन

1
आप देख सकते हैं कि आपका सेल दिखाई दे रहा है या नहीं: for (mycell * updateCell in collectionView.v अदृश्य कार्ड) {cellV अदृश्य = YES; } अगर (सेलविजिबल) {cell.coverImageView.imageURL = [NSURL URLWithString: [डिक्ट वैल्यूफ़ॉरके: @ "ImageURL"]]; } यह मेरे लिए भी काम करता है
स्नेहा

@ स्नेहा हां, आप यह देख सकते हैं कि यह उस visibleCellsतरह से पुनरावृत्ति करके दिखाई दे रहा है, लेकिन मुझे संदेह है कि इसका उपयोग [collectionView cellForItemAtIndexPath:indexPath]अधिक कुशल है (और यही कारण है कि आप पहली बार में उस कॉल को करते हैं)।
रोब

@ स्नेहा इसके अलावा, इस उत्तर में अपने कोड नमूने में, ऊपर, आप यह देखने के लिए जांचें कि updateCellक्या नहीं है nil, लेकिन आप तब इसका उपयोग नहीं करते हैं। आपको इसका उपयोग न केवल यह निर्धारित करने के लिए करना चाहिए कि संग्रह दृश्य सेल अभी भी दिखाई दे रहा है, बल्कि आपको updateCellइस ब्लॉक के अंदर का उपयोग करना चाहिए , न कि cell(जो किसी भी अधिक मान्य नहीं हो सकता है)। और जाहिर है, अगर यह है nil, तो आपको कुछ भी करने की ज़रूरत नहीं है (क्योंकि यह सेल दिखाई नहीं देता है)।
रोब

2
 - (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;
    }

0

मुझे लगता है कि आप पृष्ठभूमि में सेल के लिए छवि लोडिंग के समय अपने सेल लोडिंग को गति देना चाहते हैं। उसके लिए हमने निम्नलिखित कदम उठाए हैं:

  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];
             }];
}

ManabImageOperations.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

अगर कोई समस्या होती है तो कृपया उत्तर की जाँच करें और टिप्पणी करें…।


0

बस बदलो,

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];
         });
     });

0

आप बस अपना 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];

क्या मुझे इसका कारण पता चल सकता है?
User558

-1
-(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;}

2
बिना किसी स्पष्टीकरण के कोड डंप शायद ही कभी सहायक होते हैं। कृपया कुछ संदर्भ प्रदान करने के लिए इस उत्तर को संपादित करने पर विचार करें।
क्रिस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.