ProgressView not updating in iOS - ios

I know this question asked many times on StackOverflow, But I already checked that. my problem is I am updating progress bar like this on main thread after getting response from api hit
dispatch_async(dispatch_get_main_queue(),^{
for (int i = 0; i < 5000; i++)
{
NSLog(#"%f",i);
[progressView setProgress:i/5000 animated:YES];
}
});
from this way My progressivew not updating
but when I set static digit it's working
[progressView setProgress:0.7 animated:YES];
After searching I found that there is issue regarding float decimal number digits. Because i/5000 give 0.000000 always even on 4999/5000. So after some googling I chnaged
dispatch_async(dispatch_get_main_queue(),^{
for (int i = 0; i < 5000; i++)
{
float f = (float)i/5000;
[progressView setProgress:f animated:YES];
}
});
now it's updating but progress view not update continuously, it's track update in last at once from 0 to 1

The first issue you got right, the integer division is always going to give you 0.
The second issue is that the UI updates on the main thread - and only periodically even then. Now your code is also running on the main thread, so the UI is completely blocked while your code executes. It will only get a chance to update after your code ends, so you will only ever see the final value actually get rendered.

#trapper you are right, thanks for help me but I got this solution before a min ago, no problem.
Here I am posting my answer to help another guys.
This was a thread issue.
The drawing of progress are stacked in the main queue and executed only at some point, at the end.
So the solution is to run the loop in a user thread, and call the main thread when you need to draw (that is compulsory).
So, change update progress as follows
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
for (int i = 0; i < 50000; i++) {
float f = (float)i/50000;;
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[self->progressView setProgress:f animated:YES];
});
}
});

Related

Changing UICollectionView cell image at regular interval

I am developing an IOS 10.x application using UICollectionView and would like to change the image of specific cells, at regular interval.
The code below shows the current implementation. Even though it should change the cell background image every half of second, it changes the images immediately disregarding the NSThread SleepAt interval of 0.5 seconds.
I suspect something about the main thread handling or the ReloadItem method but hasn't reached a clear conclusion. Any insight is very welcome! Thank you.
NSNumber* originalCardSelected;
int position;
for (int i = 0; i < [opponentOriginalCardArray count]; i++) {
originalCardSelected = [opponentOriginalCardArray objectAtIndex:i];
position = [self convertLevelDataPositionToCellViewPosition:[originalCardSelected intValue]];
NSMutableArray *tmpBackgroundAssets = [self getNewAssetBackgroundBasedOnBackgroundType:playbackBackground Index:position];
self.backgroundAssets = tmpBackgroundAssets;
dispatch_async(dispatch_get_main_queue(), ^{
[collectionViewRoot reloadItemsAtIndexPaths:[collectionViewRoot indexPathsForVisibleItems]];
});
[NSThread sleepForTimeInterval:0.5];
}
You should use performBatchUpdates(_:completion:) methods and add your for loop inside it.
Remember to not keep strong references inside the block to avoid retain cycles.
EDIT:
and in the completion block, you can check if finished and add your NSThread methods

iOS UI elements won't update while in the method called

