Question

Final solution to this issue is given below the question

My goal is to create an image picker that initially creates each image view inside of a scroll view, and then passes a reference for that image view to a method that will asynchronously update the imageview.image with the appropriate image.

This works great as is.

The issue I run into is that the scroll view only shows the image views in a batch at the end of the method, whereas I'd like for them to print one by one as they are created (image available or not).

If I touch the scroll view to scroll, it works the way I want it to, but if I make no touch at all, the screen stays white until all of the images, not just image views, have finished loading.

I've tried placing setNeedsDisplay everywhere, for every view, including self.view, scrollview, and each individual imageview. What's going on?

- (void)viewDidLoad {
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
     dispatch_async(queue, ^{
          [self setGridView];
     });
}

- (void)setGridView {
    // begin loading images
    NSString * retina = ([[MVProject sharedInstance] settings_retina]) ? @"2" : @"";

    CGRect scrollframe = CGRectMake(0, 0, 300, 275);
    UIScrollView * newscrollview;
    newscrollview = [[UIScrollView alloc] initWithFrame:scrollframe];
    newscrollview.scrollEnabled = YES;
    newscrollview.delegate = self;
    [newscrollview setContentSize:CGSizeMake(300, (((ceilf([[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:@"Flickr_Dictionary"]] count]/4)) * (58+15)) + 15 + 58 + 15))];
    [picsMVContentCell.contentView addSubview:newscrollview];

    float currentx = 11;
    float currenty = 17;
    NSInteger index = 0;

    for (NSDictionary * d in [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:@"Flickr_Dictionary"]]) {

        // create button
        UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(currentx, currenty, 58, 58);
        button.imageView.backgroundColor = [UIColor colorWithRed:(241/255.0) green:(241/255.0) blue:(241/255.0) alpha:1];
        button.imageView.contentMode = UIViewContentModeScaleAspectFit;
        button.imageView.layer.cornerRadius = 6;
        button.imageView.layer.masksToBounds = YES;
        button.imageView.accessibilityLabel = [d objectForKey:@"id"];
        button.imageView.accessibilityValue = [d objectForKey:@"full"];
        button.imageView.tag = index++;
        [button addTarget:self action:@selector(imageClick:) forControlEvents:UIControlEventTouchUpInside];

        UIImage * image = [MVImage imageWithImage:[UIImage imageNamed:@""] covertToWidth:58.0f covertToHeight:58.0f];
        [button setImage:image forState:UIControlStateNormal];

        [newscrollview addSubview:button];
        [newscrollview bringSubviewToFront:button];
        [newscrollview setNeedsDisplay];

        // calculate next image view position
        currentx += (button.imageView.frame.size.width+15);
        if (currentx >= 289) {
            currentx = 11;
            currenty += (button.imageView.frame.size.height+15);
        }

        // set image
        NSString * nsuserdefault = [NSString stringWithFormat:@"Settings_SFThumbs%@_%@", retina, [d objectForKey:@"id"]];
        if ([NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:nsuserdefault]]) {
            MVImage * thumb = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:nsuserdefault]];
            [button setImage:[MVImage imageWithImage:[[UIImage alloc] initWithData:thumb.data] covertToWidth:58.0f covertToHeight:58.0f] forState:UIControlStateNormal];
        } else {
            [MVProject asynchronousImageLoad:button.imageView urlpath:[d objectForKey:@"thumb"] nsuserdefaultpath:nsuserdefault];
        }
    }
}

Edit on Feb 7, 2012 3:26 PM

these changes fixed all issues related to the code given in the question

thanks to @Eugene for pointing me in the right direction

/* ---------- ---------- ---------- ---------- ---------- */

- (void)viewDidLoad {
     [super viewDidLoad];

     [[MVProject sharedInstance] syncLoadSF:0];

    self.newscrollview = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 300, 275)];
    self.newscrollview.scrollEnabled = YES;
    self.newscrollview.delegate = self;
    [self.newscrollview setContentSize:CGSizeMake(300, (((ceilf(([[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:@"Flickr_Dictionary"]] count]-1)/4)) * (58+15)) + 15 + 58 + 15))];
    [picsMVContentCell.contentView addSubview:self.newscrollview];

    [self setGridView];
}

