Change CGRect or Override CGRect - ios

I am drawing a radial gradient in quartz.
I created a new file and put the code in drawRect as
CGContextRef ctx = UIGraphicsGetCurrentContext();
//Draw the gray Gradient
CGFloat BGLocations[2] = { 0.0, 1.0 };
CGFloat BgComponents[8] = { 0.25, 0.25, 0.25 , 1.0, // Start color
0.11, 0.11, 0.11, 1.0 }; // Mid color and End color
CGColorSpaceRef BgRGBColorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef bgRadialGradient = CGGradientCreateWithColorComponents(BgRGBColorspace, BgComponents, BGLocations, 2);
CGPoint startBg = CGPointMake(250, 250);
CGFloat endRadius= 250;
CGContextDrawRadialGradient(ctx, bgRadialGradient, startBg, 0, startBg, endRadius, kCGGradientDrawsAfterEndLocation);
CGColorSpaceRelease(BgRGBColorspace);
CGGradientRelease(bgRadialGradient);
Now i am making a UIView and initializing it with the RadialGradientView created through the website but i need to change the endRadius according to the different implementations.
IS there a way to do that.
I am initializing the UIView with Frame.
UIView* backgroundView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
UIView* gradientView = [[RadialGradientView alloc] initWithFrame: backgroundView.bounds];
So when i am trying to access endRadius property of gradientView, Its not showing up.

Declare the endRadius as a property in your subclassed yourView.h file as
#property(assign) CGFloat endRadius;
and you can access this property like:
yourView.endRadius=40;
and in the code , change this line:
CGContextDrawRadialGradient(ctx, bgRadialGradient, startBg, 0, startBg, endRadius, kCGGradientDrawsAfterEndLocation);
to
CGContextDrawRadialGradient(ctx, bgRadialGradient, startBg, 0, startBg, self.endRadius, kCGGradientDrawsAfterEndLocation);
or
CGContextDrawRadialGradient(ctx, bgRadialGradient, startBg, 0, startBg, _endRadius, kCGGradientDrawsAfterEndLocation);

Related

Black to White radial gradient is giving whole black screen

It should be very simple to create a black to white radial gradient using Core Graphics, but even after many tries I am not able to figure where I am going wrong.
I want something like this:
This is my code:
CGFloat radius = shapeRect.size.width/2.0;
CGPoint center = CGPointMake(shapeRect.origin.x+shapeRect.size.width/2, shapeRect.origin.y+shapeRect.size.height/2);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate(NULL, shapeRect.size.width, shapeRect.size.height, 8, shapeRect.size.width*4, colorSpace, kCGImageAlphaNone);
CGFloat components[] = {0.0, 1.0};
CGFloat locations[] = {0.0, 1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
CGContextDrawRadialGradient(context, gradient, center, 10, center, radius, 0);
CGImageRef img = CGBitmapContextCreateImage(context);
[maskedView setImage:[UIImage imageWithCGImage:img]];
CGGradientRelease(gradient);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
ShapeRect here has self.view bounds.
I am getting a whole black screen with no gradient as output.
I am using iPhone 7 simulator.
Your suggestions can save my day
Thank you.
Please check this snippet. You can change components and locations values to get what you want and to understand how the drawing works. Refer to CGGradientCreateWithColorComponentsdescription
#import "TestView.h"
#implementation TestView {
CGGradientRef _gradient;
}
-(void)drawRect:(CGRect)rect
{
CGFloat radius = self.frame.size.width / 2.0;
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGContextRef context = UIGraphicsGetCurrentContext();
CGGradientRef gradient = [self createGradient];
CGContextDrawRadialGradient(context, gradient, center, 10, center, radius, kCGGradientDrawsBeforeStartLocation);
}
-(CGGradientRef)createGradient
{
if (!_gradient) {
CGFloat components[] = { 0.0, 1.0, 0.5, 0.5, 1.0, 0.3 };
CGFloat locations[] = { 0.0, 0.6, 1.0 };
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
_gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 3);
CGColorSpaceRelease(colorSpace);
}
return _gradient;
}
- (void)layoutSubviews
{
self.backgroundColor = [UIColor whiteColor];
self.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.layer.borderWidth = 1.;
}
#end
Result:

UIView with transparent rounded rectangles?

First of all, i've looked for and found this:
Cut transparent hole in UIView
Putting multiple transparent rectangles on my view, but now I need these rectangles to be rounded, like this:
http://postimg.org/image/ozxr0m5sh/
So I mixed some codes I found and did that, but for some reason it only works for the first rectangle, here is the full code for the custom view:
(if you take off the method "addRoundedRect..." call, it works for all the rects).
- (void)drawRect:(CGRect)rect{
[backgroundColor setFill];
UIRectFill(rect);
// clear the background in the given rectangles
for (NSValue *holeRectValue in rectsArray) {
CGRect holeRect = [holeRectValue CGRectValue];
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
CGContextRef context = UIGraphicsGetCurrentContext();
if( CGRectIntersectsRect( holeRectIntersection, rect ) )
{
addRoundedRectToPath(context, holeRectIntersection, 6, 6);
CGContextClosePath(context);
CGContextClip(context);
CGContextClearRect(context, holeRectIntersection);
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextFillRect( context, holeRectIntersection);
}
}
static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight){
float fw, fh;
if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM (context, ovalWidth, ovalHeight);
fw = CGRectGetWidth (rect) / ovalWidth;
fh = CGRectGetHeight (rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
CGContextClosePath(context);
CGContextRestoreGState(context);
}
In iOS it's generally better not to use drawRect. It tends to be slower than other methods of rendering your content.
I don't know the specific answer to your question about why you can't punch multiple round-edged holes in your view, but I would suggest a different approach. than mucking around with CGContexts in your drawRect method.
Set up your view with whatever content you need. Then create a CAShapeLayer the same size as your view and fill it with a path (shape layers want a CGPath, but you can create a UIBezierPath and get a CGPath from that.) Attach the shape layer as the mask of your view's layer. (The shape you put in your mask layer defines the opaque parts of your view, though, so you have to create a shape that fills the mask and then punch holes in it with other shapes.
here I had a crack. UserInteraction must be turned off on this view in order to pass touch events through, so don't add any subviews here unless you want them to ignore touch as well..
interface:
#import "AslottedView.h"
//header is empty but for #import <UIKit/UIKit.h>
//this is a subclass on UIView
#interface AslottedView ()
//helper function, nb resultantPath will rerquire releasing(create in func name)
CGMutablePathRef CGPathCreateRoundedRect(CGRect rect, CGFloat cornerRadius);
#end
implementation:
#implementation AslottedView
{
CGRect slots[4];
}
CGMutablePathRef CGPathCreateRoundedRect(CGRect rect, CGFloat cornerRadius){
CGMutablePathRef result = CGPathCreateMutable();
CGPathMoveToPoint(result, nil, CGRectGetMinX(rect)+cornerRadius, (CGRectGetMinY(rect)) );
CGPathAddArc(result, nil, (CGRectGetMinX(rect)+cornerRadius), (CGRectGetMinY(rect)+cornerRadius), cornerRadius, M_PI*1.5, M_PI*1.0, 1);//topLeft
CGPathAddArc(result, nil, (CGRectGetMinX(rect)+cornerRadius), (CGRectGetMaxY(rect)-cornerRadius), cornerRadius, M_PI*1.0, M_PI*0.5, 1);//bottomLeft
CGPathAddArc(result, nil, (CGRectGetMaxX(rect)-cornerRadius), (CGRectGetMaxY(rect)-cornerRadius), cornerRadius, M_PI*0.5, 0.0, 1);//bottomRight
CGPathAddArc(result, nil, (CGRectGetMaxX(rect)-cornerRadius), (CGRectGetMinY(rect)+cornerRadius), cornerRadius, 0.0, M_PI*1.5, 1);//topRight
CGPathCloseSubpath(result);
return result;
}
CGColorRef fillColor(){
return [UIColor whiteColor].CGColor;
//or whatever..
}
-(instancetype )initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.userInteractionEnabled = NO;
self.backgroundColor = [UIColor clearColor];
//quick loop to make some rects
CGRect rct = CGRectMake(10.0, 10.0, 40.0, 40.0);
CGFloat margin = 30.0;
for (NSInteger i = 0; i < 4; i ++) {
slots[i] = CGRectOffset(rct, ((rct.size.width+margin) * ((i%2==0)? 1.0 : 0.0)) , ((rct.size.height+margin) * ((i>1)? 1.0:0.0) )) ;
}
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctx, fillColor() );
CGContextAddRect(ctx, self.bounds);
for (NSInteger i = 0; i < 4; i ++) {
CGMutablePathRef roundRect = CGPathCreateRoundedRect(slots[i], 5.0);
CGContextAddPath(ctx, roundRect);
CGPathRelease(roundRect);
}
CGContextEOFillPath(ctx);
}
#end