I'm doing a pretty simple iOS/ObjC program. Click a button, it's loops thru a for..next and displays counters and pics.
for (int i=0; i <= numberOfExercises; i++) {
exerciseName = [exercises objectAtIndex:j][0];
[self.lastLabel setText:exerciseName];
[self.lastLabel setNeedsDisplay];
usleep(1000000);
NSLog(#"Count: %d Name:%#", i, exerciseName);
}
However, it's not updating the actual textfield on the screen.
I've tried everything I know of and there's just something I can't see.
(IBAction)btnExerciseClicked:(id)sender {
for (int i=0; i <= numberOfExercises; i++) {
exerciseName = [exercises objectAtIndex:j][0];
exerciseImageName = [exercises objectAtIndex:j][1];
exerciseImageName = [exerciseImageName stringByAppendingString:#".jpg"];
self.exerciseImage.image = [UIImage imageNamed:exerciseImageName];
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
[self.exerciseImage setNeedsDisplay];
}
I've put the block in there:
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"inside the block");
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
[self.exerciseImage setNeedsDisplay];
});
And I've done a direct assign before the loop starts:
exerciseName = [exercises objectAtIndex:5][0];
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
NSLog(#"Name: %#", self.ExerciseName.text);
I've even written a separate method for it:
-(void) myCycleDisplay: (NSString *) imageName
nameOfExercise: (NSString *) exerciseName
voiceOver: (BOOL) useVoiceOver
countDown: (BOOL) useCountDown
beep: (BOOL) useBeep{
[self.ExerciseName setText:exerciseName];
[self.ExerciseName setNeedsDisplay];
self.exerciseImage.image = [UIImage imageNamed:imageName];
}
And after all that, STILL no display until AFTER the method is done. Put in a delay: usleep(1000000);
I've confirmed that the data is in the element. I've setup the element in code and in IB.
The app is pretty simple, a button is pressed on the screen, data is loaded, an array is walked and display items are updated based on elements in the array.
The data is in the textfield, this has been confirmed. It even display them, but only after the method is exited.
Once the method is exited, the text/pic are displayed properly.
So, as a test, I made the for..next loop run twice and clicked the button over and over. Sure enough it displayed properly AFTER leaving the method.
I can't get it to update the display while IN the method. (this also includes the slider).
Why do I have to exit a method to get the display to update?
I think the easiest solution to your problem is to dump the whole for-loop into another queue.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
for (int i=0; i <= numberOfExercises; i++) {
NSString *exerciseName = [exercises objectAtIndex:i][0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.lastLabel setText:exerciseName];
[self.lastLabel setNeedsDisplay];
});
usleep(1000000);
NSLog(#"Count: %d Name:%#", i, exerciseName);
}
});
There are three additional changes.
Changed [exercises objectAtIndex:j] to [exercises objectAtIndex:i]
I think this was a mistake on your part
Made exerciseName local to the block.
I could have made the declaration of exerciseName __block, but it's easier to just make the whole thing local.
Wrapped setting the label in dispatch_async(dispatch_get_main_queue(), ^{ … });
UI updates must be made on the main queue.
Note, this is a bad solution. You should rethink your approach entirely. I would move this logic into it's own separate class, then use notifications to get the UI to update.
When you call usleep(1000000) you are blocking the main queue. UI updates happen on the main queue, but you're blocking that queue, so they don't happen. Also, UI updates in general don't happen until you finish the current pass through the event loop-- from the user tapping your button, through your method doing whatever it needs to do, continuing until your method finishes. Then UIKit updates the UI. You need to let your method finish because that's how UIKit works.
I'm not exactly sure what you're trying to do. If you want to update your UI at intervals, look into NSTimer.
I hope I understood the problem well enough to give a relevant answer and I apologize if I didn't. Here's how I see it:
There is a set of exercises that have to be done one after another;
Exercises take certain amount of time;
The name of the exercise and a relevant image are displayed when exercise starts.
The current implementation does precisely that: display is updated and the system sleeps for a certain amount of time before updating the display again.
The problem with that approach is that UIKit frameworks that is commonly used when doing interactive things on iOS devices is what could be called "indirect". For instance, when .text property of a label is updated, text is not drawn, instead, the system is notified that display should be updated. UIKit periodically checks whether updates are requested and if they are, the display is redrawn.
All of this is done on the "main queue" by sequentially executing blocks of code added to it (as a side note, this is to unlike the way Javascript work in a browser). This means that as long as some block of code is executed, everything else of the main queue will not be and the app will stop being interactive and updating display. Thus, the way to use UIKit is by quickly notifying it of necessary changes and finishing the function. Consequently, one never does long calculations or "sleeps" on on the main queue.
In context of this question, this leaves a problem of timed updates: we want to update the UI after some time has expired. There are 2 solution that come to mind:
The "typical" one would to be use the NSTimer class;
If constant updates to the UI are necessary, there is a CADisplayLink class and a convenient [UIScreen displayLinkWithTarget:selector:] method to create one on, say, [UIScreen mainScreen]. What it does is, it calls the selector in time for screen updates (currently, that's about 60 times per second but you can configure it to be called less frequently). It is ideal for things like count-down timers and frequently updating UI elements such as progress bars.
(I've switched to Swift and the latest API so the method names might not be exactly right in Objective-C).
Please let me know if I misunderstood the question or if you need sample code or further clarification.
Good luck!

Stop or completely erase iOS Global Dispatch Queue

So I have read several posts on here about the queueing system but I cannot seem to figure out how to do what I am looking for. Currently I am going to a page and loading images using a loop, and each image uses async dispatch seen here.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Load Image Code Goes Here
dispatch_async(dispatch_get_main_queue(), ^{
//Display Image Code Goes Here After Loading.
});
});
And this works perfectly, however I need to be able to destroy this queue or wait until it is finished before doing anything else. Basically certain pages have dozens and dozens of images, so they all start loading, then I go to a totally separate area in the app and do a completely different image loading (1-2 images) and it will take almost a minute because it is still waiting for the other images to load. Is there a way to destroy my old queue or suspend? I have seen a people say "you can but it will corrupt the incoming data" which is fine because the image would just re download upon a new page load. Any ideas?
A slightly different approach is to only dispatch a few images to start and then dispatch another when any of the previous requests finishes. Here's what the code looks like
- (IBAction)startButtonPressed
{
self.nextImageNumber = 1;
for ( int i = 0; i < 2; i++ )
[self performSelectorOnMainThread:#selector(getImage) withObject:nil waitUntilDone:NO];
}
- (void)getImage
{
if ( self.view.window && self.nextImageNumber <= 6 )
{
int n = self.nextImageNumber++;
NSLog( #"Requesting image %d", n );
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://images.apple.com/v/iphone-5s/gallery/a/images/download/photo_%d.jpg", n]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:url]];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog( #"Received image %d", n );
[self updateImage:image forView:n];
[self performSelectorOnMainThread:#selector(getImage) withObject:nil waitUntilDone:NO];
});
});
}
}
The images being downloaded are named "photo_1.jpg" through "photo_6.jpg". The process is started by requesting the first two photos. When one of those finishes, the next request is dispatched, and so on until all 6 photos are retrieved. The key line of code is
if ( self.view.window && self.nextImageNumber <= 6 )
The first part of the if checks whether the view controller is still on the screen. When the user navigates away from the view controller, self.view.window will be set to nil. So navigating away from the view controller breaks the chain of downloads.
The second part of the if checks whether all of the downloads are finished. This is easy to do since the filenames contain a number. For random filenames, you could fill an NSArray with the URLs and then index through the array until you reach the end.
I started the chain with 2 downloads because there are only 6 images to download at that URL. Depending on the image sizes and number of images, you might want to start by dispatching more. The tradeoff is to maximize bandwidth usage (by starting with more) versus minimizing cancellation time (by starting with less).
if you are using an NSURLConnection, you should maybe keep references to them outside of the blocks, then if you move away from the screen call [downloadConnection cancel] on each of them

