Multiple GCD request in a scrollView - objective-c

I have a scrollView which i load images into while scrolling,and every time it has only 3 pages with images(current previous next ) .
It works great ,and the memory consumption is low (30-40M) for the iPad .
Problem is, that when i scroll fast(no paging enable), i get a memory warning, even that the memory is low.
I discovered that this is because of multiple GCD requests at once .
What can be done, in order to eliminate this from happen ?
One solution i could think of, is to start loading only when scroll view is decelerating , but i am afraid that the UX will be bad with this(now it looks great) ,
Another one, is using NSOperation for this- which is a higher level and might be better ?
Another one, is to use AutoreleasePool for the thread request-which i saw in a few articles.
What is the best approach for this ?
Here is the loader, that happens while scrolling (to relevant pages):
[self downloadImageWithURL:userUrl completionBlock:^(BOOL succeeded, NSData *tdata)
{
if (succeeded)
{
//load back
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
{
UIImage *image=[UIImage imageWithData:tdata scale:1];
if (image)
{
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
[image drawAtPoint:CGPointZero];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
dispatch_async(dispatch_get_main_queue(), ^
{
//LOAD EASY
[UIView transitionWithView:view
duration:0.1f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^
{
view.image=nil;
view.image=image;
}
completion:nil];
});
image=nil;
});
}
}];
}

I found a great solution that works great for me, i have an array of all what is being loading right now, and i have checked when this array size causing troubles . When user scrolls i check its size, and when the size exceeded a certain size, i just stop the scroller from scrolling fast. This happens usually when the user is scrolling too fast.
CGRect frame=scroller.frame;
frame.origin.y=scroller.contentOffset.y-1.0;
if ([imagesAreLoading count]>10 )//too many requests has being made.
[scroller scrollRectToVisible:frame animated:YES];
Hope it helps other people.

Related

Terminated due to Memory Error when load number of images into UIScrollView

I am using UIScrollView in my app that loads number of images(more then 200 images).
I load images from disk and just add into the UIScrollView.I get the Xcode 5 error message "Terminated due to memory error" and app has Terminated unexpectedly.But not every time it has happened occasionally.
I am still entirely not sure this is a memory problem.But i didn't found the code cause of the Memory Problem.
Even I have checked memory leak issue through the instrument tool there is no memory leak.
I have created custom reusable UIView Class with help of Custom ACReuseQueue for UIScrollView like UITableView due to the efficiency and time consuming of the object creation
I have implemented enqueue and dequeue concept inside the scrollViewDidScroll:
To add image into the UIScrollView: used to following code
-(void)imageAdd:(ALAsset *)item
{
__block ImageControl *imageControl;
imageControl = (ImageControl*)[[ACReuseQueue defaultQueue] dequeueReusableObjectWithIdentifier:#"ImageControl"];
[imageControl setAlAsset:item];
[imageControl setDuration:1.0];
[imageControl setIsCopy:NO];
[self.galleryView addItem:imageControl];
[imageControl release];
}
below code is used to when user scroll the ScrollView
-(void)scrollViewDidScroll:(UIScrollView*)scrollView
{
for (ImageControl *imageControl in self.galleryView.items)
{
if (CGRectIntersectsRect(imageControl.frame, self.galleryView.bounds))
{
imageControl = (ImageControl*)[[ACReuseQueue defaultQueue] dequeueReusableObjectWithIdentifier:#"ImageControl"];
[self.galleryView addItem:imageControl];
}
else
{
[imageControl removeFromSuperview];
[[ACReuseQueue defaultQueue] enqueueReusableObject:imageControl];
}
}
}
Still,i didn't achieve the Reusable UIScrollView like UITableView
Don't load all of the images at the same time. Most aren't visible anyway.
Using a table view will likely make your life a lot easier and be the fastest way of having only the required images loaded.
If you want to use the plain scroll view, you need to implement the scrollViewDidScroll: delegate method and use it to add and remove subviews as the user scrolls the view.

How to remove or hide background in MKMapView when using custom tiles

Having tried many methods I still haven't found a good and full-proof way of preventing the usual "maps" from being shown behind custom map tiles that I am using. Ultimately I want my app to have a map page consisting only of a custom map.
I am really looking for a solution that is pure iOS and doesn't require any 3rd party software but it would appear difficult.
I have tried 3 methods already:
Number 1, hiding the background map via it's view:
NSArray *views = [[[self.mapView subviews] objectAtIndex:0] subviews];
[[views objectAtIndex:0] setHidden:YES];
this however doesn't work on a certain new operating system coming out very soon! The whole screen goes blank. The Apple Developer Forum hasn't provided a solution either
Number 2, Using another blank overlay (e.g. MKCircle) to cover the background map. This works however when scrolling or zooming out quickly, sometimes the overlay flickers off and you can briefly see the background map behind so not ideal.
Number 3, and this is what I have been working on for a few days now is to simply prevent the user from zooming out. Most documented methods tend to use regionDidChangeAnimated or regionWillChangeAnimated, however these do not seem to suddenly stop the map zooming out when pinching - they wait until the pinch movement has finished before taking effect so again it means the background map can be viewed briefly.
So now I am stumped, unless of course I have missed something with these other two methods.
So any help would be much appreciated!
Add this:
-(void)viewDidAppear:(BOOL)animated
{
MKTileOverlay *overlay = [[MKTileOverlay alloc] init];// initWithURLTemplate:tileTemplate];
overlay.canReplaceMapContent = YES;
[map addOverlay:overlay];
overlay = nil;
}
-(void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData *, NSError *))result
{
NSData *tile =nil;
}
-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay: (id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKTileOverlay class]])
{
MKTileOverlayRenderer *renderer = [[MKTileOverlayRenderer alloc] initWithOverlay:overlay];
[renderer setAlpha:0.5];
return renderer;
}
}
It will replace the map content from the background. It worked very well in my case where I am adding an overlay on the whole map and hiding the real map from the user.
You can't do this in current releases without a third-party library like MapBox. However, the future OS release that you speak of lets you do this.

