I am trying to create a triangle using UIBezierPath in a category of UIView. but the triangle is not being shown. also getting an : CGContextSetFillColorWithColor. So my question is how to make triangle or any path using a category of UIView. Demo Project
#import "UIView+Bubble.h"
#import <QuartzCore/QuartzCore.h>
#implementation UIView (Bubble)
+(UIView *)makeBubble{
UIView *customView = [[UIView alloc] init];
customView.frame=CGRectMake(0, 0, 320, 500);
UIBezierPath *triangle = [UIBezierPath bezierPath];
[triangle moveToPoint:CGPointMake(100, 0)];
[triangle addLineToPoint:CGPointMake(0, 100)];
[triangle addLineToPoint:CGPointMake(200, 100)];
[triangle closePath];
[[UIColor blackColor] setFill];
[triangle fill];
customView.layer.shadowPath = [triangle CGPath];
return customView;
}
#end
In ViewController.m using it like:-
- (void)viewDidLoad
{
UIView *helpBubble=[UIView makeBubble];
[self.view addSubview:helpBubble];
}
In your UIView+Bubble.h category UIBezierPath tries to draw triangle on a null context. If you want to draw shape using UIBezierPath like above, you have to put this code inside drawRectmethod of a View class.
On the other hand, you could create a new context to draw. You may modify your makeBubble method as follow:
+(UIView *)makeBubble{
// declare UIimageView, not UIView
UIImageView *customView = [[UIImageView alloc] init];
customView.frame=CGRectMake(0, 0, 320, 500);
// create a new contex to draw
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0);
UIBezierPath *triangle = [UIBezierPath bezierPath];
[triangle moveToPoint:CGPointMake(100, 0)];
[triangle addLineToPoint:CGPointMake(0, 100)];
[triangle addLineToPoint:CGPointMake(200, 100)];
[triangle closePath];
[[UIColor blackColor] setFill];
[triangle fill];
customView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return customView;
}
EDIT:
to make it dynamic you could pass a cgRect argument, like
+(UIView *)makeBubble:(CGRect)rect
also change to customView.frame=rect; and UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0); inside this method. And call makeBubble:(CGRect)rect method as
UIView *helpBubble=[UIView makeBubble:/*your desire rect*/];
P.S. it will be great if you calculate the points depending on the rect too.
Related
I'm making mask using UIBezierPath and CAShapeLayer with this code:
- (void)createPath {
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(100, 100)];
[path moveToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(0, 100)];
[path moveToPoint:CGPointMake(0, 100)];
[path addLineToPoint:CGPointMake(0, 0)];
[path closePath];
CAShapeLayer *layer = [CAShapeLayer new];
layer.frame = self.contentView.bounds;
layer.path = path.CGPath;
self.contentView.layer.mask = layer;
}
But instead of masking my contentView disappears completely. I tried to look at path in debugger and it looks exactly as I want it to look.
When using the layer.mask, the first is to get the right path. You don't need to move to a new point every time. By that way, your path is made of three or four subpaths which can not be closed to form a right path.
The second is trying to use in the View class itself, not to call for other subviews, like contentView. Because you may not know when to call it in subviews. Run the following in a UIView subclass, like in a UITableViewCell (awake from nib). You can get what I mean. If you really want to use contentView, just find the right position to put your layer code. like override setNeedLayout, etc.
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
[self createPath];
}
- (void)createPath {
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(100, 100)];
// [path moveToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(0, 100)];
// [path moveToPoint:CGPointMake(0, 100)];
[path addLineToPoint:CGPointMake(0, 0)];
[path closePath];
CAShapeLayer *layer = [CAShapeLayer new];
layer.frame = self.contentView.bounds;
layer.path = path.CGPath;
self.layer.mask = layer; // not self.contentView.layer.mask;
}
I want to use custom code to draw the image for UIButton. How do I go about doing that? I have the code that can go into drawRect, but that's for UIView, not UIButton.
In the Custom view's drawRect:
#implement MyCustomView
- (void)drawRect:(CGRect)rect {
UIColor *color = [UIColor blueColor];
// Vertical stroke
{
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint: CGPointMake(20, 0)];
[bezierPath addLineToPoint: CGPointMake(20, 40)];
[color setStroke];
bezierPath.lineWidth = 1;
[bezierPath stroke];
}
}
- (UIImage *)imageFromView {
[self drawRect:CGRectMake(0, 0, 40, 40)];
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0.0);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
In the button code:
UIButton *button = [[UIButton alloc] init];
MyCustomView *view = [[MyCustomView alloc] init];
[button setImage:view.imageFromView forState:UIControlStateNormal];
Use your drawing code to draw into an image graphics context opened with UIGraphicsBeginImageContextWithOptions.
Pull out the image using UIGraphicsGetImageFromCurrentImageContext.
Close the image graphics context with UIGraphicsEndImageContext.
Use the image in the button.
Simple example:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
UIBezierPath* p =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColor blueColor] setFill];
[p fill];
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// im is the blue circle image, do something with it here ...
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];
I have table cells that can expand to show some sub-detail, and I want a downward triangle to appear to let the user knows this is possible.
Here's the code that creates the triangle view...
-(UIView *)triangularViewWithWidth:(CGFloat)width height:(CGFloat)height pointsUp:(BOOL)pointsUp {
UIBezierPath *trianglePath = [UIBezierPath bezierPath];
if(pointsUp){
[trianglePath moveToPoint:CGPointMake(width / 2, 0)];
[trianglePath moveToPoint:CGPointMake(width, height)];
[trianglePath moveToPoint:CGPointMake(0, height)];
[trianglePath closePath];
} else {
[trianglePath moveToPoint:CGPointMake(0, 0)];
[trianglePath moveToPoint:CGPointMake(width, 0)];
[trianglePath moveToPoint:CGPointMake(width / 2, height)];
[trianglePath closePath];
}
CAShapeLayer *l = [CAShapeLayer layer];
l.path = trianglePath.CGPath;
UIView *triangleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
triangleView.layer.mask = l;
return triangleView;
}
and the code the implements it...
-(void)drawTriangle {
if([self.detail.subDetails count]){
self.openCloseTriangle = [self triangularViewWithWidth:100 height:100 pointsUp:NO];
NSLog(#"triangle origin is: %#", NSStringFromCGPoint(self.openCloseTriangle.frame.origin));
NSLog(#"triangle size is: %#", NSStringFromCGSize(self.openCloseTriangle.frame.size));
self.openCloseTriangle.backgroundColor = [UIColor redColor];
[self.contentView addSubview:self.openCloseTriangle];
[self.contentView bringSubviewToFront:self.openCloseTriangle];
}
}
The code works as expected (creates a large red rectangle) if I comment out this line:
l.path = trianglePath.CGPath;
so I suppose there's something I'm not understanding about shape layers, but I'm using basically the same drawing code in another part of my app and it works fine.
The point and rect NSLog output checks out fine, too (even though it doesn't appear):
triangle origin is: {0, 0}
triangle size is: {100, 100}
you must use addLineToPoint: instead of moveToPoint: to create the path:
-(UIView *)triangularViewWithWidth:(CGFloat)width height:(CGFloat)height pointsUp:(BOOL)pointsUp {
UIBezierPath *trianglePath = [UIBezierPath bezierPath];
if(pointsUp){
[trianglePath moveToPoint:CGPointMake(width / 2, 0)];
[trianglePath addLineToPoint:CGPointMake(width, height)];
[trianglePath addLineToPoint:CGPointMake(0, height)];
[trianglePath closePath];
} else {
[trianglePath moveToPoint:CGPointMake(0, 0)];
[trianglePath addLineToPoint:CGPointMake(width, 0)];
[trianglePath addLineToPoint:CGPointMake(width / 2, height)];
[trianglePath closePath];
}
CAShapeLayer *l = [CAShapeLayer layer];
l.path = trianglePath.CGPath;
UIView *triangleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
triangleView.layer.mask = l;
return triangleView;
}
also this line
self.openCloseTriangle = [self triangularViewWithWidth:100 height:100 pointsUp:NO];
is problematic: a view-property usually should be weak, as the view is owned by the view hierarchy, but if you assign the newly created view directly to such a property, it might be deallocated immediately. create a local view, add it to another view, than assign it to the property, that is weak.
my test code
#import "ViewController.h"
#interface ViewController ()
#property (weak) UIView *openCloseTriangle;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self drawTriangle];
}
-(UIView *)triangularViewWithWidth:(CGFloat)width height:(CGFloat)height pointsUp:(BOOL)pointsUp {
UIBezierPath *trianglePath = [UIBezierPath bezierPath];
if(pointsUp){
[trianglePath moveToPoint:CGPointMake(width / 2, 0)];
[trianglePath addLineToPoint:CGPointMake(width, height)];
[trianglePath addLineToPoint:CGPointMake(0, height)];
[trianglePath closePath];
} else {
[trianglePath moveToPoint:CGPointMake(0, 0)];
[trianglePath addLineToPoint:CGPointMake(width, 0)];
[trianglePath addLineToPoint:CGPointMake(width / 2, height)];
[trianglePath closePath];
}
CAShapeLayer *l = [CAShapeLayer layer];
l.path = trianglePath.CGPath;
UIView *triangleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
triangleView.layer.mask = l;
return triangleView;
}
-(void)drawTriangle {
UIView * v= [self triangularViewWithWidth:100 height:100 pointsUp:NO];
self.openCloseTriangle = v;
NSLog(#"triangle origin is: %#", NSStringFromCGPoint(self.openCloseTriangle.frame.origin));
NSLog(#"triangle size is: %#", NSStringFromCGSize(self.openCloseTriangle.frame.size));
self.openCloseTriangle.backgroundColor = [UIColor redColor];
[self.view addSubview:self.openCloseTriangle];
}
#end
I am trying to use UIbezierPaths for the first time and I am having no success in displaying it in the view. I am using a very old version of xcode (3.2.6).
-(void)drawshape:(CGRect)Rect {
UIBezierPath *cloudpath = [UIBezierPath bezierPath];
[cloudpath moveToPoint:CGPointMake(100.0, 100.0)];
[cloudpath addLineToPoint:CGPointMake(200, 200)];
[cloudpath addLineToPoint:CGPointMake(200, 300)];
[cloudpath addLineToPoint:CGPointMake(250, 330)];
[cloudpath addLineToPoint:CGPointMake(20, 400)];
[cloudpath closePath];
cloudpath.lineWidth = 2;
[[UIColor blueColor]setStroke];
[cloudpath stroke];
}
I have also Imported QuartzCore. I have only added -(void)drawshape in viewcontroller.h
When I build, i have no errors.Any help is massively welcomed
You will need to initiate the drawing from overriding drawRect: in a custom UIView and not in your view controller.