Changing UICollectionView cell image at regular interval - ios

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

Related

ProgressView not updating in 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];
});
}
});

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!

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.

Adding UIViews using GCD

In an application I'm developing, I have a horizontal UIScrollView that is used as a sort of table view.
Adding the subviews to it blocked the main thread, so I decided to use GCD instead, and create the views in a background thread and add them to the UIScrollView instance in the main queue.
The relevant code is this:
NSUInteger numberOfItems = [_dataSource numberOfItemsInBandView:self];
CGFloat __block nextX = 0.0;
dispatch_queue_t bgQueue = dispatch_queue_create("bandview", NULL);
for (NSUInteger i = 0; i < numberOfItems; i++) {
dispatch_async(bgQueue, ^{
UIView *itemView = [_dataSource bandView:self viewForItemAtIndex:i];
itemView.frame = CGRectMake(nextX, 0, itemView.frame.size.width, itemView.frame.size.height);
dispatch_async(dispatch_get_main_queue(), ^{
[_scrollView addSubview:itemView];
_scrollView.contentSize = CGSizeMake(nextX, self.frame.size.height);
});
nextX += itemView.frame.size.width;
});
}
dispatch_release(bgQueue);
_scrollView is a UIScrollView instance (properly initialized).
What I expected is to see the subviews be added to the UIScrollView one by one, but instead, what I'm experiencing is that everything runs asynchroniously, and then the scrollbars refreshes with all its subviews added at once (which is not what I expect).
Can anyone spot what I'm missing here?
UI changes need to happen on the main thread. Performing UI changes in a background thread (async gcd queue) will result in undefined/undesirable behavior as you are seeing. It seems unlikely that adding subviews is the main cause of the problem. The issue likely lies in the way the data is retrieved for the subviews. A solution would be to add all of the subviews then retrieve the data for them asynchronously, and then update them as the operation completes. Also consider not adding views that will not be visible to the user, and add them dynamically as the user scrolls similar to how a UITableView works.
Another thing worth pointing out is that you have created a serial queue (bandview) to dispatch the UIView creation requests into, but this also means that each creation request will happen in linear order vs being created in the most concurrent fashion possible. You might try dispatching the creation requests onto one of the global concurrent queues if that's not the desired behavior.

Resources