I have a "Loading Screen" with a "Loading bar". This loading screen gets images from my server and loads them to CCSprites, so i will be using them in other screens. While the loading screen is downloading images and creating the CCSprites, i want my Loading Bar to update it's UI. LoadingBar is a CCNode subclass.
I know I should use threading to update UI, but the problem is that (as far as I know) Cocos2D nodes are not thread safe so when i use the code below, it does update the UI but then the sprites does not load:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
// go do something asynchronous...
dispatch_async(dispatch_get_main_queue(), ^{
// update UI
});
});
I have tried many things, but i can't solve this problem. Please, help :)
Here is the code that makes my LoadingBar update:
-(void)makeStep{
int actualPersentage = 100 * (numSteps - numStepsToGo + 1) / numSteps;
int objectAtIndex = MAX(0, numSteps - numStepsToGo);
persentageLabel.string = [NSString stringWithFormat:#"%i%% %#", actualPersentage, [steps objectAtIndex:objectAtIndex]];
[frontground setTextureRect:CGRectMake(0, 0, frontground.textureRect.size.width + (200/numSteps), 40)];
frontground.position = ccp(initialPosition + (frontground.contentSize.width/2) - (background.contentSize.width/2), frontground.position.y);
numStepsToGo --;
if(numStepsToGo == 0){
[frontground setTextureRect:CGRectMake(0, 0, 200, 40)];
persentageLabel.string = [NSString stringWithFormat:#"All loaded correctly!"];
}
}
Basically what I have is a background gray rectangle as background and a green one (frontground) that "fills" the background each time makeStep is called.
Here is the code example where i parse a JSON and where my LoadingBar should update:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
...
CCSprite *ssButtonStart = [self getSpriteFromUrl:[startScreenData objectForKey:#"SSimg_button_start"]];
CCSprite *ssButtonExit = [self getSpriteFromUrl:[startScreenData objectForKey:#"SSimg_button_exit"]];
CCSprite *ssBackground = [self getSpriteFromUrl:[startScreenData objectForKey:#"SSimg_background"]];
self.gameProperties.slBackground = ssBackground;
self.gameProperties.slButtonExit = ssButtonExit;
self.gameProperties.slButtonStart = ssButtonStart;
NSLog(#"Start Screen Loading done!");
dispatch_async(dispatch_get_main_queue(), ^{
[self updateStep];
});
...
dispatch_async(dispatch_get_main_queue(), ^{
[self updateStep];
});
//Etc.
Code where i get the image and convert to CCSprite:
-(CCSprite*)getSpriteFromUrl:(NSString*)stringUrl{
__block NSData *imageData;
__block CCSprite *sprite;
imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:stringUrl]];
//NSLog(#"string imageData: %#", imageData);
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageWithData: imageData]];
dispatch_async(dispatch_get_main_queue(), ^{
CCTexture2D *texture = [[CCTexture2D alloc] initWithCGImage:imView.image.CGImage resolutionType:kCCResolutionUnknown];
if(texture != nil){
sprite = [[CCSprite alloc] init];
sprite = [[CCSprite alloc] initWithTexture:texture];
}else{
[self showError:#"Some textures didn't load correctly. Please try again!"];
}
// update UI
});
return sprite;
}
I am using iOS 7 and Cocos2D 2.0
You should update the bar on the main thread. Instead move the downloading of images to a background thread. NSData or NSURLRequest even has async methods to do so. Then when you have completed downloading the images create the CCSprites on the main thread as a last step. Sprites have to be created on the main thread anyway.
This answer from LearnCocos2D (see code in question) plus:
Solved! I made an sleep after getting every sprite in getSpriteFromUrl. The code is [NSThread sleepForTimeInterval:0.2];. Hope it helps anyone else!
From me, solved my question. Thanks!
Related
I would like to create a pdfReader with a collectionview. What I want is to have a collectionview with Thumbnails of the pdf to display. So I use this in the viewDidLoad (to avoid the fact that it will generate the thumbnail in the collectionview each time we go down or up). It is generated one time, and it is without lag :
Loading the thumbnail of the pdf in viewDidLoad:
- (void)viewDidLoad
...
coverPdf = [NSMutableArray new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Load image on a non-ui-blocking thread
NSString *pdfPath = nil;
NSURL *pdfUrl = nil;
CGPDFDocumentRef pdfRef = nil;
NSMutableArray *arr = [NSMutableArray new];
for (id cover in filePathsArray)
{
pdfPath = [categoryPath stringByAppendingPathComponent:cover];
pdfUrl = [NSURL fileURLWithPath:pdfPath];
pdfRef = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
[arr addObject:[self imageFromPDFWithDocumentRef:pdfRef]];
NSLog(#"first process");
}
coverPdf = [NSMutableArray arrayWithArray:arr];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[pdfCollectionView reloadData];
});
});
...
}
Generating the thumbnail:
- (UIImage *)imageFromPDFWithDocumentRef:(CGPDFDocumentRef)documentRef
{
CGPDFPageRef pageRef = CGPDFDocumentGetPage(documentRef, 1);
CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, CGRectGetMinX(pageRect),CGRectGetMaxY(pageRect));
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, -(pageRect.origin.x), -(pageRect.origin.y));
CGContextDrawPDFPage(context, pageRef);
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
Using the thumbnail:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ListPdfCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CollectionViewCell" forIndexPath:indexPath];
cell.productLabel.text = [filePathsArray objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Load image on a non-ui-blocking thread
dispatch_sync(dispatch_get_main_queue(), ^(void) {
// Assign image back on the main thread
if ([coverPdf count] > indexPath.row)
{
cell.pdfImage.image = [coverPdf objectAtIndex:indexPath.row];
}
});
});
return cell;
}
I have two problems with that method :
- The first one is that the thumbnail is taking too much time to appear. When it is loaded, it works well.
- The second problem, is that the memory is continually increasing and even if I close the viewcontroller, and I come in it, it seems that the memory is not released. If I close the viewcontroller and come in 9 or 10 times, if crash the app.
In conclusion, how can I create a collection view by loading the thumbnails of the pdfs in advance and how to avoid a crash with the memory increasing ?
Thanks in advance.
SOLUTION :
For the fact that it takes too much time to appear, I just replaced the DISPATCH_QUEUE_PRIORITY_BACKGROUND with the DISPATCH_QUEUE_PRIORITY_HIGH. It is much better.
For the memory leak, I used the CGPDFDocumentRelease() function just at the end of the loop like this, and all works like a charm :
for (id cover in filePathsArray)
{
pdfPath = [categoryPath stringByAppendingPathComponent:cover];
pdfUrl = [NSURL fileURLWithPath:pdfPath];
pdfRef = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
[arr addObject:[self imageFromPDFWithDocumentRef:pdfRef]];
CGPDFDocumentRelease(pdfRef);//Line added
NSLog(#"first process");
}
I have bad news for you. Memory leak is not your problem but iOS 10 memory management bug
There is a memory management bug in iOS 10.0.1 and 10.0.2 in the CGContextDrawPDFPage() function.
You can find details here http://www.openradar.me/28415289
Also you can find useful this discussion https://github.com/vfr/Reader/issues/166
In few words possible workaround is not to create
CGPDFPageRef pageRef = CGPDFDocumentGetPage(documentRef, 1);
each time but use one CGPDFPageRef for all of your pdf files. Then set it NULL when you not needs it anymore.
Creating thumbnails lag
And in terms of solution for creating thumbnails. I can only suggest not to do it in this VC at all but create thumbnail for each .pdf in the app at the moment when this .pdf added to the app (or on app launch in background service if all pdfs stored in the app bundle). Then you can save this previews as .jpg or .png files with names equal to pdf names or set relationships between pdf and preview in any database or other storage if you have it in your app.
Then just reuse this previews in your collectionView.
Successfully able to detect square object from video stream using powerful OpenCV. Everything was fine, except video lags frame due to calculation burden on main thread.
As you can see below code snippet, I added NSOperationQueue inside CvVideoCameraDelegate delegate method processImage:(cv::Mat&)image When I tried to run findSquaresInImage on background operation queue and UIImageFromCVMat main queue.
- (void)processImage:(cv::Mat&)image
NSOperationQueue *videoProcessQueue = [[NSOperationQueue alloc] init];
[videoProcessQueue addOperationWithBlock:^{
// do some time consuming stuff in the background
cv::Mat matResultImage = [self findSquaresInImage:image]; //---> Method will return the square object and takes more than 0.3 seconds
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update the UI here
self.imageView.image = [UtilityClass UIImageFromCVMat:matResultImage]; //---> Updates the UI
}];
}];
}
But I got exception in this line cv::pyrDown()
- (cv::Mat)findSquaresInImage:(cv::Mat)_image
{
std::vector<std::vector<cv::Point> > squares;
cv::Mat pyr, timg, gray0(_image.size(), CV_8U), gray;
int thresh = 20, N = 2;
cv::pyrDown(_image, pyr, cv::Size(_image.cols/2, _image.rows/2)); //----> HERE I GOT EXCEPTION
cv::pyrUp(pyr, timg, _image.size());
std::vector<std::vector<cv::Point> > contours;
//remaining logic goes here........ ...... ...... ...
}
What is wrong here? Any alternative way to improve ?
//UPDATE :
Same issue even with GCD blocks :
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
cv::Mat matResultImage = [self findSquaresInImage:image];
dispatch_async( dispatch_get_main_queue(), ^{
self.imageView.image = [JKUtilityClass UIImageFromCVMat:matResultImage];
});
});
Finally able to resolve after 2 major changes :
Directly mapping image from processImage:(cv::Mat&)image to CvVideoCamera object. Never used already initialized parent view self.imageView.
To speedup process, included OpenCVSquares Detection library.
I have added the loading bar in below snippet.It’s worked fine which I have deleted small amount of data(say 30 to 150 mb). But the problem is when I deleted large size of data, the loading bar doesn't get loaded(approx 190mb).
dispatch_async(dispatch_get_main_queue(), ^{
[self addloading];
if(_isProgress)
return;
_lastDeleteItemIndexAsked = index;
NSInteger catalogue_id =[[[_currentData objectAtIndex:index%[_currentData count]] valueForKey:#"catalogue_id"] integerValue];
BOOL update_avilable = [[[online_date_array objectAtIndex:index%[_currentData count]] valueForKey:#"update_version"] boolValue];
if(update_avilable)
[[self GridViewDelegate] deletecatlogue_for_update:catalogue_id];
else
[[self GridViewDelegate] deletecatlogue:catalogue_id];
[online_date_array replaceObjectAtIndex:index%[online_date_array count] withObject:[NotificationHandler check_online_date_of_catalogue:catalogue_id]];
[_gmGridView reloadObjectAtIndex:index%[_currentData count] withAnimation:GMGridViewItemAnimationFade];
[self stopLoadingfor_delete];
});
Is it size or execution time?
I'm going to assume that self is a UI object. If it is, the block should be:
Controller* __weak weakSelf = self;
dispatch_async(queue, ^{
Controller* strongSelf = weakSelf;
if (strongSelf) {
...
}
else {
// self has been deallocated in the meantime.
}
});
Thus you may prefer a MVVC solution, where a view model does the hard work, not the view controller.
I am trying to call a method in which I send to the background making use of dispatch_async.
It should be something that is simple, but for some reasons the UI is still blocked until the method returns.
Here is what I have:
dispatch_queue_t privateQueue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
dispatch_async(dispatch_get_main_queue(), ^
{
imgView = [controllerB startProcess];
controllerC.imageView = imgView;
});
});
I still have to wait for startProcess returns before UI is free again.
Then I tried to move imgView = [controllerB startProcess]; outside of dispatch_get_main_queue():
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
imgView = [controllerB startProcess];
dispatch_async(dispatch_get_main_queue(), ^
{
controllerC.imageView = imgView;
});
});
In this case, the UI is never updated with imgView but UI is not locked up.
I have tried to use a global queue, but the result is the same (UI has to wait):
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
I think I am missing something very obvious here. Either that or it has been long day for me.
EDIT:
In [controllerB startProcess];
I am making use of:
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);
UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I am not sure if these methods have anything to do with GCD that causes my problem. The image is just .png.
Been thinking hard on this. Am running out of ideas. The only way I can update the UI with the image is to place the method call within dispatch_get_main_queue(), which beats the purpose of using GCD because all UI is blocked until the image is ready and method returns.
Any suggestion would be greatly greatly appreciated.
Use the second approach. Modify startProcess to use completion blocks and update your imageView inside the completion block. This ensures that imageView is updated after startProcess is complete.
Is it possible, that -in your 2nd example- when you try to set the imageView on the main queue the asynchronous calculation of the imageView in the background has not finished yet, so it can't display the imageView?
In that case a dispatch group might help:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.name.queue”, DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
//Do work
imgView = [controllerB startProcess];
});
dispatch_group_notify(group,queue,^{
//This code runs as soon as the code in the dispatch group is done.
controllerC.imageView = imgView;
});
I create a UIView contentView and display it. I then fetch data from server and create a bunch of subviews displaying the data. I am using MBProgressHUD to display while waiting on data.
if (datasetSubBar.panels == nil) {
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:datasetSubBar.filterListView animated:YES];
HUD.labelText = #"Creating Panels";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[datasetSubBar createPanels];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:datasetSubBar.filterListView animated:YES];
});
});
}
in createPanels method, i fetch the data then create the panels. The panel is created, added to filterListView (the content view) and then add the constraints:
for (int i = 0; i < panels.count; i++) {
NSLog(#"thread: %#", [NSThread currentThread]);
NSDate *startDate = [NSDate date];
DatasetFilterListPanelView *panel = [panels objectAtIndex:i];
[contentView addSubview:panel];
// add constraints to position each panel
}
these are run on a separate thread which is what I believe the issue is. The UI can only be updated on the main thread.
I tried adding:
dispatch_async(dispatch_get_main_queue(), ^{
[contentView addSubview:panel];
});
But that raises errors for the constraints (the constraints don't have reference to it since its in a different thread).
If I run createPanels on the main thread, the panels will display but it also locks up the UI until its complete.
Any ideas?
I'm not sure which constraints are violated, but you could also try:
[contentView performSelctorOnMainThread:#selector(addSubview) withObject:panel waitUntilDone:YES];
instead of
dispatch_async(dispatch_get_main_queue(), ^{
[contentView addSubview:panel];
});
Not sure whether this will help.