CGContext clips more than it should

I have a UIView that has 3 custom subviews whose class is called (for now) LVStemp and that represent nodes in a tree. An LVStemp's frame is larger than the node itself because it will also include some drawing that lies outside the node (not coded yet). LVStemp has a property called nodeFrame that represents the frame of the node itself, in the main view's coordinates.
I'm trying to draw the nodes and fill them with a gradient. But the gradient is clipping in a way that is not what I intend. The result looks like this:
The top node is filled correctly but the bottom two are not.
Setting up, in the UIView's superview:
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0, 100, 350, 350)];
[self.view addSubview:myView];
LVStemp *node1 = [[LVStemp alloc] init];
node1.frame = CGRectMake(75, 0, 50, 50);
node1.nodeFrame = node1.frame;
node1.backgroundColor = [UIColor clearColor];
[myView addSubview:node1];
LVStemp *node2 = [[LVStemp alloc] init];
node2.frame = CGRectMake(0, 25, 100, 175);
node2.nodeFrame = CGRectMake(0, 150, 50, 50);
node2.backgroundColor = [UIColor clearColor];
[myView addSubview:node2];
LVStemp *node3 = [[LVStemp alloc] init];
node3.frame = CGRectMake(100, 25, 100, 175);
node3.nodeFrame = CGRectMake(150, 150, 50, 50);
node3.backgroundColor = [UIColor clearColor];
[myView addSubview:node3];
}
My code in LVStemp.h:
#interface LVStemp : UIView
#property (nonatomic) CGRect nodeFrame;
#end
My code in LVStemp.m:
#implementation LVStemp
- (void)drawRect:(CGRect)rect
{
// Build nodeBounds (= nodeFrame converted to self's coordinate system)
CGRect nodeBounds = [self convertRect:self.nodeFrame fromView:self.superview];
// Get CGContextRef
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw node with gradient
[self drawEllipseInRect:nodeBounds withGradient:context];
}
- (void)drawEllipseInRect:(CGRect)rect withGradient:(CGContextRef)context
{
UIGraphicsPushContext(context);
CGFloat colors[] = {
0.1, 0.8, 1.0, 1.0,
0.1, 0.4, 0.9, 1.0
};
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
CGContextSaveGState(context);
CGContextAddEllipseInRect(context, rect);
CGContextClip(context);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxX(rect));
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(context);
CGContextAddEllipseInRect(context, rect);
CGContextDrawPath(context, kCGPathStroke);
UIGraphicsPopContext();
}
#end
(drawEllipseInRect:withGradient: is adapted from here.)
And here's what happens if I comment out the clipping part:
//CGContextAddEllipseInRect(context, rect);
//CGContextClip(context);
Any suggestions?
The context is clipped correct, but you are drawing the gradient at the wrong Position:
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxX(rect));
Change that to use maxY:
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
But as you might have noticed, the strokes are being clipped. That's because they are drawn centered on the given outline. You might consider calling it with some inset:
[self drawEllipseInRect:CGRectInset(nodeBounds, lineWidth/2, lineWidth/2) withGradient:context];