Cocoa Application does not redraw after overlapping another application

After I overlay an application over my previous application, I go back to previous application and encounter a few errors:
certain components have disappeared
only way to make the components visible is to resize the window
that seems to redraw the whole canvas.
Weird thing is that there are only a couple of components and drawn images that are missing
It doesn't always happen but only a couple of times
I haven't found a solid way to reproduce the problem.
Anybody have an Idea why this is happening?
I experienced exactly the same issue (view was updated correctly only after resizing), except that I've used OpenGL drawing in OSX game.
My problem was solved by adding this:
GLint vblSynch = 1;
[[self openGLContext] setValues:&vblSynch forParameter:NSOpenGLCPSwapInterval];
in my custom NSOpenGLView init method.
Then I've implemented:
- (void)drawRect:(NSRect)rect
{
[self destroyFramebuffer]; // glDeleteFramebuffers..
[self createFramebuffer]; // [super prepareOpenGl], glGenFrame(Render)Buffers, bind buffers, etc
[self drawView]; // [[self openGLContext] makeCurrentContext], make some drawing, [[self openGLContext] flushBuffer]..
}
like this.
After these changes, when window gets focus it redraws itself (without any resizing stuff :) ).
Hope this helps!

UIView animation to "rewind" a pan gesture: recursion?

I have a scenario where the user dragged a UIView somewhere; I want to return it to where it started, and I want it to return along the same track and at the same speed that the user dragged. I have come up with a recursive solution that looks like this:
#property (...) NSMutableArray *dragTrack; // recorded during pan gesture
- (void)goHome
{
[self backtrack: [self.dragTrack count]
withDelay: 1.0 / [self.dragTrack count]];
// interim version of delay; will implement accurate timing later
}
- (void)backtrack: (int)idx withDelay: (double)delay
{
if (idx > 0){
[UIView animateWithDuration:0 delay:delay options: 0
animations: ^{
self.center = [[self.dragTrack objectAtIndex:idx - 1] CGPointValue];
}
completion:^(BOOL finished){
[self backtrack: idx - 1 withDelay: delay];
}];
} else {
// do cleanup stuff
}
}
This works, and recursion depth doesn't appear to be an issue - my drag tracks are typically only a couple of hundred points long. (I assume the chances of the recursive call to backtrack getting tail call optimized are rather slim?). But I'm still wondering: is this a reasonable/normal/safe solution to what I'm trying to achieve? Or is there a simpler way to pass a collection of timestamps and states to an animation and say "play these"?
You are not causing recursion there. Since the completion block is called asynchronously, it is not getting deeper into function stack. You can see it yourself by setting a breakpoint there and check the stack trace.
You can imagine NSTimer there triggering theoretically infinite times and not causing recursion.
To your second question, you can make such animation. It is called CAKeyframeAnimation and it is part of QuartzCore framework. It would require you to work with layers instead of views, but it is not that difficult.
Just one note to your code: try to use option UIViewAnimationOptionCurveLinear for the animation to be more smooth.

