How to draw to multiple CGLayers at the same time? - ios

I'd like to draw objects to two separate CGLayers from within the same for loop, but am unsure how to do this.
For example, I'd like to draw three orange circles behind three blue circles, with the orange circles in one layer, and the blue circles in another. The following code will place each circle on top of the previous circle:
-(void) drawRect:(CGRect)rect {
UIBezierPath *circle;
for (int i = 1; i <= 3; i++) {
// Create an orange circle
circle = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(CGRectMake(i*50, 80, 50, 50), 0, 0)];
circle.lineWidth = 4.0f;
[[UIColor colorWithRed:1.0 green:0.75 blue:0 alpha:1.0] setFill];
[[UIColor orangeColor] setStroke];
[circle stroke];
[circle fill];
// Create a blue circle
circle = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(CGRectMake(25 + i*50, 80, 50, 50), 0, 0)];
circle.lineWidth = 4.0f;
[[UIColor colorWithRed:0 green:0.5 blue:1.0 alpha:1.0] setFill];
[[UIColor blueColor] setStroke];
[circle stroke];
[circle fill];
}
}
How would I modify this so that the three orange circles would end up in an orangeLayer that sits behind the three blue circles in a blueLayer? I imagine this has something to do with saving and restoring contexts, but I can't really wrap my head around it.
Thanks so much!
PS: I realize that I can simply draw using two for loops inline to achieve the right effect, but this example is for instructional purposes to learn layering. Thanks!

I build a custom subclass of UIView, and create a function makeCircleWithFrame to draw a circle inside a view using UIGraphicsBeginImageContextWithOptions, i believe it will solve your main problem:
#import "circleView.h"
#import <math.h>
#implementation circleView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) return self;
[self setupViews];
return self;
}
- (void)awakeFromNib
{
[self setupViews];
}
- (void)setupViews
{
[self setBackgroundColor:[UIColor whiteColor]];
for (int i = 1; i <= 6; i++) {
UIColor *circleColor;
//Math function just to set different colors for each circle
if (fmodf(i, 2) == 0) {
circleColor = [UIColor colorWithRed:1.0 green:0.75 blue:0 alpha:1.0];
}
else {
circleColor = [UIColor colorWithRed:0 green:0.5 blue:1.0 alpha:1.0];
}
UIView *circleView = [self makeCircleWithFrame:(CGRectMake(10*i, 10*i, 100, 100)) andFillColor:circleColor];
circleView.tag = i;
NSLog(#"circle %i", i);
}
}
- (UIView *)makeCircleWithFrame:(CGRect)rect andFillColor:(UIColor *)color {
// declare UIimageView, not UIView
UIImageView *customView = [[UIImageView alloc] init];
customView.frame= self.bounds;
// create a new contex to draw
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0);
UIBezierPath *grayCircle = [UIBezierPath bezierPathWithOvalInRect:rect];
grayCircle.lineWidth = 6;
[color setFill];
[[UIColor orangeColor] setStroke];
[grayCircle stroke];
[grayCircle fill];
customView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self addSubview:customView];
return customView;
}
Please give me a feedback if you still having problems or need anymore help.

for that purpose you need to either draw the blue or the orange circles partially (depends on which one you want to be on top). You probably understand that there are only 2 possible options: a) the orange layer is on top 2) the blue one is. I think, if you want to group the circles by color (without using 1 layer per circle), you'd better:
1) use 1 layer for drawing
2) store Bezier paths (which represent circles) somewhere
3) draw the paths in accordance to the order and overlaying you need.

Related

How to make a transparent cut on UIVisualEffectView?

