Questions regarding drawing in iOS - iphone

I want to create a little navigation bar on the bottom of my iPhone screen where I basically just draw 5 rectangles next to each other. However, only the active page should have the opacity of 1.0 and others should be slightly transparent (alpha=0.4). That is what I already have.
Now my questions:
How do I change the opacity of the individual elements of my navigation ? Do I have to redraw the whole thing whenever something changes ? So I would have global variables called nav1Opacity,nav2Opacity...nav5Opacity, change them when the navigation changes and redraw the whole thing ? If so,
How do I clear what I have drawn before ? Do i create the rectangles as CGMutablePathRef()s and store them in an array and clear them all ?
I have very little experience with drawing, so I am a little lost there. I have read the documentation of Quartz2d and contexts, but still, as I mentioned I have not fully figured out how it works.
Here is some code I use:
-(void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
//save state
CGContextSaveGState(context);
//NAV1
CGMutablePathRef nav1 = CGPathCreateMutable();
CGPathAddRect(nav1, NULL, CGRectMake(0 , 15, 64, 10));
UIColor *blueColor = UIColorFromRGB(0x35BFE5,0.1);
CGColorRef bC = [blueColor CGColor];
[colorArray addObject:(__bridge id)bC];
[navArray addObject:(__bridge id)nav1];
CGPathRelease(nav1);
/*
*
*
... I do this for all 5 navigation elements
*
*
*/
//then I go through all my rectangles and add/fill them
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
// restore to last saved context state
CGContextRestoreGState(context);
}
//and this is how I redraw
-(void)updateActiveNav{
[navArray removeAllObjects];
[colorArray removeAllObjects];
[self setNeedsDisplay];
}

If you actually draw the interface, you will have to redraw it whenever it changes, at least the rectangles that change. You can reuse CGPaths, but they aren't graphical objects on screen, they are just instructions on how to draw shapes, so you will have to draw everything again.
That being said, you can use individual UIViews instead, which represent onscreen objects, and you can change their opacity, which will reflect on screen.

This is the problem:
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
You are added the path to your context, then setting a fill color, then filling it. Then without restoring your context you're doing it again so your filling the previous path and the new one. Its not the drawing from the last drawRect its the drawing from here. Try something like below so that after you fill the path you reset the context and draw the next block by itself instead of both the 1st and 2nd etc.
for(int i=0;i<[navArray count];i++){
CGContextSaveGState(context);
// Add Path, Fill
CGContextRestoreGState(context);
}

Related

Emboss effect in Core Graphics

I am again here with two Question, both inter-related
I want to draw embossed lines with core graphics.
Can any one suggest me how to give inner shadows to line drawn on touch events?
Even for drawing outer shadows.
Shadow drawn overlaps in between. and line drawn with colors other than black is like worm..
Can any one help me?
Following image illustrates what I mean to explain for Question 2:
Shadows creates are not even. They darken at some points
I am adding the code that I am using to draw lines..
for (int i=0; i<[currentPath count]; i++)
{
CGPoint mid1 = [[self midPoint:[currentPath objectAtIndex:i+1] :[currentPath objectAtIndex:i]] CGPointValue];
CGPoint mid2 = [[self midPoint:[currentPath objectAtIndex:i+2] :[currentPath objectAtIndex:i+1]] CGPointValue];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, [[currentPath objectAtIndex:i+1] CGPointValue].x, [[currentPath objectAtIndex:i+1] CGPointValue].y, mid2.x, mid2.y);
CGContextSetShadow(context, CGSizeMake(-2, -2), 3);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetStrokeColorWithColor(context,[color CGColor]);
CGContextSetLineWidth(context, linewidth);
CGContextStrokePath(context);
i+=2;
}
I found my solution..
Problem was very silly...
I was stoking path on every iteration which was creating the issue.. Now I can draw even with alpha less then 1..
CGContextStrokePath(context);
This line goes outside for loop.. And all is working fine now :)
For your overlapping shadows, you want a transparency layer to composite them first. See Transparency Layers in the Quartz 2D Programming Guide.
It looks like you are drawing the path by using a series of circles.
The trouble is that you have set the shadow on the individual dots, and that's why you are getting the strange effects.
A possible solution - dont put a shadow on the dots, put on the path: duplicate the line that you have drawn, draw it in a different colour, offset it and put in under your actual line.
Alternatively, if you are using layers - have a look at shadow paths.

