UIGraphicsBeginImageContextWithOptions very masive memory disaster - ios

I have this line of code to take screenshot of uiview:
- (UIImage *)imageWithView:(UIView *)view {
UIImage *viewImage = nil;
float height;
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if(orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight){
height = view.frame.size.height;
}else{
height = 730;
}
UIGraphicsBeginImageContextWithOptions(CGSizeMake(view.frame.size.width, height), YES, 0.0);
[view drawViewHierarchyInRect:CGRectMake(0, -60, view.bounds.size.width, height) afterScreenUpdates:YES];
viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage;
}
... in this uiview is UIWebView.
This - (UIImage *)imageWithView:(UIView *)view method I call in everytime uiwebview finish load. And there's is my problem: When I open some small webpages like google it method takes some kb of memory, but when I open something biger like bbc, cnn, yahoo or stackoverflow, it can take up to 80mb memory usage.
There is snapshoot of instrument when cnn.com was opened.
After few seconds it realeses, but I don't want that big memory usage, because you might imagine, how useless becomes uiwebview in these seconds.
So, what is your suggestions, how to take screenshoot of uiwebview, without so big memory usage, I don't even need this uiimage in good quality, because I put it in 120*80 uiimageview in one of the screen corners.

The last parameter in UIGraphicsBeginImageContextWithOptions is scale. If you know you only need a low resolution rendering of the web view, try setting a value for scale less than 1.0.
UIGraphicsBeginImageContextWithOptions(CGSizeMake(view.frame.size.width, height), YES, 0.2);

Related

VM: CG raster Data memory keep growing