iOS UINavigation "Back" button Bezier Path

In my app I have custom gradients I use to stylize all instances of UIBarButtonItem, accomplished by creating a gradient, clipping the gradient with a rounded rectangle Bezier path, then creating a resizable UIImage, like so:
CGRect rect = CGRectMake(0, 0, 44, 44);
CGSize size = rect.size;
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
size_t gradientNumberOfLocations = 2;
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef btncontext = UIGraphicsGetCurrentContext();
CGFloat btngradientLocations[2] = { 0.2, 1.0 };
CGFloat btngradientComponents[8] = { 0.10, 0.10, 0.10, 1.0, 0.05, 0.05, 0.05, 1.0 };
CGGradientRef btngradient = CGGradientCreateWithColorComponents (colorspace, btngradientComponents, btngradientLocations, gradientNumberOfLocations);
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:4.0f];
[path addClip];
CGContextDrawLinearGradient(btncontext, btngradient, CGPointMake(0, 0), CGPointMake(0, size.height), 0);
UIImage *btnimage = UIGraphicsGetImageFromCurrentImageContext();
CGGradientRelease(btngradient);
UIGraphicsEndImageContext();
CGColorSpaceRelease(colorspace);
UIImage *navButton = [btnimage resizableImageWithCapInsets:UIEdgeInsetsMake(22.0, 8.0, 22.0, 8.0)];
[[UIBarButtonItem appearance] setBackgroundImage:navButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
This works just fine, and I am developing a similar method to stylize the back button via -setBackButtonBackgroundImage, however I have a question:
I would like to know if anyone has knows a proper way to create a UIBezierPath object that represents the outline of the back button, such that I can create a gradient and programmatically clip the gradient to a back button shape (just like in the example) without having to use a PNG or some such resource.
Any help is appreciated, thank you.

self.bounds confusion

In this code
CGRect contextRect = self.bounds;
will refer which bound? rectangle, imageRect, or whole iOS view.
I am trying to manipulate image using quartz2D, i created this code by looking different examples, and code written between // is from Apple Quartz2D guide draw text.
Thanks in advance,
Regards.
- (void)drawRect:(CGRect)rect
{
CGSize cgs = CGSizeMake(250.0, 400.0);
UIGraphicsBeginImageContext(cgs);
CGRect rectangle = CGRectMake(0,0,cgs.width,cgs.height);
CGRect imageRect = CGRectInset(rectangle, 5.4, 5.4);
imageRect.size.height -= 100;
UIImage *myImage = [UIImage imageNamed:#"pumpkin.jpg"];
[myImage drawInRect:imageRect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 10.0);
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 1.0);
CGContextStrokeRect(context, rectangle);
//
CGRect contextRect = self.bounds;
CGContextTranslateCTM(context, 0, contextRect.size.height);
CGContextScaleCTM(context, 1, -1);
float w, h;
w = contextRect.size.width;
h = contextRect.size.height;
CGAffineTransform myTextTransform;
CGContextSelectFont (context, "Helvetica-Bold", h/10, kCGEncodingMacRoman);
CGContextSetCharacterSpacing (context, 10);
CGContextSetTextDrawingMode (context, kCGTextFillStroke);
CGContextSetRGBFillColor(context, 0, 1, 0, .5);
CGContextSetRGBStrokeColor(context, 0, 0, 1, 1);
myTextTransform = CGAffineTransformMakeRotation(0);
CGContextSetTextMatrix(context, myTextTransform);
CGContextShowTextAtPoint(context, 0, 50, "Quartz 2D", 9);
//
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[testImg drawAtPoint:CGPointMake(35, 10)];
}
self.frame indicates the coords and size of the view in its parent view coordinate system.
self.bounds indicates the coords and size of the view in its own coordinate system. So it always has the same width and height as self.frame, but it has x and y equal to 0.
self.bounds is equal to CGRectMake(0, 0, self.frame.size.width, self.frame.size.height).
So your code:
CGRect contextRect = self.bounds;
is the same as:
CGRect contextRect = rect;

Resources