In my app , I made a see through UIView by subclassing simple UIView's. However, If I try to do the same using UIVisualEffectView, I am not able to do it.
Here is what I am able to do using normal UIView:
When I use the UIVisualEffectView in place of green UIView,I cannot see the see through UIView , even though see through UIView is added to the UIVisualEffectView as subview.
Code:
- (void)drawRect:(CGRect)rect { //this is same for the UIVIew and for the UIVisualEffectView
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
// Clear any existing drawing on this view
// Remove this if the hole never changes on redraws of the UIView
CGContextClearRect(context, self.bounds);
// Create a path around the entire view
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:self.bounds];
// Your transparent window. This is for reference, but set this either as a property of the class or some other way
CGRect transparentFrame;
// Add the transparent window
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:transparentFrame cornerRadius:5.0f];
[clipPath appendPath:path];
// NOTE: If you want to add more holes, simply create another UIBezierPath and call [clipPath appendPath:anotherPath];
// This sets the algorithm used to determine what gets filled and what doesn't
clipPath.usesEvenOddFillRule = YES;
// Add the clipping to the graphics context
[clipPath addClip];
// set your color
UIColor *tintColor = [UIColor greenColor];
// (optional) set transparency alpha
CGContextSetAlpha(context, 0.7f);
// tell the color to be a fill color
[tintColor setFill];
// fill the path
[clipPath fill];
}
Question: Why this didn't work with UIVisualEffectView ?
Add following global variables in your ViewController.h file-
CAShapeLayer *fillLayer;
UIVisualEffectView *overlayView;
Add following methods in your ViewController.m file-
-(void)addOverlay:(CGRect)rect{
float x = rect.origin.x;
float y = rect.origin.y;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) cornerRadius:0];
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(x, y, rect.size.width, rect.size.height) cornerRadius:5];
[path appendPath:circlePath];
[path setUsesEvenOddFillRule:YES];
[self removeOverlay];
overlayView = [[UIVisualEffectView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height+64)];
overlayView.backgroundColor = [UIColor clearColor];
[self.view addSubview:overlayView];
fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor colorWithRed:78/255.0 green:103/255.0 blue:135/255.0 alpha:1.0].CGColor;
fillLayer.opacity = 0.85;
[[UIApplication sharedApplication].keyWindow.layer addSublayer:fillLayer];
}
-(void)removeOverlay{
[overlayView removeFromSuperview];
[fillLayer removeFromSuperlayer];
}
and call it as -
[self addOverlay:rect];

Unable to make UILabel text glow

I'm running the following code in viewDidLoad, it is applying the glow outside the label not on the text. Please advise.
lbl_score.font = myfont;
lbl_score.alpha = 1.0;
lbl_score.textColor = UIColor.greenColor;
lbl_score.layer.shadowColor = [[UIColor blueColor] CGColor];
lbl_score.layer.shadowOffset = CGSizeMake(0.0, 0.0);
lbl_score.layer.shadowRadius = 30.0f;
lbl_score.layer.shadowOpacity = 0.9;
lbl_score.layer.masksToBounds = NO;
I've imported QuartzCore/QuartzCore.h". I would like to apply the glow only on the label text.
Thanks
To apply a glow to the actual text in a label, you have to override drawTextInRect on the label itself. You're setting a shadow on the label's layer, which is a rectangle.
The override is quite simple. You just adjust the graphics context, call super, then restore the graphics context:
-(void)drawTextInRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeZero, 30.0, [UIColor blueColor].CGColor);
[super drawTextInRect:rect];
CGContextRestoreGState(context);
}
You are putting a shadow on the layer, which is rectangular. UILabel has shadowColor and shadowOffset properties which you should use instead. You can’t set a separate opacity for the shadow this way, but you can bake it into the UIColor that you pass for the shadow color.
lbl_score.font = myfont;
lbl_score.alpha = 1.0;
lbl_score.textColor = UIColor.greenColor;
lbl_score.shadowColor = [[UIColor blueColor] colorWithAlphaComponent:0.9];
lbl_score.shadowOffset = CGSizeMake(0.0, 0.0);
Unfortunately, you can’t set a shadow radius in this way. If you need to change the radius, look at doing your own custom drawing in -drawRect: instead.
I found code similar to jrturton ,but with more customisation.
This is working for me.
DTGlowingLabel.h
#interface DTGlowingLabel : UILabel {
}
#end
DTGlowingLabel.m
#import "DTGlowingLabel.h"
#implementation DTGlowingLabel
- (void) drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIColor *insideColor = [UIColor colorWithRed:69.0/255.0 green:254.0/255.0 blue:0 alpha:1];
UIColor *outlineColor = [UIColor colorWithRed:22.0/255.0 green:145.0/255.0 blue:0 alpha:0.8];
UIColor *blurColor = [UIColor colorWithRed:104.0/255.0 green: 248.0/255.0 blue:0 alpha:0.7];
CGContextSetStrokeColorWithColor(ctx, outlineColor.CGColor);
CGContextSetFillColorWithColor(ctx, insideColor.CGColor);
CGContextSetLineWidth(ctx, self.font.pointSize/60.0);
CGContextSetShadowWithColor(ctx, CGSizeMake(0, 0), self.font.pointSize / 10.0, blurColor.CGColor);
CGContextSetTextDrawingMode(ctx, kCGTextFillStroke);
[self.text drawInRect:self.bounds withFont:self.font lineBreakMode:self.lineBreakMode alignment:self.textAlignment];
}
#end
Source : http://www.cocoanetics.com/2010/01/uilabels-with-neon-effect/

