How to draw to context without losing it? - iphone

I am drawing UIImages to/from a grid. I am currently only drawing the changes... and only the changes... but the old images are supposed to stay where they are... however right now they vanish after the next call of drawRect:(CGRect)rect:
- (void)drawRect:(CGRect)rect
{
int cellSize = self.bounds.size.width / WIDTH;
double xOffset = 0;
CGRect cellFrame = CGRectMake(0, 0, cellSize, cellSize);
NSUInteger cellIndex = 0;
cellFrame.origin.x = xOffset;
for (int i = 0; i < WIDTH; i++)
{
cellFrame.origin.y = 0;
for (int j = 0; j < HEIGHT; j++, cellIndex++)
{
if([[self.state.boardChanges objectAtIndex:(i*HEIGHT)+j] intValue]==1){
if (CGRectIntersectsRect(rect, cellFrame)) {
NSNumber *currentCell = [self.state.board objectAtIndex:cellIndex];
if (currentCell.intValue == 1)
{
[image1 drawInRect:cellFrame];
}
else if (currentCell.intValue == 0)
{
[image2 drawInRect:cellFrame];
}
}
}
cellFrame.origin.y += cellSize;
}
cellFrame.origin.x += cellSize;
}
}
I tried mixtures of the following without any results:
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
//CGContextAddRect(context, originalRect);
CGContextClip(context);
[image drawInRect:rect];
CGContextRestoreGState(context);

Cocoa/UIKit may discard the previously-drawn content of views whenever it likes. When your view is asked to draw, it must draw everything within the provided rect. You can't just decide to not draw some stuff. (Well, you can do whatever you like, but you get the results that you're seeing.)

