Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update uilabel from blocks

Importing multiple photos from album, one of the delegate method is

// Here info is array of dictionary containing FileName/AssetURL etc
- (void)somePicker(SomePicker*)somePicker didFinishPickingMediaWithInfo:(NSArray *)info {
    _importStatusView.center = self.view.center;
    [self.view addSubview:_importStatusView];
    [self dismissViewControllerAnimated:YES completion:^{
        NSNumber *total = [NSNumber numberWithInteger:info.count];
        [info enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSDictionary *imageInfo = (NSDictionary*)obj;
            NSString *fileName = [imageInfo objectForKey:@"UIImagePickerControllerFileName"];
            NSURL *imageURL = [imageInfo objectForKey:UIImagePickerControllerReferenceURL];
            ALAssetsLibrary *assetLibrary=[[ALAssetsLibrary alloc] init];
            [assetLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset) {
                NSLog(@"start");
                ALAssetRepresentation *rep = [asset defaultRepresentation];
                Byte *buffer = (Byte*)malloc(rep.size);
                NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil];
                NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
                NSString *filePath = [_currentPath stringByAppendingPathComponent:fileName];
                [data writeToFile:filePath atomically:YES];

                //This also has no effect 
                //dispatch_async(dispatch_get_main_queue(), ^{
                    //_lblImportCountStatus.text = [NSString stringWithFormat:@"%d of %d",idx+1,[total integerValue]];
                    //NSLog(@"Label value->%@",_lblImportCountStatus.text); //This prints values but after everything is finished it prints all line at once i.e. at the end of the enumeration of all items 
                //});

            //Update UI
            NSNumber *current = [NSNumber numberWithInteger:idx+1];
            NSDictionary *status = [NSDictionary dictionaryWithObjectsAndKeys:current,@"current", total,@"totalCount", nil];
            [self performSelectorOnMainThread:@selector(updateImportCount:) withObject:status waitUntilDone:YES];

                //_lblImportCountStatus.text = [NSString stringWithFormat:@"%d of %d",idx+1,[total integerValue]];
                if(idx==info.count-1){
                    [_importStatusView removeFromSuperview];
                }

                NSLog(@"Finish");
            } failureBlock:^(NSError *error) {
                NSLog(@"Error: %@",[error localizedDescription]);
            }];
        }];
    }];
}

My declaration for status view and label is

@property (strong, nonatomic) IBOutlet UIView *importStatusView; //View containing label 
@property (weak, nonatomic) IBOutlet UILabel *lblImportCountStatus; //Label 

Everything in above code is working fine and as expected, but the problem is a importStatusView is being added to the screen but lblImportCountStatus value is not displaying, though If I log the values it shows updated.

When enumeration is finished at the end all the NSLog gets printed for e.g. If I have imported 10 photos than at last it prints, i.e. dispatch_async(dispatch_get_main_queue() this function has no effect at all while enumeration is in progress.

Label value->1 of 10
Label value->2 of 10
Label value->3 of 10
Label value->4 of 10
Label value->5 of 10
Label value->6 of 10
Label value->7 of 10
Label value->8 of 10
Label value->9 of 10
Label value->10 of 10

What could be the issue ?

Update:

-(void)updateImportCount:(NSDictionary*)info{ //(NSNumber*)current forTotalItems:(NSNumber*)totalCount{
    NSNumber *current = [info objectForKey:@"current"];
    NSNumber *totalCount = [info objectForKey:@"totalCount"];
    _lblImportCountStatus.text = [NSString stringWithFormat:@"%d of %d",[current integerValue],[totalCount integerValue]];
    [_lblImportCountStatus setNeedsDisplay];
    NSLog(@"Updating ui->%@",_lblImportCountStatus.text);
}

Above function works on main thread and updates but stil label is not shown it prints following NSLog

start
Updating ui->1 of 10
Finish
start
Updating ui->2 of 10
Finish
start
Updating ui->3 of 10
Finish
start
Updating ui->4 of 10
Finish
start
Updating ui->5 of 10
Finish
start
Updating ui->6 of 10
Finish
start
Updating ui->7 of 10
Finish
start
Updating ui->8 of 10
Finish
start
Updating ui->9 of 10
Finish
start
Updating ui->10 of 10
Finish

I have uploaded project at this location, please feel free to help.

like image 336
Janak Nirmal Avatar asked Mar 11 '14 04:03

Janak Nirmal


1 Answers

All those blocks are executing on the main thread (the easiest way to verify this is using NSThread's +currentThread to get the current thread, and -isMainThread to check if it's the main thread. Anywhere you have code that you want to see what thread it's on, do something like this:

NSLog( @"enumeration block on main thread: %@", 
       [[NSThread currentThread] isMainThread] ? @"YES" : @"NO" );

I think the problem is, since this is all executing on the main thread, you're locking up the runloop, not giving the UI a chance to update.

The right way to fix this is probably to really do this processing on a separate thread (calling the code to update the UI via performSelectorOnMainThread:, as you're doing now). But, a quick hack to make it work would be to allow the runloop to run. At the end of updateImportCount:, do something like this:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];

It ain't pretty, but it will work.

Update: In Cocoa (Mac OS and iOS) there a concept of a runloop. The main thread of your application drives an NSRunLoop which serves as the event loop. User actions -- taps, etc. -- are processed through this loop, as are things like timers, network connections, and other things. See the NSRunLoop Reference for more information on that.

In iOS, drawing also happens on the runloop. So when you call setNeedsDisplay on a view, that view is not redrawn immediately. Rather, it's simply flagged as needing redraw, and then on the next drawing cycle (the next trip through the runloop) the actual drawing takes place. The UIView reference has a brief description of this (see the section "The View Drawing Cycle". Quoting from that section:

When the actual content of your view changes, it is your responsibility to notify the system that your view needs to be redrawn. You do this by calling your view’s setNeedsDisplay or setNeedsDisplayInRect: method of the view. These methods let the system know that it should update the view during the next drawing cycle. Because it waits until the next drawing cycle to update the view, you can call these methods on multiple views to update them at the same time.

When you return from whatever method was called in response to some user action (in this case, somePicker:didFinishPickingMediaWithInfo:, control returns to the runloop, and any views that need redrawing, are. However, if you do a bunch of processing on the main thread without returning, the runloop is basically stalled, and drawing won't take place. The code above basically gives the runloop some time to process, so drawing can take place. [NSDate date] returns the current date and time, right now, so that basically tells the runlooop "run until right now", which ends up giving it one cycle through the loop, giving you one drawing cycle, which is an opportunity for your label to be redrawn.

like image 184
zpasternack Avatar answered Oct 17 '22 01:10

zpasternack