UIView created in a GCD global queue odd behavior after added as a subview

I'm creating some UIViews and caching them for reasons that aren't really important to the question at hand.
After I add the view X as a subview to Y one of X's subviews does not appear. If I wait 20-30 seconds it suddenly appears.
Here's how I am creating the views and adding them to the cache. These views are not yet added to the ui, that happens later.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for(int i = 0; i < 10;i++){
MyUIView *cTemp = [[MyUIView alloc] initWithFrame:CGRectZero];
[self addViewToCahce:cTemp forKey:#"key"];
}
});
but if I remove the dispatch_async it appears as it should. Anyone know what is going on here or how to prevent this unusual behavior?
Never modify UI outside of the main thread. Cocoa, like most UI frameworks, is not multithreaded. Try the following instead:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// Do whatever processing you want to do here
dispatch_async(dispatch_get_main_queue(), ^{
for(int i = 0; i < 10;i++){
MyUIView *cTemp = [[MyUIView alloc] initWithFrame:CGRectZero];
[self addViewToCahce:cTemp forKey:#"key"];
}
});
});
As for why you experience the behavior you describe, I can only speculate. I would not trust the constructor to UIView to be thread safe. If you need to create your views in another thread, I would suggest refactoring the code a bit, if possible.

MKOverlayView performance

I am adding about 3000 MKOverlays to my map, and as you can imagine, it takes a while, up to about eight seconds sometimes. I'm looking for a way to use threading to improve performance, so the user can move the map around while overlays are being added. Preferably, the overlays would be added sequentially, starting with the just the ones within the map's region. I have tried something along these lines with GCD:
- (MKOverlayView*)mapView:(MKMapView*)mapView viewForOverlay:(id)overlay {
__block MKPolylineView* polyLineView;
//do the heavy lifting (I presume this is the heavy lifting part, but
// because this code doesn't compile, I can't actually *test* it)
// on a background thread
dispatch_async(backgroundQueue, ^ {
polyLineView = [[[MKPolylineView alloc] initWithPolyline:overlay] autorelease];
[polyLineView setLineWidth:11.0];
//if the title is "1", I want a blue line, otherwise red
if([((LocationAnnotation*)overlay).title intValue]) {
[polyLineView setStrokeColor:[UIColor blueColor]];
} else {
[polyLineView setStrokeColor:[UIColor redColor]];
}
//return the overlay on the main thread
dispatch_async(dispatch_get_main_queue(), ^(MKOverlayView* polyLineView){
return polyLineView;
});
});
}
But because GCD blocks are defined with void parameter and return types, this code doesn't work- I get an incompatible pointer type error on the return line. Is there something I am missing here, or another way to thread this? Or perhaps an entirely different way to improve the performance of the overlay-adding process? I appreciate any and all help!
Edit:
I have found that the problem is not where I actually add the overlays here:
for(int idx = 1; idx < sizeOverlayLat; idx++) {
CLLocationCoordinate2D coords[2];
coords[0].latitude = [[overlayLat objectAtIndex:(idx - 1)] doubleValue];
coords[0].longitude = [[overlayLong objectAtIndex:(idx - 1)] doubleValue];
coords[1].latitude = [[overlayLat objectAtIndex:idx] doubleValue];
coords[1].longitude = [[overlayLong objectAtIndex:idx] doubleValue];
MKPolyline* line = [MKPolyline polylineWithCoordinates:coords count:2];
[line setTitle:[overlayColors objectAtIndex:idx]];
[mapViewGlobal addOverlay:line];
}
Adding all 3000 takes maybe 100ms here. The part that takes a long time (I assume) is where I actually create the overlays, in the first method I showed.
There is a little gap between what you want and what the compiler can do. When you are calling dispatch_async, you are actually telling the CPU "here, have this chunk of code, and run it whenever you feel like it, not now, not blocking my user interface thread". But, your method has to return now. There is simply no way for you to create anything in a background thread, because you are going to have to wait for it anyway before mapView:viewForOverlay: returns, since it has to return something.
This method is not the place to use GCD or any background code. If your problem is the addition of a big number of overlays at once, I would split all the overlays into chunks of say 100 and add them to the map with a delay of 100ms between each batch.

Resources