Found another solution-> Just take a screenshot and draw it with drawRect as a background.
-(void)createContent{
CGRect rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
UIGraphicsBeginImageContext(rect.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
CGImageRef screenshotRef = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
_backgroundImage = [[UIImage alloc] initWithCGImage:screenshotRef];
CGImageRelease(screenshotRef);
UIGraphicsEndImageContext();
}
- (void)drawRect:(CGRect)rect
{
[_backgroundImage drawInRect:CGRectMake(0.0f, 0.0f, self.bounds.size.width, self.bounds.size.height)];
....
....
....
}
######EDIT
Found a much more sophisticated way to do the above :
http://www.cimgf.com/2009/02/03/record-your-core-animation-animation/
#### EDIT
Another solution would be to use CALayers and only update those parts that change...
####EDIT
OR:
self.layer.contents = (id) [_previousContent CGImage];

Related

Mechanism to assign UIImages their own Rect inside a CALayer?

I have one or more CALayers which represent/fill the whole screen.
I want to fill those CALayers from a grid structure, using a lot of small Rects with images.
What i tried was assigning each Rect its own CALayer and an UIImage.
With over 6000 CALayers the result was very slow. Now i am looking for a way not to use that much CALayer. Maybe only the top level ones.
So ist there a mechanism to assign UIImages their own Rect inside a CALayer ?
Slow / bad code Example:
- (void)createLayer{
CALayer *currentLayer = self.layer;
_layerArray = [[NSMutableArray alloc] initWithCapacity:WIDTH*HEIGHT];
int cellSize = self.bounds.size.width / WIDTH;
double xOffset = 0;
CGRect cellFrame = CGRectMake(0, 0, cellSize, cellSize);
NSUInteger cellIndex = 0;
cellFrame.origin.x = xOffset;
for (int i = 0; i < WIDTH; i++)
{
cellFrame.origin.y = 0;
for (int j = 0; j < HEIGHT; j++, cellIndex++)
{
if([[self.levelState.boardChanges objectAtIndex:(i*HEIGHT)+j] intValue]==1){
{
NSNumber *currentCell = [self.levelState.board objectAtIndex:cellIndex];
CALayer *sublayer = [CALayer layer];
sublayer.frame = cellFrame;
if (currentCell.intValue == 1)
{
[[_layerArray objectAtIndex:(i*HEIGHT)+j ] setContents:(id) [UIImage imageNamed:#"image1.png"].CGImage];
}
else if (currentCell.intValue == 0)
{
[[_layerArray objectAtIndex:(i*HEIGHT)+j ] setContents:(id) [UIImage imageNamed:#"image2.png"].CGImage];
}
[currentLayer addSublayer:sublayer];
[_layerArray addObject:sublayer];
}
}
cellFrame.origin.y += cellSize;
}
cellFrame.origin.x += cellSize;
}
}
I would recommend a very different approach.
subclass a uiview and use it as your background
in your uiview subclass, override
-(void)drawRect:(CGRect)rect
inside, you can draw your uiimages within rectangles you define
[[UIImage imageNamed:#"image1.png"]drawInRect:CGRectMake(0,0,10,10)]; //first square
[[UIImage imageNamed:#"image2.png"]drawInRect:CGRectMake(10,0,10,10)]; // and another right beside it
you can study core graphics more to get a better idea

Resizing an Image Objective C

I am zooming an image in scrollView, I wants the image size resize according to zoom, I know i have to write some code in scrollViewDidEndZooming method.
I am following this link but I think this is not updated ( I got error when implementing the resize Image category), So please direct me in right way, How to resize the image according to zoom scale.
Thanks
#define DOC_WIDTH 505
#define DOC_HEIGHT 802
+(UIImage*)imageResizeWithBorderFromImage:(UIImage*)source
{
//******* Change size
int tnw;
int tnh;
double x_ratio = DOC_WIDTH / source.size.width;
double y_ratio = DOC_HEIGHT / source.size.height;
if (source.size.width <= DOC_WIDTH && source.size.height <= DOC_HEIGHT){
tnw=(int)source.size.width ;
tnh=(int)source.size.height;
}
else if ((x_ratio * source.size.height) < DOC_HEIGHT) {
tnh = (int)(ceil(x_ratio * source.size.height));
tnw = (int)DOC_WIDTH ;
}
else {
tnw = (int)(ceil(y_ratio * source.size.width));
tnh = (int)DOC_HEIGHT;
}
CGSize size = CGSizeMake(tnw, tnh);
//CGSize size = [source size];
UIGraphicsBeginImageContext(size);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
[source drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context,(0/255.f),(162/255.f),(237/255.f), 1.0);
CGContextStrokeRect(context, rect);
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return testImg;
}

UIWebView to UIImage

I tried to capture image from UIWebView using this method but the image contains only visible area of the screen. How do I capture full content of UIWebView including invisible areas, i.e. the entire web page into one single image?
-(UIImage*)captureScreen:(UIView*) viewToCapture{
UIGraphicsBeginImageContext(viewToCapture.bounds.size);
[viewToCapture.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage;
}
Check this out Rendering a UIWebView into an ImageContext
or just use this :) :
(UIImage*) imageFromWebview:(UIWebView*) webview{
//store the original framesize to put it back after the snapshot
CGRect originalFrame = webview.frame;
//get the width and height of webpage using js (you might need to use another call, this doesn't work always)
int webViewHeight = [[webview stringByEvaluatingJavaScriptFromString:#"document.body.scrollHeight;"] integerValue];
int webViewWidth = [[webview stringByEvaluatingJavaScriptFromString:#"document.body.scrollWidth;"] integerValue];
//set the webview's frames to match the size of the page
[webview setFrame:CGRectMake(0, 0, webViewWidth, webViewHeight)];
//make the snapshot
UIGraphicsBeginImageContextWithOptions(webview.frame.size, false, 0.0);
[webview.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//set the webview's frame to the original size
[webview setFrame:originalFrame];
//and VOILA :)
return image;
}
EDIT (from a comment by Vad)
Solution was to call
webView.scalesPageToFit = YES;
in the initialization, and
[webView sizeToFit]
when the page did finish loading.
You are currently capturing only the visible part because you are limiting the image context to what's visible. You should limit it to what's available.
UIView has a scrollView property that has contentSize, telling you what is the size of the web view inside the scroll view. You can use that size to set your image context like this:
-(UIImage*)captureScreen:(UIView*) viewToCapture{
CGSize overallSize = overallSize;
UIGraphicsBeginImageContext(viewToCapture.scrollView.contentSize);
// Save the current bounds
CGRect tmp = viewToCapture.bounds;
viewToCapture.bounds = CGRectMake(0, 0, overallSize.width, overallSize.height);
// Wait for the view to finish loading.
// This is not very nice, but it should work. A better approach would be
// to use a delegate, and run the capturing on the did finish load event.
while (viewToCapture.loading) {
[NSThread sleepForTimeInterval:0.1];
}
[viewToCapture.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Restore the bounds
viewToCapture.bounds = tmp;
return viewImage;
}
EDIT : New answer from me with tested code.
Add below method to capture UIWebViewinto UIImage. It will also capture unvisible area as well.
- (UIImage*)webviewToImage:(UIWebView*)webView
{
int currentWebViewHeight = webView.scrollView.contentSize.height;
int scrollByY = webView.frame.size.height;
[webView.scrollView setContentOffset:CGPointMake(0, 0)];
NSMutableArray* images = [[NSMutableArray alloc] init];
CGRect screenRect = webView.frame;
int pages = currentWebViewHeight/scrollByY;
if (currentWebViewHeight%scrollByY > 0) {
pages ++;
}
for (int i = 0; i< pages; i++)
{
if (i == pages-1) {
if (pages>1)
screenRect.size.height = currentWebViewHeight - scrollByY;
}
if (IS_RETINA)
UIGraphicsBeginImageContextWithOptions(screenRect.size, NO, 0);
else
UIGraphicsBeginImageContext( screenRect.size );
if ([webView.layer respondsToSelector:#selector(setContentsScale:)]) {
webView.layer.contentsScale = [[UIScreen mainScreen] scale];
}
//UIGraphicsBeginImageContext(screenRect.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor blackColor] set];
CGContextFillRect(ctx, screenRect);
[webView.layer renderInContext:ctx];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (i == 0)
{
scrollByY = webView.frame.size.height;
}
else
{
scrollByY += webView.frame.size.height;
}
[webView.scrollView setContentOffset:CGPointMake(0, scrollByY)];
[images addObject:newImage];
}
[webView.scrollView setContentOffset:CGPointMake(0, 0)];
UIImage *resultImage;
if(images.count > 1) {
//join all images together..
CGSize size;
for(int i=0;i<images.count;i++) {
size.width = MAX(size.width, ((UIImage*)[images objectAtIndex:i]).size.width );
size.height += ((UIImage*)[images objectAtIndex:i]).size.height;
}
if (IS_RETINA)
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
else
UIGraphicsBeginImageContext(size);
if ([webView.layer respondsToSelector:#selector(setContentsScale:)]) {
webView.layer.contentsScale = [[UIScreen mainScreen] scale];
}
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor blackColor] set];
CGContextFillRect(ctx, screenRect);
int y=0;
for(int i=0;i<images.count;i++) {
UIImage* img = [images objectAtIndex:i];
[img drawAtPoint:CGPointMake(0,y)];
y += img.size.height;
}
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
} else {
resultImage = [images objectAtIndex:0];
}
[images removeAllObjects];
return resultImage;
}
Also add these macro for checking if iOS is retina display
#define IS_RETINA ([[UIScreen mainScreen] respondsToSelector:#selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0))

show image in a CGContextRef

What i am doing, i downloded a code for calender now i want to show images on its tiles(for date).
What i am trying shows in code
- (void)drawTextInContext:(CGContextRef)ctx
{
CGContextSaveGState(ctx);
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
CGFloat numberFontSize = floorf(0.3f * width);
CGContextSetFillColorWithColor(ctx, kDarkCharcoalColor);
CGContextSetTextDrawingMode(ctx, kCGTextClip);
for (NSInteger i = 0; i < [self.text length]; i++) {
NSString *letter = [self.text substringWithRange:NSMakeRange(i, 1)];
CGSize letterSize = [letter sizeWithFont:[UIFont boldSystemFontOfSize:numberFontSize]];
CGContextSaveGState(ctx); // I will need to undo this clip after the letter's gradient has been drawn
[letter drawAtPoint:CGPointMake(4.0f+(letterSize.width*i), 0.0f) withFont:[UIFont boldSystemFontOfSize:numberFontSize]];
if ([self.date isToday]) {
CGContextSetFillColorWithColor(ctx, kWhiteColor);
CGContextFillRect(ctx, self.bounds);
} else {
// CGContextDrawLinearGradient(ctx, TextFillGradient, CGPointMake(0,0), CGPointMake(0, height/3), kCGGradientDrawsAfterEndLocation);
CGDataProviderRef dataProvider = CGDataProviderCreateWithFilename("left-arrow.png");
CGImageRef image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO, kCGRenderingIntentDefault);
//UIImage* image = [UIImage imageNamed:#"left-arrow.png"];
//CGImageRef imageRef = image.CGImage;
CGContextDrawImage(ctx, CGRectMake(8.0f+(letterSize.width*i), 0.0f, 5, 5), image);
//im.image=[UIImage imageNamed:#"left-arrow.png"];
}
CGContextRestoreGState(ctx); // get rid of the clip for the current letter
}
CGContextRestoreGState(ctx);
}
In else condition i want to show images on the tile so for that i am converting image objects in the CGImageRef.
Please help me.
I am not sure this would be done in same manner or in other manner please suggest your way to do this.
Thanx a lot.
The file-path of the image seems to problematic. You can retrieve the correct path with the NSBundle-methods. Also you're leaking a lot of memory, because you don't release your images and data-providers. To make a long story short, try this:
[[UIImage imageNamed:#"left-arrow.png"] drawInRect:...]
or even simpler:
[[UIImage imageNamed:#"left-arrow.png"] drawAtPoint:...]

Using CAGradientLayer as backround while also drawing in context with drawRect:

I'm writing an app that plots some data into a simple graph and sometimes want to draw out the scale in the background. To do this I have a UIView subclass which acts as the graph background and simply use the drawRect: method to draw the scale (data elements will get added as subviews to this view, since I want the user to be able to interact with them).
However, I also want a gradient background color and have used CAGradientLayer for this purpose (as suggested in this thread). But when I add this as a sublayer, the gradient background appears, but nothing I do in drawRect: shows!
I'm sure I'm missing something simple or have misunderstood how to use CAGradientLayer or something, so any help or suggestions is appreciated!
This is the relevant code in my UIView subclass:
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Create a gradient background
CAGradientLayer *gradientBackground = [CAGradientLayer layer];
gradientBackground.frame = self.bounds;
gradientBackground.colors = [NSArray arrayWithObjects:(id)[[UIColor grayColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
[self.layer insertSublayer:gradientBackground atIndex:0];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Get the graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
// Clear the context
CGContextClearRect(context, rect);
// Call actual draw method
[self drawInContext:context];
}
-(void)drawInContext:(CGContextRef)context {
CGFloat step;
// Draw Y scale
if (displayYScale) {
CGContextSetRGBStrokeColor(context, 0, 0, 0, kScaleLineAlpha);
if (yAxisScale.mode == FCGraphScaleModeData) {
step = (self.frame.size.height-(yAxisScale.padding*2))/yAxisScale.dataRange.maximum;
for (int i = 0; i <= yAxisScale.dataRange.maximum; i++) {
CGContextMoveToPoint(context, 0.0f, (step*i)+yAxisScale.padding);
CGContextAddLineToPoint(context, self.frame.size.width, (step*i)+yAxisScale.padding);
}
} else if (yAxisScale.mode == FCGraphScaleModeDates) {
int units = (int)[yAxisScale units];
step = (self.frame.size.height-(yAxisScale.padding*2))/units;
for (int i = 0; i <= units; i++) {
CGContextMoveToPoint(context, 0.0f, (step*i)+yAxisScale.padding);
CGContextAddLineToPoint(context, self.frame.size.width, (step*i)+yAxisScale.padding);
}
}
CGContextStrokePath(context);
}
// Draw X scale
if (displayXScale) {
CGContextSetRGBStrokeColor(context, 0, 0, 255, kScaleLineAlpha);
if (xAxisScale.mode == FCGraphScaleModeData) {
step = (self.frame.size.width-(xAxisScale.padding*2))/xAxisScale.dataRange.maximum;
for (int i = 0; i <= xAxisScale.dataRange.maximum; i++) {
CGContextMoveToPoint(context, (step*i)+xAxisScale.padding, 0.0f);
CGContextAddLineToPoint(context, (step*i)+xAxisScale.padding, self.frame.size.height);
}
} else if (xAxisScale.mode == FCGraphScaleModeDates) {
int units = (int)[xAxisScale units];
step = (self.frame.size.width-(xAxisScale.padding*2))/units;
for (int i = 0; i <= units; i++) {
CGContextMoveToPoint(context, (step*i)+xAxisScale.padding, 0.0f);
CGContextAddLineToPoint(context, (step*i)+xAxisScale.padding, self.frame.size.height);
}
}
CGContextStrokePath(context);
}
}
Thanks!
/Anders
Since you're drawing anyway, you can also just draw your own gradient:
// the colors
CGColorRef topColor = [[UIColor grayColor] CGColor];
CGColorRef bottomColor = [[UIColor whiteColor] CGColor];
NSArray *colors =
[NSArray arrayWithObjects: (id)topColor, (id)bottomColor, nil];
CGFloat locations[] = {0, 1};
CGGradientRef gradient =
CGGradientCreateWithColors(CGColorGetColorSpace(topColor),
(CFArrayRef)colors, locations);
// the start/end points
CGRect bounds = self.bounds;
CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y);
CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds));
// draw
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawLinearGradient(context, gradient, top, bottom, 0);
CGGradientRelease(gradient);
Sublayers will show above anything done in drawRect. Add another sublayer on top of the gradient layer that uses your view as the delegate, in that layers drawLayer:inContext: callback do what you're currently doing in drawRect.