CGLayer and Anti-aliased CGPaths

I am drawing several CGPaths in a Cocoa view in the drawRect method on an iPad. I started out drawing them straight to the UIGraphicsGetCurrentContext() context, but performance went south when my paths got really long. Based on several other questions, I started looking into using CGLayers.
So what I do now is to render a path inside of the CGContextRef I get from calling CGLayerGetContext. Here's the basic outline of what I'm doing:
// rect comes from the drawRect: method
layer = CGLayerCreateWithContext(context, rect.size, NULL);
layerContext = CGLayerGetContext(layer);
CGContextSetLineCap(layerContext, kCGLineCapRound);
// Set the line styles
CGContextSetLineJoin(layerContext, kCGLineJoinRound);
CGContextSetLineWidth(layerContext, 1.0);
CGContextSetStrokeColorWithColor(layerContext, [UIColor blackColor].CGColor);
// Create a mutable path
path = CGPathCreateMutable();
// Move to start point
//...
for (int i = 0; i < points.count; i++) {
// compute controlPoint and anchorPoint
//...
CGPathAddQuadCurveToPoint(path,
nil,
controlPoint.x,
controlPoint.y,
anchorPoint.x,
anchorPoint.y);
}
// Stroke the path
CGContextAddPath(layerContext, path);
CGContextStrokePath(layerContext);
// Add the layer to the main context
CGContextDrawLayerAtPoint(context, CGPointZero, layer);
Now, I get good performance drawing, but my lines are extremely jagged and do not seem to be anti-aliased. I've tried adding
CGContextSetShouldAntialias(layerContext, YES);
CGContextSetAllowsAntialiasing(layerContext, YES);
CGContextSetInterpolationQuality(layerContext, kCGInterpolationHigh);
to the code above to no avail. I've even set the anti-aliasing properties on the main context, with no change. I took screen shots of the same code results, but with the second image being the image created from drawing in the CGLayer. As you can see, it is really jagged, despite being the same code, just drawing into a layerContext. How can I get the lines in the CGLayer to be smooth?
I found the reason the second image is not anti-aliased is because there was nothing to anti-alias against. The background was empty, and so anti-aliasing didn't work. If you make a big rectangle over the bounds of the view that is entirely transparent, the line will draw with anti-aliasing. However, performance is killed. Better not to go this route.

iPhone - How to draw something in a view

