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];
Related
I was tinkering around with BezierPath and noticed something that I can't seem to figure out. This is the code-
UIBezierPath *path = [UIBezierPath new];
CGSize screenDimensions = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
CGPoint firstPoint = CGPointMake(screenDimensions.width/2, screenDimensions.height/2);
CGPoint secondPoint = CGPointMake(screenDimensions.width/2 + 10, screenDimensions.height/2 + 10);
CGPoint thirdPoint = CGPointMake(screenDimensions.width/2 - 10, screenDimensions.height/2 + 10);
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:thirdPoint];
[path addLineToPoint:firstPoint];
[path closePath];
CAShapeLayer *tempLayer = [CAShapeLayer new];
[tempLayer setPath:path.CGPath];
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(screenDimensions.width/2 - 30, screenDimensions.height/2 - 30, 100, 100)];
// UIView *tempView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[tempView setBackgroundColor:[UIColor blueColor]];
tempView.layer.mask = tempLayer;
[self.view addSubview:tempView];
If i run the above code block, nothing is drawn on the UIView that is added on the screen. But if i were to comment the current "tempView" and uncomment the currently commented allocation, it would draw on screen perfectly. Can anyone please point out what am I doing wrong here when setting the frame or is it something else?
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint point1 = CGPointMake(100, 100);
CGPoint point2 = CGPointMake(200, 200);
CGPoint point3 = CGPointMake(300, 300);
[path moveToPoint:point1];
[path addQuadCurveToPoint:point3 controlPoint:point2];
CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.lineWidth = 5;
shape.strokeColor = [UIColor blueColor].CGColor;
shape.fillColor = [UIColor clearColor].CGColor;
shape.frame = self.view.bounds;
[self.view.layer addSublayer:shape];
You can see below output :
It's a simple problem.
Let's say the screen dimensions are 320x480 (and old iPhone). This means that firstPoint will be 160, 240.
In the current code, your view's frame is 130, 210, 100, 100. It's width and height are both 100.
The shape layer will be drawn relative to the view's bounds, not its frame. So as far as the layer and bezier path are concerned, the view's bounds are 0, 0, 100, 100. Since firstPoint is outside those bounds, it doesn't appear. It's actually drawn but outside the visible bounds of the view.
When you switch the two lines that create the view, the view's frame becomes 0, 0, 320, 480. This means its bounds is also 0, 0, 320, 480. The view is much larger now and the layer and bezier path fit and can be seen.
The proper solution is to create the coordinates of the bezier path based on the size of the view it will be applied to, not the size of the screen. This way, the bezier path will fit inside its view no matter how big it is.
More like this:
CGSize screenDimensions = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(screenDimensions.width/2 - 30, screenDimensions.height/2 - 30, 100, 100)];
// UIView *tempView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIBezierPath *path = [UIBezierPath new];
CGPoint firstPoint = CGPointMake(tempView.bounds.size.width/2, tempView.bounds.size.height/2);
CGPoint secondPoint = CGPointMake(tempView.bounds.size.width/2 + 10, tempView.bounds.size.height/2 + 10);
CGPoint thirdPoint = CGPointMake(tempView.bounds.size.width/2 - 10, tempView.bounds.size.height/2 + 10);
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:thirdPoint];
[path addLineToPoint:firstPoint];
[path closePath];
CAShapeLayer *tempLayer = [CAShapeLayer new];
[tempLayer setPath:path.CGPath];
[tempView setBackgroundColor:[UIColor blueColor]];
tempView.layer.mask = tempLayer;
[self.view addSubview:tempView];
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);
Hi i want to create a quarter transparent hole at right bottom on overlay UIView.
i am able to solve it using below code. But it does not look right as i am creating a rectangle outside the bond of view.
What i have tried:
#implementation PartialTransparentView
- (id)initWithBottomRightCornerRadiusForView:(UIView *)view withRadius:(CGFloat)radius
{
[self commonInitWithRect:CGRectMake(view.frame.size.width - radius, view.frame.size.height - radius, radius*2, radius*2)];
self = [super initWithFrame:CGRectMake(0, 0, 5000, 5000)];//**it does not look right to me**
if (self) {
// Initialization code
self.opaque = NO;
}
return self;
}
-(void)commonInitWithRect:(CGRect)rect{
backgroundColor = [UIColor colorWithWhite:1 alpha:0.75];
rectToBeSurrounded = rect;
}
- (void)drawRect:(CGRect)rect {
[backgroundColor setFill];
UIRectFill(rect);
CGFloat x = rectToBeSurrounded.origin.x;
CGFloat y = rectToBeSurrounded.origin.y;
CGFloat width = rectToBeSurrounded.size.width;
CGFloat height = rectToBeSurrounded.size.height;
//create outer square
CGFloat outerX = (x - width/2);
CGFloat outerY = y - height/2;
CGFloat outerWidth = 2*width;
CGFloat outerHeight = outerWidth;
//create outer square
CGRect outerRect = CGRectMake(outerX, outerY, outerWidth, outerHeight);
CGRect holeRectIntersection = CGRectIntersection( outerRect, rect );
CGContextRef context = UIGraphicsGetCurrentContext();
if( CGRectIntersectsRect( holeRectIntersection, rect ) )
{
CGContextAddEllipseInRect(context, holeRectIntersection);
CGContextClip(context);
CGContextClearRect(context, holeRectIntersection);
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextFillRect( context, holeRectIntersection);
}
}
Now i am using above code as :
PartialTransparentView *transparentView = [[PartialTransparentView alloc] initWithBottomRightCornerRadiusForView:self.view withRadius:50];
[self.view addSubview:transparentView];
Result as expected:
i know my solution will break if i have to acheive same thing but on top left of view.
what i am looking for just provide center (x, y) and radius for circle and get desired results.
Thanks
Basd on Mr.T
UIView *transparentView = [[UIView alloc] initWithFrame:self.view.frame];
[transparentView setBackgroundColor:[UIColor colorWithWhite:1 alpha:0.75]];
[self.view addSubview:transparentView];
circleView *acircleView = [[circleView alloc] initWithFrame:CGRectMake(50, 50, 60, 60)];
[acircleView setBackgroundColor:[UIColor grayColor]];
[transparentView addSubview:acircleView];
and circleView.m
- (void)drawRect:(CGRect)rect {
// Drawing code
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(50, 50, 60, 60)];
[UIColor.grayColor setFill];
[ovalPath fill];
}
output:
My suggestions is to add the transparent view as a separate view on your view controller. This can be either done on storyboard,so that you can set the background color and the alpha value to give the transparent effect!!!
Now create another view to make the circle and add it to transparent view, , and move this view on the transparent view according to your need!!!
Create the circle using bezier path:
circleView.m
- (void)drawRect:(CGRect)frame {
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(frame), CGRectGetMinY(frame), 60, 60)];
[UIColor.grayColor setFill];
[ovalPath fill];
}
For testing purpose, I have created a circle view on my IB and created an outlet property in my view controller.
HEre is the screenshot.
Now to move the circle, I can simply change the frame of the circle view, wherever I need.
For example, If I want to move it to top left, I simply do:
-(void)moveCircleViewwithX:(float) x withY:(float) y{
_cView.frame=CGRectMake(x, y, _cView.frame.size.width, _cView.frame.size.height);
}
The result will be:
Update
Put the following the drawRect method of transparentView:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect transparentPart = self.seeRect; //this is the rect of the circle view
CGContextAddEllipseInRect(ctx,transparentPart); //make the circle shape
CGContextClip(ctx);
CGContextClearRect(ctx, transparentPart);
and in your view controller:
when you want to apply the mask i.e the transparent for both circle and for the transparent layer:
-(void)applyMask{
[_cView setCircleColor:[UIColor clearColor]]; //circle view bezier path color
[_cView setNeedsDisplay];
[_tView setSeeRect:_cView.frame]; //set the transparency cut on transparency view
[_tView setNeedsDisplay];
}
Once you do this, you will get the transparency view!!!
You can move the circle by simply calling
[self moveCircleViewwithX:-30 withY:10]; //top left corner
and you can apply the transparency mask by simply calling:
[self applyMask];
So, the final result after you call the applyMask method will be:
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
Im working on a project with out Interface Builder,
i want to draw a line and a test square, but is not working
here the code
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
//Set the stroke (pen) color
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
//Set the width of the pen mark
CGContextSetLineWidth(context, 5.0);
//Start at this point
//CGContextMoveToPoint(context, 10.0, 30.0);
CGContextMoveToPoint(context, 20, 20);
//Give instructions to the CGContext
//(move "pen" around the screen)
CGContextAddLineToPoint(context, 310.0, 30.0);
CGContextStrokePath(context);
// Draw a red solid square
CGContextSetRGBFillColor(context, 255, 0, 0, 1);
CGContextFillRect(context, CGRectMake(10, 10, 50, 50));
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
self.view.backgroundColor = [UIColor grayColor];
UILabel *labelA = [[[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 20)]autorelease];
labelA.text = #"mk ss9";
[self.view addSubview:labelA];
}
the label shows fine, but the line and square are not showing, what is missing??
thanks a lot!
Ok got it!
the problem is that I was attempting to draw in my ViewController, and that is not the way,
So I created a new class, with subclass UIView, and it works fine now!