CATiledLayer being removed and refreshed while zoomed in iPad 3rd gen

I'm experiencing a redraw problem on a CATiledLayer when the parent UIScrollView is zoomed in.
I'm rendering a PDF page in a CATiledLayer backed UIView. It has another UIImageView behind it, which contains a low-res image of the page that the CATiledLayer will draw. When I zoom in, it works as expected. The CATiledLayer will render a higher resolution image according to the zoom level.
The problem occurs after zooming. If I zoom in then just leave the iPad alone, the displayed image blurs then resharpens. It looks like the CATiledLayer is being removed, since I see the blurry low resolution image in the backing view, then the CATiledLayer gets redrawn, i.e. I see the tiling effect and the resharpening of the image. This happens if I just leave the app alone and wait about 30 to 40 seconds. I've only observed it on the iPad 3rd gen (New iPad, iPad3, whatever). I'm also testing on iPad2s and I have yet to encounter the issue.
Has anyone else encountered this problem? Any known cause and, possibly, solutions?
Edit:
My UIScrollViewDelegate methods are as follows:
// currentPage, previousPage, and nextPage are the pdf page views
// that are having the refresh problem
- (void)positionBufferedPages {
// performs math {code omitted}
// then sets the center of the views
[previousPage.view setCenter:...];
[nextPage.view setCenter:...];
}
- (void)hideBufferedPages {
if ([previousPage.view isDescendantOfView:scrollView]) {
[previousPage.view removeFromSuperview];
}
if ([nextPage.view isDescendantOfView:scrollView]) {
[nextPage.view removeFromSuperview];
}
}
- (void)showBufferedPages {
if (![previousPage.view isDescendantOfView:scrollView]) {
[scrollView addSubview:previousPage.view];
}
if (![nextPage.view isDescendantOfView:scrollView]) {
[scrollView addSubview:nextPage.view];
}
if (![currentPage.view isDescendantOfView:scrollView]) {
[scrollView addSubview:currentPage.view];
}
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return currentPage.view;
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
[self hideBufferedPages];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollViewParam withView:(UIView *)view atScale:(float)scale {
[self positionBufferedPages];
[self showBufferedPages];
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
// nothing relating to the pdf page view
// but does set the center of some other subviews on top of the pdf page views
}
Not sure how helpful this will be though, as the scrollview is not receiving input while the problem happens. As mentioned above, the pdf page is loaded to the CATiledLayer, then I leave the iPad alone (no input is received by the device), and the CATiledLayer will redraw by itself.
I've also tried catching calls to setNeedsDisplay, setNeedsDisplayInRect:, setNeedsLayout, and setNeedsDisplayOnBoundsChange: on both the view and the tiled layer, but the redraw happens without any of those functions getting called. drawLayer:inContext: gets called, of course, but the trace only shows some Quartz calls being started in a background thread (as expected, as tiled layers prepare the content in the background), so it is of no help either.
Thanks in advance!
How is your app's memory usage looking? CATiledLayer will discard its cache and redraw if a memory warning occurs. I've seen it do this even without memory warnings being sent to the app (just a higher than usual memory load). Use Instruments to see memory usage. You may need to use the OpenGL ES Driver instrument to see what's going on with graphics memory.
I spoke with an Apple engineer about this and the short answer is that iOS only has X amount of memory available for caching a CATiledLayer and on the Retina display of the iPad, there are just too many pixels to use more than one layer.
I had been using two CATileLayers to display a map view and a drawing view on top. I removed the second CATiledLayer and the problem went away.
I've had the exact same problem. In my case it was caused by using the UIGraphicsBeginImageContext() function; this function does not take scale into account, which gives problems on a retina display. The solution was to replace the call with UIGraphicsBeginImageContextWithOptions(), with the scale (=third) parameter set to 0.0.
If you based your code on the Zooming PDF Viewer sample code from Apple, like I did, chances are this will solve your problem too.
Longshot, any chance you are calling any methods on the views from the non-main thread? All sorts of unexpected funky stuff can happen if you do.