I am facing a problem with NSString method drawAtPoint and kCGTextFillClip. I needed to create a UILabel with a gradient instead of a simple color, so I've subclassed it and overrode the drawRect method. I managed to get what I wanted by using the function CGContextShowTextAtPoint, but it doesn't handle UTF-8 properly, which is essential to me.
I am aware that this is a common problem so after searching for a bit, I found out that I could use drawAtPoint to solve this encoding problem. Indeed, the text is now showing properly. The problem is I don't know how to make kCGTextFillClip work anymore.
Look at the result.
As you can see, it seems to clip to the first letter just fine, but not after that. Do you have any idea on how to solve this issue?
Here's the code I'm using :
- (void)drawRect:(CGRect)rect {
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect viewBounds = self.bounds;
CGContextSaveGState(theContext);
CGContextTranslateCTM(theContext, 0.0, viewBounds.size.height/2 + 1);
CGContextSetTextDrawingMode(theContext, kCGTextFillClip);
[self.text drawAtPoint:CGPointMake(0, 0) withFont:[UIFont fontWithName:#"Helvetica-Bold" size:11.5]];
// Creation of the gradient
CGFloat locations[2] = { 0.0f, 1.0f };
CGFloat components[8] = {
190.0/255, 190.0/255, 190.0/255, 1.0, // Start color
113.0/255, 113.0/255, 113.0/255, 1.0 // End color
};
CGColorSpaceRef rgbColorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, 2);
CGPoint topCenter = CGPointMake(CGRectGetMidX(viewBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(viewBounds), CGRectGetMidY(viewBounds));
CGContextDrawLinearGradient(theContext, gradient, topCenter, midCenter, 0);
Thank you!
Try and draw letter by letter?
Related
I want to accomplish a function just like a Brush. The area where finger swipes changes to trasparent with gradually changed border.
I can only change the color to crystal clear now with following codes:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(self.eraser) return;
CGFloat scale = self.transform.a;
if (scale < 1) scale = 1;
CGPoint p = [[touches anyObject] locationInView: self];
CGPoint q = [[touches anyObject] previousLocationInView: self];
UIImage* image;
image = self.image;
CGSize size = self.frame.size;
UIGraphicsBeginImageContext(size);
CGRect rect;
rect.origin = CGPointZero;
rect.size = size;
[image drawInRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineCap(context, kCGLineCapRound);
CGContextBeginPath(context);
CGContextSaveGState( context );
CGContextSetLineWidth(context, (10.0 / scale) + 1);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextMoveToPoint(context, q.x, q.y);
CGContextAddLineToPoint(context, p.x, p.y);
CGContextStrokePath(context);
CGContextRestoreGState( context );
UIImage* editedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setBounds:rect];
[self setImage:editedImage];
}
How can I get the edge with gradually change? Thanks in advance.
You can achieve this effect by drawing a radial gradient with a variable alpha in the kCGBlendModeDestinationIn mode in each spot the user passes.
This blend mode has the effect of only applying the layer's alpha to layers below. With the variable alpha of our gradient, we can achieve this effect.
const CGFloat kBrushSize = 10.f;
CGContextSaveGState(context);
// Make a radial gradient that goes from transparent black on the inside
// to opaque back on the outside.
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 1.0 };
CGColorSpaceRef myColorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGGradientRef myGradient = CGGradientCreateWithColorComponents (myColorspace, components,
locations, num_locations);
CGColorSpaceRelease(myColorspace);
// Draw the gradient at the point using kCGBlendModeDestinationIn
// This mode only applies the new layer's alpha to the lower layer.
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawRadialGradient(context, myGradient, p, 0.f, p, (kBrushSize / scale) + 1, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(myGradient);
CGContextRestoreGState(context);
Here is a screenshot of this code in action:
Note: Using this technique, if the user is moving his/her finger very fast, you may see a spacing effect where discrete brush dots are visible. This is a feature of some graphics software, but if this is undesirable for you, you can add code to interpolate the points between the current and last to draw more brush points, creating a more continuous stroke.
Also, you should be able to adjust the gradient color stops to achieve any kind of brush softness you like.
Source: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html
I write some code for creating the below with UIBezierPath but it didn't work. Can someone help me what's wrong with my code.
but i was tried with drawRect: it works fine....My problem is same i need in UIBezierPath as,
+ (UIBezierPath *)DropShape:(CGRect)Frame {}
my code using drawRect: is
CGContextRef context = UIGraphicsGetCurrentContext();
CGGradientRef gradient;
CGColorSpaceRef colorspace;
CGFloat locations[3] = { 0.0, 0.5, 1.0 };
NSArray *colors = #[(id)[UIColor redColor].CGColor];
colorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColors(colorspace,
(CFArrayRef)colors, locations);
CGPoint startPoint, endPoint;
CGFloat startRadius, endRadius;
startPoint.x = self.frame.size.width/2;
startPoint.y = 30;
endPoint.x = 175;
endPoint.y = 175;
startRadius = 0;
endRadius = 75;
CGContextDrawRadialGradient(context, gradient, startPoint,
startRadius, endPoint, endRadius, 0);
Thanks in advance...
There is an app called PaintCode which you can try for free.
You can draw onto a canvas and it will produce the drawRect code that you can copy and paste into your app.
I don't think you can copy from the trial version but it will create the path which you can type into your code manually.
It's worth a look.
I am trying to apply an alpha gradient to a UIView but can't get it to work at all. My current drawRect: method looks like this -
- (void)drawRect:(CGRect)rect
{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGGradientRef alphaGradient;
CGColorSpaceRef rgbColorSpace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, // Start color
1.0, 1.0, 1.0, 0.0 }; // End color
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
alphaGradient = CGGradientCreateWithColorComponents(rgbColorSpace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint bottomCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds));
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(currentContext, alphaGradient, midCenter, bottomCenter, 0);
CGGradientRelease(alphaGradient);
CGColorSpaceRelease(rgbColorSpace);
}
I am sure there is something I am missing here. I have tried changing the RGB values to 0.0 instead of 1.0, changing the color space to gray scale, etc.
Any help pointing me in the right direction would be much appreciated. Thanks
I resolved the issue by making sure the view's background color was set to clear, then applying the view's layer as a mask on the image view.
I use CAGradient:
UIView * viewToGradient = // view to add gradient
UIColor * color1 = [UIColor lightGrayColor]; // gradient color 1
UIColor * color2 = [UIColor clearColor]; // gradient color 2
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = viewToGradient.bounds;
gradientLayer.colors = [NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil];
gradientLayer.opacity = .6;
[viewToGradient.layer addSublayer:gradientLayer];
unfortunately I can't get to a solution with drawing a radial gradient in my UIView.
Look...that's how I want to draw it (this was made in PS):
But I'm currently stuck on this:
That's my code:
CGGradientRef radialGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
const CGFloat *colorComponents = CGColorGetComponents(textColor.CGColor);
CGFloat components[8] = { colorComponents[0], colorComponents[1], colorComponents[2], 1.0, // Start color
1.0, 1.0, 1.0, 1.0 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
radialGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawRadialGradient(myContext, radialGradient, midCenter, 8.0, midCenter, 1.0, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(radialGradient);
CGColorSpaceRelease(rgbColorspace);
I hope someone can give me the right tip ;)
Enjoy the weekend,
Chris
Basically, you need to use more than 2 locations. With 2 locations you get a constant gradient between the start and end locations. Your sample image shows the start colour for most locations up to the end and a much paler end colour. Looking at the sample image you will need at least 3 locations and to change the end colour.
I have an UILabel subclass that draws some text in drawinRect method using a pattern image. This pattern image is a gradient image I am creating.
-(void)drawRect:(CGRect)rect
{
UIColor *color = [UIColor colorWithPatternImage:[self gradientImage]];
[color set];
[someText drawInRect:rec withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:someAlignment];
}
The gradient image method is
-(UIImage *)gradientImage
{
NSData *colorData = [[NSUserDefaults standardUserDefaults] objectForKey:#"myColor"];
UIColor *co = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
colors = [NSArray arrayWithObjects:co,co,[UIColor whiteColor],co,co,co,co,nil];
NSArray *gradientColors=colors;
CGFloat width;
CGFloat height;
CGSize textSize = [someText sizeWithFont:font];
width=textSize.width; height=textSize.height;
UIGraphicsBeginImageContext(CGSizeMake(width, height));
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
// push context to make it current (need to do this manually because we are not drawing in a UIView)
UIGraphicsPushContext(context);
//draw gradient
CGGradientRef gradient;
CGColorSpaceRef rgbColorspace;
//set uniform distribution of color locations
size_t num_locations = [gradientColors count];
CGFloat locations[num_locations];
for (int k=0; k<num_locations; k++)
{
locations[k] = k / (CGFloat)(num_locations - 1); //we need the locations to start at 0.0 and end at 1.0, equaly filling the domain
}
//create c array from color array
CGFloat components[num_locations * 4];
for (int i=0; i<num_locations; i++)
{
UIColor *color = [gradientColors objectAtIndex:i];
NSAssert(color.canProvideRGBComponents, #"Color components could not be extracted from StyleLabel gradient colors.");
components[4*i+0] = color.red;
components[4*i+1] = color.green;
components[4*i+2] = color.blue;
components[4*i+3] = color.alpha;
}
rgbColorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(context, gradient, topCenter, midCenter, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(rgbColorspace);
// pop context
UIGraphicsPopContext();
// get a UIImage from the image context
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up drawing environment
UIGraphicsEndImageContext();
return gradientImage;
}
Everything is working fine. The text is drawn with nice glossy effect. Now my problem is if I change the x or y position of the text inside the UILabel and then call the
[someText drawinRect:rec]... to redraw, the gloss effect is generated differently. It seems that the pattern image is changing with change of text position..
The following is the effect for the frame rec = CGRectMake(0, 10, self.frame.size.width, self.frame.size.height);
The following is the effect for the frame rec = CGRectMake(0, 40, self.frame.size.width, self.frame.size.height);
I hope I have explained my question well. Could not find any exact answer elsewhere. Thanks in advance.
You might want to look at using an existing 3rd party solution like FXLabel.