iOS: Draw clear circle with UIBezierPath bezierPathWithOvalInRect:

In my UITableViewController's cells, I want to display a circle with a number in it. I am using UIBezierPath's bezierPathWithOvalInRect: to draw the circle.
Unfortunately, while I can set the fill color to be clearColor, the unused portion of the CGRect passed to bezierPathWithOvalInRect: is black.
How do I get rid of the black area created?
Partial screenshot for reference:
(I eventually hope to get that number inside the circle)
Code:
LTTTableViewCell:
- (void)awakeFromNib {
[super awakeFromNib];
// Create a square view using the height of the cell
CGRect positionFrame = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height);
LTTDrawBallView *drawBallView = [[LTTDrawBallView alloc] initWithFrame:positionFrame];
[self.contentView addSubview:drawBallView];
}
LTTDrawBallView:
- (void)drawRect:(CGRect)rect
{
// Create a new rect with some padding
// + create a circle from this new rect:
CGRect box = CGRectInset(self.bounds, self.bounds.size.width * 0.1f, self.bounds.size.height * 0.1f);
UIBezierPath *ballBezierPath = [UIBezierPath bezierPathWithOvalInRect:box];
[[UIColor whiteColor] setStroke];
[[UIColor greenColor] setFill]; // Green here to show the black area
[ballBezierPath stroke];
[ballBezierPath fill];
[self setBackgroundColor:[UIColor clearColor]]; // Happens with and without this line
}
In the init method of your LTTDrawBallView, include the code:
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];

How to Get the reverse path of a UIBezierPath