so i am trying to make an app that will let the user change the color of the UIImage, for that i am using this function i found
- (UIImage *)imageWithTintColor:(UIColor *)color fraction:(CGFloat)fraction
{
if (color)
{
UIImage *image;
if ([UIScreen instancesRespondToSelector:#selector(scale)])
{
UIGraphicsBeginImageContextWithOptions([self size], NO, 0.f);
}
else
{
UIGraphicsBeginImageContext([self size]);
}
CGRect rect = CGRectZero;
rect.size = [self size];
[color set];
UIRectFill(rect);
[self drawInRect:rect blendMode:kCGBlendModeDestinationIn alpha:1.0];
if (fraction > 0.0)
{
[self drawInRect:rect blendMode:kCGBlendModeSourceAtop alpha:fraction];
}
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
return self;
}
everything works but the CG raster Data is growing in memory
I found the problem, and it was my bad logic, i am using 2 views one to show and one to work with ex:resize, move, rotate. And each time i was addingSubview to both where one of them need to hold just 1 at a time, a simple:
for (UIView *view in 2cndView.subviews)
{
[view removeFromSuperview];
}
did the trick for me
I have been fighting with my app, that suddenly would not launch properly, for some time now. It turned out that when I had switched a number of images' Render as to Template in the Image asset file, it caused the app to totally bomb out. CG Raster Data was growing exponentially and finally caused the app to stop and Xcode just said
Lost connection with iPhone.. check connections etc
It would appear that during every launch the images get reprocessed for this 'Template' setting, which consumed a disgusting amount of RAM and actually left it unable to boot. To solve this, I lowered the resolution of the images - as simple as that.

iOS8 scale glitch when calling drawViewHierarchyInRect afterScreenUpdates:YES

I was converting a project from iOS7 to iOS8 which uses custom transitions and needs to capture the modal after it finishes loading afterScreenUpdates:YES and was seeing that the entire screen scale up for a second and scale back down. I also see this happening in the Flickr app for iOS between sections and on Yelp app when transitioning to a photo on iOS8.
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, YES, 22.0);
[self.view drawViewHierarchyInRect:self.view.frame afterScreenUpdates:YES];
UIGraphicsEndImageContext();
Adding a larger scale factor helps emphasize the glitch more... but i'm just calling this on a button press in the example.
EDIT This appears to happen on iPhone 6 and 6 plus not on the 5.
Sample project github
Do you NEED it to draw after the screen updates? because I'm using:
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
and it seems to work fine on iOS7 and iOS8. I imagine this isn't a great solution for capturing images regularly (like multiple times a second) but it seems to work for a once off blur.
You have to provide #3x launch images for the 6 and 6 plus. The 6 being scaled at 750x1334, and the 6 plus image being scaled at 1242x2208.
Even it looks like bug in API, you can call drawViewHierarchyInRect with afterScreenUpdates set to NO to build snapshot AFTER screen updates if you use cunstruction like this:
typedef void (^CompletionHandlerWithId)(id result);
-(void)imageContaining:(CGRect)rect afterScreenUpdates:(bool)afterScreenUpdates opaque:(BOOL)opaque completion:(CompletionHandlerWithId)completion
{
bool success __block;
UIImage *snapshotImage __block = nil;
CompletionHandler block = ^{
// Create the image
UIGraphicsBeginImageContextWithOptions(self.bounds.size, opaque, [[UIScreen mainScreen] scale]);
success = [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
if (success)
{
snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
CGImageRef imageRef = CGImageCreateWithImageInRect(
[snapshotImage CGImage],
CGRectMake(
snapshotImage.scale*rect.origin.x,
snapshotImage.scale*rect.origin.y,
snapshotImage.scale*rect.size.width,
snapshotImage.scale*rect.size.height));
// or use the UIImage wherever you like
snapshotImage = [UIImage imageWithCGImage:imageRef scale:snapshotImage.scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
}
UIGraphicsEndImageContext();
if (completion)
{
if (! success)
{
NSLog(#"Error: [UIView drawViewHierarchyInRect] failed on %#", self);
(completion)(nil);
}
else
{
NSLog(#"Success: [UIView drawViewHierarchyInRect] on %#", self);
(completion)(snapshotImage);
}
}
};
if (afterScreenUpdates)
[CATransaction setCompletionBlock:^{
(block)();
}];
else
(block)();
}
This bug also exists when you run on an iPad2 running iOS7.
Fix: set afterScreenUpdates: to NO
My app has some moving UIButtons, so I don't allow the blur transition until after the movement has stopped. As far as I have found so far, there is no difference in YES or NO.
Appears to be fixed in iOS9 / XCODE 7 builds
I found a solution for me to solve this problem.
I add #3x launch images to my project. And choose launch Screen file to "Main".
This will make app run at original resolution, smaller bounds when run at iphone6, but not glitch when calling drawViewHierarchyInRect. Like this.
Then, scale my view to fullscreen when viewDidLoad.
- (void)viewDidLoad{
[super viewDidLoad];
UIScreen *mainScreen = [UIScreen mainScreen];
CGRect tempFrame=mainScreen.bounds;
double aspect=tempFrame.size.width/320;
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, aspect, aspect);
}
Hope helpful :)

iOS: renderInContext and Landscape orientation issue

I'm trying to save the currently shown views on my iOS device for a certain app, and this is working properly. But I've got a problem as soon as I'm trying to save a UIImageView in Landscape orientation.
See the following image that describes my problem:
I'm using Auto layout for this app, and it runs on both iPhone and iPad. It seems like the ImageView is always saved as shown in portrait mode, and I'm a little bit stuck right now.
This is the code I use:
CGSize frameSize = self.view.frame.size;
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
frameSize = CGSizeMake(self.view.frame.size.height, self.view.frame.size.width);
}
UIGraphicsBeginImageContextWithOptions(frameSize, NO, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat scale = CGRectGetWidth(self.view.frame) / CGRectGetWidth(self.view.bounds);
CGContextScaleCTM(ctx, scale, scale);
[self.view.layer renderInContext:ctx];
[self.delegate photoSaved:UIGraphicsGetImageFromCurrentImageContext()];
UIGraphicsEndImageContext();
Looking forward to your help!
I still have no idea what your exact issue is but using your screenshot code makes a bit strange image (not rotated or anything though, just too small). Can you try this code instead please.
+ (UIImage *)imageFromView:(UIView *)view {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, .0f);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Other then that you must understand there is a huge difference between UIImage and CGImage as the UIImage includes the orientation while CGImage does not. When dealing with image transformations it is usually with the CGImage and getting its width or height will discard the orientation. That means a CGImage will have flipped dimensions when its orientation is not up (UIImageOrientationUp). But usually when dealing with such images you create a CGImage from the context and then use [UIImage imageWithCGImage:ref scale:1.0f orientation:originalOrientation]. Only if you wish to explicitly rotate the image so it has no orientation (being UIImageOrientationUp) you need to rotate and translate the image and draw it onto the context.
Anyway, this orientation issues are quite fixed by now, UIImagePNGRepresentation respects the orientation and you have an image constructor from the CGImage already written above which is what used to be missing in the past if I remember correctly.

Why does my UIImage take up so much memory?

I have a UIImage that I'm loading into one of my app's views. It is a 10.7 MB image, but when it loads in the app, the app's resource usage suddenly jumps by 50 MB. Why does it do this? Shouldn't memory used increase by only about 10.7MB? I am certain that loading the image is what causes the jump in memory usage because I tried commenting these lines out and the memory usage went back to around 8 MB. Here's how I load the image:
UIImage *image = [UIImage imageNamed:#"background.jpg"];
self.backgroundImageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:self.backgroundImageView];
If there is no way to decrease the memory used by this image, is there a way to force it to deallocate when I want it to? I'm using ARC.
No, it should not be 10.7MB. The 10.7MB is the compressed size of the image.
The image loaded in to the UIImage object is a decoded image.
For each pixel in the image 4 bytes (R,G,B and Alpha) are used, therefore you can calculate the memory size, height x width x 4 = total bytes in memory.
So the moment you loaded the image into memory it will take up lots of memory, and since a UIImageView is used to present the image and as a subview the images is kept in memory.
You should try and change the size of the image to match the size of the iOS screen size.
As #rckoenes said
Don't show the images with high file size.
You need to resize the image before you display it.
UIImage *image = [UIImage imageNamed:#"background.jpg"];
self.backgroundImageView =[self imageWithImage:display scaledToSize:CGSizeMake(20, 20)];//Give your CGSize of the UIImageView.
[self.view addSubview:self.backgroundImageView];
-(UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
//UIGraphicsBeginImageContext(newSize);
// In next line, pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution).
// Pass 1.0 to force exact pixel size.
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
You can do one thing. if you can afford 50 MB for this image. If this image with 10 mb size is that much critical to your application then. you can release it just after its use to keep memory usage in control.
As you are using ARC there is no option for release but you can do this
#autoreleasepool {
UIImage *image = [UIImage imageNamed:#"background.jpg"];
self.backgroundImageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:self.backgroundImageView];
}
using autoreleasepool it will be sure that after this autoreleasepool{} block memory for fat image will be deallocated. making your device RAM happy again.
Hope it helps !

Getting black (empty) image from UIView drawViewHierarchyInRect:afterScreenUpdates:

After successfully using UIView’s new drawViewHierarchyInRect:afterScreenUpdates: method introduced in iOS 7 to obtain an image representation (via UIGraphicsGetImageFromCurrentImageContext()) for blurring my app also needed to obtain just a portion of a view. I managed to get it in the following manner:
UIImage *image;
CGSize blurredImageSize = [_blurImageView frame].size;
UIGraphicsBeginImageContextWithOptions(blurredImageSize, YES, .0f);
[aView drawViewHierarchyInRect: [aView bounds] afterScreenUpdates: YES];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
This lets me retrieve aView’s content following _blurImageView’s frame.
Now, however, I would need to obtain a portion of aView, but this time this portion would be “inside”. Below is an image representing what I would like to achieve.
I have already tried creating a new graphics context and setting its size to the portion’s size (red box) and calling aView to draw in the rect that represents the red box’s frame (of course its superview’s frame being equal to aView’s) but the image obtained is all black (empty).
After a lot of tweaking I managed to find something that did the job, however I heavily doubt this is the way to go.
Here’s my [edited-for-Stack Overflow] code that works:
- (UIImage *) imageOfPortionOfABiggerView
{
UIView *bigViewToExtractFrom;
UIImage *image;
UIImage *wholeImage;
CGImageRef _image;
CGRect imageToExtractFrame;
CGFloat screenScale = [[UIScreen mainScreen] scale];
// have to scale the rect due to (I suppose) the screen's scale for Core Graphics.
imageToExtractFrame = CGRectApplyAffineTransform(imageToExtractFrame, CGAffineTransformMakeScale(screenScale, screenScale));
UIGraphicsBeginImageContextWithOptions([bigViewToExtractFrom bounds].size, YES, screenScale);
[bigViewToExtractFrom drawViewHierarchyInRect: [bigViewToExtractFrom bounds] afterScreenUpdates: NO];
wholeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// obtain a CGImage[Ref] from another CGImage, this lets me specify the rect to extract.
// However since the image is from a UIView which are all at 2x scale (retina) if you specify a rect in points CGImage will not take the screen's scale into consideration and will process the rect in pixels. You'll end up with an image from the wrong rect and half the size.
_image = CGImageCreateWithImageInRect([wholeImage CGImage], imageToExtractFrame);
wholeImage = nil;
// have to specify the image's scale due to CGImage not taking the screen's scale into consideration.
image = [UIImage imageWithCGImage: _image scale: screenScale orientation: UIImageOrientationUp];
CGImageRelease(_image);
return image;
}
I hope this will help anyone that stumped upon my issue. Feel free to improve my snippet.
Thanks

Resources