I create the following View in code, the idea is to use it as an image in UITableViewCell and UITabBarItem:
- (void)drawRect:(CGRect)rect
{
// Fill the background with white
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor * whiteColor = [UIColor colorWithRed:1.0
green:1.0
blue:1.0
alpha:1.0];
CGContextSetFillColorWithColor(context, whiteColor.CGColor);
CGContextFillRect(context, self.bounds);
//// Color Declarations
UIColor* fillColor = [UIColor colorWithRed: 1.0
green: 1.0
blue: 1.0
alpha: 1.0];
UIColor* strokeColor = [UIColor colorWithRed: 0.0
green: 0.0
blue: 0.0
alpha: 1.0];
//// Big Oval Drawing
CGRect bigOvalRect = CGRectMake(rect.origin.x+2,
rect.origin.y+5,
42.0,
42.0);
UIBezierPath* bigOvalPath = [UIBezierPath bezierPath];
[bigOvalPath addArcWithCenter: CGPointMake(CGRectGetMidX(bigOvalRect), CGRectGetMidY(bigOvalRect))
radius: CGRectGetWidth(bigOvalRect) / 2
startAngle: 40 * M_PI/180
endAngle: 320 * M_PI/180
clockwise: YES];
[fillColor setFill];
[bigOvalPath fill];
[strokeColor setStroke];
bigOvalPath.lineWidth = 1;
[bigOvalPath stroke];
}
To create an UIImage from this UIView I am using this code:
#import <QuartzCore/QuartzCore.h>
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Everything works as expected when I use it in my UITableViewCells but when I set it as the image of my UITabBarItem I only see a blue rectangle (iOS7):
MyIcon *icon = [[MyIcon alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)];
UIImage *tabImage =[self imageWithView:icon];
self.navigationController.tabBarItem.image = tabImage;
I guess it is something related with the alpha of the image but how can I fix it?
It wasn't a problem related to the size of the view but to its alpha.
I fixed it by setting the backgroundColor (was whiteColor) and the fillColor to:
[UIColor clearColor]
and by setting the view as opaque in the initWithFrame: method:
[self setOpaque = NO];
Related
Right now this is how I do it:
CGRect handleRect = CGRectMake(0, 0, 20, 20);
UIGraphicsBeginImageContextWithOptions(handleRect.size, false, 0.0);
CGContextRef handleRectContext = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(handleRectContext, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:0.25f].CGColor);
CGContextSetFillColorWithColor([UIColor colorWithRed:255.0f/255.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:1.00f].CGColor);
CGContextSetLineWidth(handleRectContext, 0.5);
CGContextSetShadowWithColor(handleRectContext, CGSizeMake(0, 1), 2.5, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:0.75f].CGColor);
CGContextFillEllipseInRect(handleRectContext, handleRect);
CGContextStrokeEllipseInRect(handleRectContext, handleRect);
CGContextFillPath(handleRectContext);
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
This creates the circle without the stroke but with a clipped off shadow (clipped at the 20x20 rect). If I remove CGContextSetShadowWithColor the stroke is there but obviously the shadow not.
How can I actually create a circle with a stroke and a shadow that's not clipped of and/or blurry?
Here is my current solution (Create a larger rectangle that contains the smaller circle):
CGSize handleSize = CGSizeMake(overlayHeight,overlayHeight);
UIGraphicsBeginImageContextWithOptions(handleSize, false, 0.0);
CGContextRef handleContext = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(handleContext, 0.5);
CGContextSetStrokeColorWithColor(handleContext, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:0.25f].CGColor);
CGContextSetFillColorWithColor([UIColor colorWithRed:255.0f/255.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:1.00f].CGColor);
CGRect circleRect = CGRectMake(0, 0, handleSize.width, handleSize.height );
circleRect = CGRectInset(circleRect, 3, 3);
CGContextSetShadowWithColor(handleContext, CGSizeMake(0, 1), 2.5, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:0.75f].CGColor);
CGContextFillEllipseInRect(handleContext, circleRect);
CGContextStrokeEllipseInRect(handleContext, circleRect);
CGContextStrokePath(handleContext);
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
If anyone has a better/easier solution please let me know!
my code of making CAShapeLayer is
UIBezierPath *circle = [UIBezierPath bezierPathWithArcCenter:CGPointMake(75, 125)
radius:50
startAngle:0
endAngle:1.8 * M_PI
clockwise:YES];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setFrame:CGRectMake(200 - 50, 300 - 50, 100, 100)];
circleLayer.path = circle.CGPath;
circleLayer.bounds = CGPathGetBoundingBox(circle.CGPath);
circleLayer.strokeColor = [UIColor colorWithRed:0.4 green:1.0 blue:0.2 alpha:0.5].CGColor;
[circleLayer setFillColor:[UIColor clearColor].CGColor];
circleLayer.lineWidth = 3.0;
if ([circleLayer animationForKey:#"SpinAnimation"] == nil) {
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2 * M_PI];
animation.duration = 2.0f;
animation.repeatCount = INFINITY;
animation.removedOnCompletion = NO;
[circleLayer addAnimation:animation forKey:#"SpinAnimation"];
}
[self.view.layer addSublayer:circleLayer];
I want to make CAGradientLayer instead of CAShapeLayer.
REASON: I need to use gradient color in layer, which is not possible in CAShapeLayer.
I want to use yellow to black gradient upon Cirle.
Image :
In my required output, color is fetched from background image, and put some black at the end, or opacity 0 within layer.
Any Idea, suggestion.
Thanks.
One quick and dirty trick is adding a gradient layer as a sublayer then put some more layers with the background colors same as the superview (in the original question, it's white).
For example:
Add a gradient layer (and set proper cornerRadius).
Add a smaller layer with white background color at the center of the gradient layer (this will create a ring).
Create a small segment of arc with white background color.
However, this may not be suitable views with background images.
To solve this, you can create a custom subclass of UIView and override the drawRect method.
Suppose there are instance variables: startColor, endColor, radius, strokeWidth, and mirrored.
The variable mirrored is used to control the position of the gap (cut off left side or right side) and the rotation direction of the animation (clockwise or counter-clockwise).
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
CGFloat gradientColors[4 * 2];
extractColorComponents(_startColor, gradientColors, 0);
extractColorComponents(_endColor, gradientColors, 4);
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, gradientColors, NULL, 2);
CGColorSpaceRelease(baseSpace);
baseSpace = nil;
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
gradient = nil;
// fill 'clear' color
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGFloat innerCircleRadius = _radius - _strokeWidth;
// fill an 'empty' hole inside the gradient part
CGContextFillEllipseInRect(ctx, (CGRect){
{_strokeWidth, _strokeWidth},
{innerCircleRadius * 2, innerCircleRadius * 2}
});
// fill an 'empty' segment of arc
CGFloat startAngle = _mirrored ? (0.9 * M_PI) : (-0.1 * M_PI);
CGFloat endAngle = startAngle + 0.2 * M_PI;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:(CGPoint){_radius, _radius}
radius:_radius - _strokeWidth * 0.5
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
path.lineWidth = _strokeWidth;
CGContextAddPath(ctx, path.CGPath);
CGContextSetLineWidth(ctx, _strokeWidth + 2);
CGContextStrokePath(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
Here's the complete implementation of the view.
Final result:
Two rings:
You can add two rings in your viewDidLoad or somewhere else with the following code to achieve the desired effect:
- (void)viewDidLoad
{
[super viewDidLoad];
UIColor *startColor = [UIColor colorWithHue:0.02 saturation:0.74 brightness:0.91 alpha:1];
UIColor *endColor = [UIColor colorWithHue:0.57 saturation:0.76 brightness:0.86 alpha:1];
CGPoint center = CGPointMake(160, 200);
Spinner *outerSpinner = [[Spinner alloc] initWithCenter:center
radius:50
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:NO];
Spinner *innerSpinner = [[Spinner alloc] initWithCenter:center
radius:40
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:YES];
[self.view addSubview:outerSpinner];
[self.view addSubview:innerSpinner];
[outerSpinner startAnimating];
[innerSpinner startAnimating];
}
Result:
The mask property on CALayer uses the alpha component of the mask layer to determine what should be visible and not. Since both of your colors (black and white) are fully opaque (no transparency) the mask has no effect. To fix this you should change one of the colors to a clear color:
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor],
(id)[[UIColor clearColor] CGColor], nil];
EDIT
class Colors {
let colorTop = UIColor(red: 192.0/255.0, green: 38.0/255.0, blue: 42.0/255.0, alpha: 1.0).CGColor
let colorBottom = UIColor(red: 35.0/255.0, green: 2.0/255.0, blue: 2.0/255.0, alpha: 1.0).CGColor
let gl: CAGradientLayer
init() {
gl = CAGradientLayer()
gl.colors = [ colorTop, colorBottom]
gl.locations = [ 0.0, 1.0]
}
}
I've subclassed UILabel class to override drawRect: method. The label in IB is connected with the created subclass. It appears on the screen, but it doesn't show any text, whether it it is set in IB or programmatically. The label itself appears on the screen, when I'm logging it's text property, it shows OK.
Here's my code:
MyLabel.h
#import <UIKit/UIKit.h>
#interface MyLabel : UILabel
#end
MyLabel.m
#import "MyLabel.h"
#import <QuartzCore/QuartzCore.h>
#implementation MyLabel
- (void)drawRect:(CGRect)rect {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *borderColor = [UIColor colorWithWhite: .1f alpha: 5.f];
UIColor *topColor = [UIColor colorWithWhite: .3f alpha: 5.f];
UIColor *bottomColor = [UIColor colorWithWhite: .5f alpha: 5.f];
UIColor *innerGlow = [UIColor colorWithWhite: 1.f alpha: .25f];
NSArray *gradientColors = #[(id)topColor.CGColor, (id)bottomColor.CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) gradientColors, NULL);
CGFloat bWidth = self.frame.size.width;
CGFloat bHeight = self.frame.size.height;
UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, bWidth, bHeight)
cornerRadius: 0];
[roundedRectanglePath addClip];
CGGradientRef background = gradient;
CGContextDrawLinearGradient(context, background, CGPointMake(bWidth / 2.f, 0), CGPointMake(bWidth / 2.f, bHeight), 0);
[borderColor setStroke];
roundedRectanglePath.lineWidth = 6;
[roundedRectanglePath stroke];
CGFloat glowRadius = 3.f;
UIBezierPath *innerGlowRect = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(glowRadius, glowRadius, bWidth - 2 * glowRadius, bHeight - 2 * glowRadius) cornerRadius: 0];
[innerGlow setStroke];
innerGlowRect.lineWidth = 3;
[innerGlowRect stroke];
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
#end
Maybe I need to override drawTextInRect: method, but I don't know how.
Thanks for help!
You need to call:
[super drawRect:rect];
At the top or your drawRect: method. If you need more control over how the string looks you'll need to draw it your self with the NSString string drawing methods.
I want to animate the position of an inner shadow on a bezier I created using PaintCode. The offset of the shadow will be animated, here's two examples:
How can I make this an animatable property so that I can animate the movement of the shadow? The generated code (pasted into drawRect) looks like this:
//// General Declarations
CGContextRef context = UIGraphicsGetCurrentContext();
//// Color Declarations
UIColor* fillColor = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 1];
UIColor* strokeColor = [UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 0.508];
//// Shadow Declarations
UIColor* shadow = strokeColor;
CGSize shadowOffset = CGSizeMake(3.1, 3.1);
CGFloat shadowBlurRadius = 5;
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(31.5, 33.5, 26, 26)];
[fillColor setFill];
[ovalPath fill];
////// Oval Inner Shadow
CGRect ovalBorderRect = CGRectInset([ovalPath bounds], -shadowBlurRadius, -shadowBlurRadius);
ovalBorderRect = CGRectOffset(ovalBorderRect, -shadowOffset.width, -shadowOffset.height);
ovalBorderRect = CGRectInset(CGRectUnion(ovalBorderRect, [ovalPath bounds]), -1, -1);
UIBezierPath* ovalNegativePath = [UIBezierPath bezierPathWithRect: ovalBorderRect];
[ovalNegativePath appendPath: ovalPath];
ovalNegativePath.usesEvenOddFillRule = YES;
CGContextSaveGState(context);
{
CGFloat xOffset = shadowOffset.width + round(ovalBorderRect.size.width);
CGFloat yOffset = shadowOffset.height;
CGContextSetShadowWithColor(context,
CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
shadowBlurRadius,
shadow.CGColor);
[ovalPath addClip];
CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(ovalBorderRect.size.width), 0);
[ovalNegativePath applyTransform: transform];
[[UIColor grayColor] setFill];
[ovalNegativePath fill];
}
CGContextRestoreGState(context);
[strokeColor setStroke];
ovalPath.lineWidth = 1;
[ovalPath stroke];
I am an IOS beginner.
I am trying to show a custom button with code generated by paintcode.
I drag a View on Storyboard then set this generic view class to my custom view "GreenButton". When I run the app, the button does not show up. I added NSLogs and could see the drawRect method worked successfully.
The code generated by paintcode seems to include everything to draw the button. I just use XCode to resize my view.
Do I need to implement initWithFrame method at all?
//
// GreenButton.m
// Test
//
// Created by Tim on 13.04.2013.
// Copyright (c) 2013 Tim. All rights reserved.
//
#import "GreenButton.h"
#implementation GreenButton
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// setup the initial properties of the view
}
return self;
}
#pragma mark - Touch event overrides
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
NSLog(#"drawRect enter");
//// General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
//// Color Declarations
UIColor* strokeColor = [UIColor colorWithRed: 0.419 green: 0.434 blue: 0.455 alpha: 1];
UIColor* baseColor = [UIColor colorWithRed: 0.218 green: 0.24 blue: 0.268 alpha: 1];
CGFloat baseColorRGBA[4];
[baseColor getRed: &baseColorRGBA[0] green: &baseColorRGBA[1] blue: &baseColorRGBA[2] alpha: &baseColorRGBA[3]];
UIColor* upperColor = [UIColor colorWithRed: (baseColorRGBA[0] * 0.8 + 0.2) green: (baseColorRGBA[1] * 0.8 + 0.2) blue: (baseColorRGBA[2] * 0.8 + 0.2) alpha: (baseColorRGBA[3] * 0.8 + 0.2)];
UIColor* lowerColor = [UIColor colorWithRed: (baseColorRGBA[0] * 0.9) green: (baseColorRGBA[1] * 0.9) blue: (baseColorRGBA[2] * 0.9) alpha: (baseColorRGBA[3] * 0.9 + 0.1)];
UIColor* lightUpColor = [UIColor colorWithRed: (baseColorRGBA[0] * 0.5 + 0.5) green: (baseColorRGBA[1] * 0.5 + 0.5) blue: (baseColorRGBA[2] * 0.5 + 0.5) alpha: (baseColorRGBA[3] * 0.5 + 0.5)];
UIColor* lightDownColor = [UIColor colorWithRed: (baseColorRGBA[0] * 0.8) green: (baseColorRGBA[1] * 0.8) blue: (baseColorRGBA[2] * 0.8) alpha: (baseColorRGBA[3] * 0.8 + 0.2)];
//// Gradient Declarations
NSArray* buttonGradientColors = [NSArray arrayWithObjects:
(id)upperColor.CGColor,
(id)lowerColor.CGColor, nil];
CGFloat buttonGradientLocations[] = {0, 1};
CGGradientRef buttonGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)buttonGradientColors, buttonGradientLocations);
NSArray* overlayGradientColors = [NSArray arrayWithObjects:
(id)lightUpColor.CGColor,
(id)[UIColor clearColor].CGColor, nil];
CGFloat overlayGradientLocations[] = {0, 1};
CGGradientRef overlayGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)overlayGradientColors, overlayGradientLocations);
NSArray* gradientColors = [NSArray arrayWithObjects:
(id)lightUpColor.CGColor,
(id)lightDownColor.CGColor, nil];
CGFloat gradientLocations[] = {0, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradientColors, gradientLocations);
//// Shadow Declarations
UIColor* buttonShadow = [UIColor blackColor];
CGSize buttonShadowOffset = CGSizeMake(0.1, 1.1);
CGFloat buttonShadowBlurRadius = 2;
UIColor* textShadow = [UIColor blackColor];
CGSize textShadowOffset = CGSizeMake(0.1, -0.1);
CGFloat textShadowBlurRadius = 5;
//// Abstracted Attributes
NSString* textContent = #"Update";
//// Back Rectangle Drawing
UIBezierPath* backRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(48, 53, 120, 23) cornerRadius: 4];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, buttonShadowOffset, buttonShadowBlurRadius, buttonShadow.CGColor);
CGContextBeginTransparencyLayer(context, NULL);
[backRectanglePath addClip];
CGContextDrawLinearGradient(context, gradient, CGPointMake(108, 53), CGPointMake(108, 76), 0);
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
//// Rounded Rectangle Drawing
UIBezierPath* roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(49, 54, 118, 21) cornerRadius: 3];
CGContextSaveGState(context);
[roundedRectanglePath addClip];
CGContextDrawLinearGradient(context, buttonGradient, CGPointMake(108, 54), CGPointMake(108, 75), 0);
CGContextRestoreGState(context);
//// Overlay Drawing
UIBezierPath* overlayPath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(48, 53, 120, 23) cornerRadius: 4];
CGContextSaveGState(context);
[overlayPath addClip];
CGContextDrawRadialGradient(context, overlayGradient,
CGPointMake(54.72, 38.09), 10,
CGPointMake(108, 11.46), 102.2,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGContextRestoreGState(context);
//// Text Drawing
CGRect textRect = CGRectMake(55, 54, 104, 21);
UIBezierPath* textPath = [UIBezierPath bezierPathWithRect: textRect];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, textShadowOffset, textShadowBlurRadius, textShadow.CGColor);
CGContextBeginTransparencyLayer(context, NULL);
[textPath addClip];
CGContextDrawLinearGradient(context, buttonGradient, CGPointMake(107, 54), CGPointMake(107, 75), 0);
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
[strokeColor setStroke];
textPath.lineWidth = 1;
[textPath stroke];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, textShadowOffset, textShadowBlurRadius, textShadow.CGColor);
[[UIColor whiteColor] setFill];
[textContent drawInRect: textRect withFont: [UIFont boldSystemFontOfSize: [UIFont systemFontSize]] lineBreakMode: NSLineBreakByWordWrapping alignment: NSTextAlignmentCenter];
CGContextRestoreGState(context);
//// Cleanup
CGGradientRelease(buttonGradient);
CGGradientRelease(overlayGradient);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
NSLog(#"drawRect exit");
}
#end
You do not need to implement initWithFrame: when using a Storyboard.
In fact, the storyboard will use initWithCoder: so it won't even be called.
I tested your code, and it works fine, however, you are not drawing in the top left corner, but somewhere in the middle of the view, so be sure to make the view large enough if you want to see your button.
Preferably draw in the top left corner.