self.myPath=[UIBezierPath bezierPathWithArcCenter:center
radius:200
startAngle:0
endAngle:180
clockwise:YES];
(This much I was able to get up running with some web searching).
I have this path. Now I want to fill the reverse of this path, so leaving this portion and filling everything else. How can I finish the coding? I don't have much info on this.
The problem
The area it is showing after using Cemal Answer previously it only showed a circle with red stroke.
Edit
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.punchedOutPath =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 400, 400)];
self.fillColor = [UIColor redColor];
self.alpha = 0.8;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[[self fillColor] set];
UIRectFill(rect);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
[[self punchedOutPath] fill];
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
Use bezierPathByReversingPath. From the docs (iOS 6.0+ only):
Creates and returns a new bezier path object with the reversed contents of the current path.
so to reverse your path, you'd just:
UIBezierPath* aPath = [UIBezierPath bezierPathWithArcCenter:center
radius:200
startAngle:0
endAngle:180
clockwise:YES];
self.myPath = [aPath bezierPathByReversingPath];
Here's an alternative that doesn't require reversing the path at all.
You have a portion of a view you essentially want to "clip out":
Let's say you want the white area to be [UIColor whiteColor] with 75% alpha. Here's how you do it quickly:
You create a new UIView subclass.
This view has two properties:
#property (retain) UIColor *fillColor;
#property (retain) UIBezierPath *punchedOutPath;
You override its -drawRect: method to do this:
- (void)drawRect:(CGRect)rect {
[[self fillColor] set];
UIRectFill(rect);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
[[self punchedOutPath] fill];
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
There's a caveat here: The fillColor of the view must not include the alpha component. So in your case, you'd want that to just be [UIColor whiteColor]. You then apply the alpha bit yourself by calling [myView setAlpha:0.75].
What's going on here: This is using a blend mode called "Destination Out". Mathematically it's defined as R = D*(1 - Sa), but in layman's terms it means "Destination image wherever destination image is opaque but source image is transparent, and transparent elsewhere."
So it's going to use the destination (i.e., what's already in the context) wherever the new stuff is transparent (i.e. outside of the bezier path), and then where the bezier path would be opaque, that stuff is going to become transparent. However, the destination stuff must already be opaque. If it's not opaque, the blending doesn't do what you want. This is why you have to provide an opaque UIColor and then do any transparency you want with the view directly.
I ran this myself, with these circumstances:
the window has a [UIColor greenColor] background
the fillColor is white
the punchedOutPath is a oval that's inset 10 points from the edges of the view.
the view has an alpha of 0.75
With the code above, I get this:
The interior is pure green, and the outside has the semi-transparent overlay.
Update
If your covering is an image, then you'll need to create a new image. But the principle is the same:
UIImage* ImageByPunchingPathOutOfImage(UIImage *image, UIBezierPath *path) {
UIGraphicsBeginImageContextWithOptions([image size], YES, [image scale]);
[image drawAtPoint:CGPointZero];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
[path fill];
UIImage *final = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return final;
}
You would then take the result of this function and put it into a UIImageView.
You can put this into a single screen app into the view controller: It will make a yellow background view and a blue layer on top of it that has an oval region cut out by a mask.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// create a yellow background
UIView *bg = [[UIView alloc] initWithFrame:self.view.bounds];
bg.backgroundColor = [UIColor yellowColor];
[self.view addSubview:bg];
// create the mask that will be applied to the layer on top of the
// yellow background
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.frame = self.view.frame;
// create the paths that define the mask
UIBezierPath *maskLayerPath = [UIBezierPath bezierPath];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:CGRectInset(self.view.bounds, 20, 20)]];
// here you can play around with paths :)
// [maskLayerPath appendPath:[UIBezierPath bezierPathWithOvalInRect:(CGRect){{80, 80}, {140, 190}}]];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithOvalInRect:(CGRect){{100, 100}, {100, 150}}]];
maskLayer.path = maskLayerPath.CGPath;
// create the layer on top of the yellow background
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = self.view.layer.bounds;
imageLayer.backgroundColor = [[UIColor blueColor] CGColor];
// apply the mask to the layer
imageLayer.mask = maskLayer;
[self.view.layer addSublayer:imageLayer];
}
this might answer this question as well: UIBezierPath Subtract Path
I have two solution for you.
Draw this path on a CALayer. And use that CALayer as a mask layer for you actual CALayer.
Draw a rectangle with the sizes of you frame before adding arc.
UIBezierPath *path = [UIBezierPath bezierPathWithRect:view.frame];
[path addArcWithCenter:center
radius:200
startAngle:0
endAngle:2*M_PI
clockwise:YES];
I would use second solution. :)

UIColor and Graphics Context

I have a custom UIView which draws a path like so
- (void)drawRect:(CGRect)rect{
[[UIColor blackColor] setStroke];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColor(ctx, CGColorGetComponents(self.color));
CGContextFillPath(ctx);
UIBezierPath *thePath = [UIBezierPath bezierPath];
thePath.lineWidth = _lineWidth;
_center = CGPointMake(rect.size.width, rect.size.height);
[thePath moveToPoint:_center];
[thePath addArcWithCenter:_center radius:rect.size.width startAngle:M_PI endAngle:M_PI+degreesToRadians(_angle)clockwise:YES];
[thePath closePath];
[thePath fill];
}
My custom UIView also has a method to change the filling color
- (void) changeColor:(UIColor *) newColor {
self.color = [newColor CGColor];
[self setNeedsDisplay];
}
If I call the changeColor: method with any of the predefined colors such as
[UIColor redColor]
everything works fine. Instead if i try to give it a custom color such as
UIColor* color = [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f];
the color of my custom UIView flicker between white, red and blue randomly.
Any ideas of why is that?
Thank you in advance!
Try:
CGContextSetFillColorWithColor(ctx, self.color.CGColor);
instead of:
CGContextSetFillColor(ctx, CGColorGetComponents(self.color));
When you are using this method call :
[UIColor redColor];
For optimization purposes, UIColor return an object with only 2 components because he don't need the other one. (This is made possible with the mechanism of Class clustering).
That's maybe why your code show some differences between this two colors.

Resources