I'v got this piece of code :
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGFloat colors[] = {
1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
};
CGGradientRef gradientRef = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors) / (sizeof(colors[0]) * 4));
CGColorSpaceRelease(rgb);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = theCell.backgroundView.bounds;
CGPoint start = CGPointMake(rect.origin.x, 0);
CGPoint end = CGPointMake(rect.origin.x, rect.size.height/2);
CGContextDrawLinearGradient(context, gradientRef, start, end, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
And I wonder how I can make it draw into a designated view and a clipping rect passed in parameters to my function. Tip: I don't mind about drawRect, I'm not subclassing anything.
Tip 2: I don't want to insert any layers that I won't be able to remove later.
Tip 3: This piece of code does not draw anything that my eyes could see..... :-( Missing a graphic port ?
Tip 4: I'd like to erase the draw simply changing the background color, and it's done...
CGContextRef context = UIGraphicsGetCurrentContext(); will get the graphics context for the current view, so if you call this in the drawRect: method of a UIView it will draw.
I don't understand what you mean by:
I don't mind about drawRect, I'm not
subclassing anything
but if you want to do custom drawing you must either override the drawRect: method or use layers. To use layers you would call CGContextRef context = CGLayerGetContext(theLayer); instead of CGContextRef context = UIGraphicsGetCurrentContext();.
Ok, so I looked at the documentation and it says that you can get a CGContextRef by calling UIGraphicsGetCurrentContext() from the drawRectMethod. Here's what it says: In an iOS application, you set up a UIView object to draw to and implement the drawRect: method to perform drawing. Before calling your custom drawRect: method, the view object automatically configures its drawing environment so that your code can start drawing immediately. As part of this configuration, the UIView object creates a graphics context (a CGContextRef opaque type) for the current drawing environment. You obtain this graphics context by calling the UIKit function UIGraphicsGetCurrentContext. You save and restore graphics contexts using the functions UIGraphicsPushContext and UIGraphicsPopContext.
You can create custom graphics context objects in situations where you want to draw somewhere other than your view. For example, you may want to capture a series of drawing commands and use them to create an image or a PDF file. To create the context, you use the CGBitmapContextCreate or CGPDFContextCreate function. After you have the context, you can pass it to the drawing functions needed to create your content.
When creating custom contexts, the coordinate system for those contexts is different from the native coordinate system used by iOS. Instead of the origin being in the upper-left corner of the drawing surface, it is in the lower-left corner and the axes point up and to the right. The coordinates you specify in your drawing commands must take this into consideration or the resulting image or PDF file may appear wrong when rendered. See “Creating a Bitmap Graphics Context” and “Creating a PDF Graphics Context” for details on using CGBitmapContextCreate and CGPDFContextCreate.
Might I recommend you look into the CAGradientLayer, and add it as a sublayer of your view? Lots simpler, and it will be hardware accelerated which matters for table cells.
Example stolen partly from here:
http://tumbljack.com/post/188089679/gpu-accelerated-awesomeness-with-cagradientlayer
#import <QuartzCore/QuartzCore.h> // Also import this framework
......
CAGradientLayer grad = [CAGradientLayer layer];
UIColor *colorOne = [UIColor colorWithHRed:1.0f Green:1.0f Blue:1.0f alpha:1.0f];
UIColor *colorTwo = [UIColor colorWithHRed:0.0f Green:0.0f Blue:0.0f alpha:1.0f];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, nil];
grad.colors = colors;
CGRect rect = theCell.backgroundView.bounds;
rect.size.height = rect.size.height / 2;
grad.frame = rect;
[self.layer addsublayer:grad]
You may have to play with the colors a bit, not sure if you had the gradient tilted or not...

How to use a CGBlendMode on a UIView that scrolls above a fixed background?

Our main UIView is a UIScrollView with a fixed background image (very common, obviously). In that scrollView, we have several UIViews that hold content and scroll up and down as the user scrolls (also common). Those UIViews each have their own background, a simple gradient from white to black.
The goal is to have the background gradient of those (inner) UIViews be partially opaque AND use a CGBlendMode other than "kCGBlendModeNormal" (specifically, "kCGBlendModeOverlay"). You should be able to see through to the "parent" scrollView’s fixed background image as the UIViews scroll up and down above it.
- (void)drawRect:(CGRect)rect {
gradientStart = [UIColor colorWithRed:1 green:1 blue:1 alpha:1.0];
gradientEnd = [UIColor colorWithRed:0 green:0 blue:0 alpha:1.0];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[2] = { 0.0f, 1.0f };
NSArray *colors = [NSArray arrayWithObjects:(id)gradientStart.CGColor, (id)gradientEnd.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations);
CGColorSpaceRelease(colorSpace);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAlpha(context, 0.50); //this works!
CGContextSetBlendMode(context, kCGBlendModeOverlay); //doesn’t seem to do anything!
CGContextClearRect(context, rect);
CGPoint startPoint, endPoint;
startPoint.x = 0.0;
startPoint.y = 0.0;
endPoint.x = 0.0;
endPoint.y = rect.size.height;
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
[super drawRect:rect];
}
Everything works as expected except the CGContextSetBlendMode, which is ignored. We can't seem to find a way to change the blendMode of a UIView relative to what is behind it, the same way you can with alpha. Please note that this is different than building up multiple layers in a SINGLE UIView; in that case, this technique does change the blendMode of the layers "on top". We want to see through to the parent scrollView's fixed background image (as we scroll the child view up and down above it), with both an alpha and an overlay blend applied.
Here's an image showing the issue: http://img2.sbck.us/blendmode.png
Thanks in advance for your help!
I believe what you want is not possible with your current setup. On iOS, it is simply not possible for the blend mode of a view to have an effect on the stuff that is drawn under the view. You would have to draw the scroll view's background and the gradients in the same view.
This is possible, at least with two image views. It might even be possible with more general views. The approach is to implement drawRect in the parent view, and do as follows:
Determine the rect for the foreground view.
Convert the rect in the foreground view to a rect in the background view.
Begin a new graphics context.
Draw the background with the proper blend mode.
Draw the foreground with the proper blend mode.
Extract the image from the graphics context.
End the graphics context.
Use the extracted image accordingly.
This allows a foreground image to blend with a background image.
Seems like you could do this by setting the 'compositingFilter' property of your view's CALayer. The comment in CALayer.h says "A filter object used to composite the layer with its (possibly filtered) background. Default value is nil, which implies source-over compositing."
Alas, CoreImage which provides the filters is not (officially) available on iOS.
I guess your other alternative would be to use OpenGL. You could still use UIView with OpenGL after a fashion by rendering your UIView's into images which could then be used a textures.

Saving and restoring CGContext

I'm trying to save and restore a CGContext to avoid doing heavy drawing computations for a second time and I'm getting the error <Error>: CGGStackRestore: gstack underflow.
What am I doing wrong? What is the correct way to do this?
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
if (initialized) {
CGContextRestoreGState(context);
//scale context
return;
}
initialized = YES;
//heavy drawing computation and drawing
CGContextSaveGState(context);
}
I think you might be misinterpreting what CGContextSaveGState() and CGContextRestoreGState() do. They push the current graphics state onto a stack and pop it off, letting you transform the current drawing space, change line styles, etc., then restore the state to what it was before you set those values. It does not store drawing elements, like paths.
From the documentation on CGContextSaveGState():
Each graphics context maintains a
stack of graphics states. Note that
not all aspects of the current drawing
environment are elements of the
graphics state. For example, the
current path is not considered part of
the graphics state and is therefore
not saved when you call the
CGContextSaveGState() function.
The graphics state stack should be reset at the start of your drawRect:, which is why you're getting errors when you try to pop a graphics state off the stack. Since you hadn't pushed one on, there was none to pop off. All of this means that you can't store your drawing as graphics state on the stack, then restore it later.
If all you are worried about is caching your drawing, that is done for you by the CALayer that backs your UIView (on the iPhone). If all you are doing is moving your view around, it won't be redrawn. It will only be drawn if you manually tell it to do so. If you do have to update part of the drawing, I recommend splitting the static elements off into their own views or CALayers so that only the part that changes is redrawn.
Don't you want to Save first and then Restore? If you are restoring before a save, then there is no context to restore, and you'd get an underflow.
Here is the way I have used it:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextClipToRect(context, CGRectMake(stripe[i][8], stripe[i][9], stripe[i][10], stripe[i][11]));
CGContextDrawLinearGradient(context, gradient, CGPointMake(15, 5), CGPointMake(15, 25), 0);
CGContextRestoreGState(context);
or:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextAddRect(context, originalRect);
CGContextClip(context);
[self drawInRect:rect];
CGContextRestoreGState(context);
Maybe you are trying to do something else.
.. Based on your code !,
It seems that you are Restoring the Context before Saving it.
First Thing First:
Create a context
Save its state, aka push
Do some stuff with the context
Restore the context aka Pop
General rule for each Store(push) there must be Restore(pop)
Release the context when you are done with it !, This refers to those context/s which they have CGCreate, CGCopy,
Sample code:
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
// save context
CGContextSaveGState(ctx);
// do some stuff
CGContextSetRGBStrokeColor(ctx, 1.0, 0.5, 0.5, 1.0);
// drawing vertical lines
CGContextSetLineWidth(ctx, 1.0);
for (int i = 0; i < [columns count]; i++) {
CGFloat f = [((NSNumber*) [columns objectAtIndex:i]) floatValue];
CGContextMoveToPoint(ctx, f+(i*20.5), 0.5);
CGContextAddLineToPoint(ctx, f+(i*20.5), self.bounds.size.height);
}
// restore context
CGContextRestoreGState(ctx);
// do some other stuff
// drawing hozizontal lines
CGContextSetLineWidth(ctx, 1.0);
CGContextSetRGBStrokeColor(ctx, 0.12385, 0.43253, 0.51345, 1.0);
for (int i = 0; i < [columns count]; i++) {
CGFloat f = [((NSNumber*) [columns objectAtIndex:i]) floatValue];
CGContextMoveToPoint(ctx, 0.5, f+(i*20.5));
CGContextAddLineToPoint(ctx,self.bounds.size.width,f+(i*20.5));
}
CGContextStrokePath(ctx);
}
// No context CGContextRelease , since we never used CGContextCreate