/* ---------- ---------- ---------- ---------- ---------- */

- (void)setGridView {
    NSString * retina = ([[MVProject sharedInstance] settings_retina]) ? @"2" : @"";
    [self.newscrollview setNeedsDisplay];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        NSInteger index = 0;
        for (NSDictionary * d in [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:@"Flickr_Dictionary"]]) {
            // create button
            UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];               
            button.frame = CGRectMake((11 + ((index%4)*(58+15))), (17 + ((floorf(index/4))*(58+15))), 58, 58);
            button.imageView.backgroundColor = [UIColor colorWithRed:(241/255.0) green:(241/255.0) blue:(241/255.0) alpha:1];
            button.imageView.contentMode = UIViewContentModeScaleAspectFit;
            button.imageView.layer.cornerRadius = 3;
            button.imageView.layer.masksToBounds = YES;
            button.imageView.accessibilityLabel = [d objectForKey:@"id"];
            button.imageView.accessibilityValue = [d objectForKey:@"full"];
            button.imageView.tag = index++;
            [button addTarget:self action:@selector(imageClick:) forControlEvents:UIControlEventTouchUpInside];

            dispatch_sync(dispatch_get_main_queue(), ^ {
                [button setImage:[MVImage imageWithImage:[UIImage imageNamed:@""] covertToWidth:58.0f covertToHeight:58.0f] forState:UIControlStateNormal];
                [self.newscrollview addSubview:button];
                [self.newscrollview bringSubviewToFront:button];
                [self.newscrollview setNeedsDisplay];

                // set image
                NSString * nsuserdefault = [NSString stringWithFormat:@"Settings_SFThumbs%@_%@", retina, [d objectForKey:@"id"]];
                if ([NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:nsuserdefault]]) {
                    MVImage * thumb = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:nsuserdefault]];
                    [button setImage:[MVImage imageWithImage:[[UIImage alloc] initWithData:thumb.data] covertToWidth:58.0f covertToHeight:58.0f] forState:UIControlStateNormal];
                } else {
                    [MVProject asynchronousImageLoad:button.imageView urlpath:[d objectForKey:@"thumb"] nsuserdefaultpath:nsuserdefault];
                }
            });
        }
    });
    [self.newscrollview setNeedsDisplay];
}
Was it helpful?

Solution

You cannot work with drawing on the background thread. Each time you add subview you should do this in the main thread. So the salvation for you would be to use your drawing code in a sync block, that's run on the main queue

  dispatch_sync(dispatch_get_main_queue(), ^ {
    [newscrollview addSubview:button];
    [newscrollview bringSubviewToFront:button];
    [newscrollview setNeedsDisplay];

    // calculate next image view position
    currentx += (button.imageView.frame.size.width+15);
    if (currentx >= 289) {
      currentx = 11;
      currenty += (button.imageView.frame.size.height+15);
    }

    // set image
    NSString * nsuserdefault = [NSString stringWithFormat:@"Settings_SFThumbs%@_%@", retina, [d objectForKey:@"id"]];
    if ([NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:nsuserdefault]]) {
      MVImage * thumb = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:nsuserdefault]];
      [button setImage:[MVImage imageWithImage:[[UIImage alloc] initWithData:thumb.data] covertToWidth:58.0f covertToHeight:58.0f] forState:UIControlStateNormal];
    } else {
      [MVProject asynchronousImageLoad:button.imageView urlpath:[d objectForKey:@"thumb"] nsuserdefaultpath:nsuserdefault];
    }
  });

OTHER TIPS

The problem your having is with setting the images. What your trying to do is add all the image containers to the scrollview. Then as the images are ready load them into their container. There are 2 ways to go about doing this.

First off add all the containers to the scrollview. (You might want some loading indicator or default image to show its loading)

1) Create a background thread for getting the images ready. When the image is ready add it to its container. (I find this way to be more work and kinda a pain)

2) Create a custom container, and let the custom container add its own image when its image is ready.

For example, I am loading images from a server. So I created AsyncImage class that is a custom class of UIImageView. And I created a function that I send the image url to and it handles the rest. It creates an NSURLConnection and when the data is loaded it calls self.image = [UIImage imageWithData:data];

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top