What appears when I run this thing is a little off on positioning. It is a simple CALayer subclass with a drawInContext function and positioning it in the view controller.
When I create the layer and add it, it is done with this code in the controller code. This runs, so the layer is created. But the position is not correct. It seems to work better in portrait, but I also need ways of detecting the rotation (but that is not my concern immediately).
- (void)viewDidLoad
{
[super viewDidLoad];
Canvas *c = [Canvas layer];
self.canvas = c;
[self.view.layer addSublayer: self.canvas];
//c.frame = self.view.bounds;
self.canvas.anchorPoint = CGPointMake(0, 0);
self.canvas.position = CGPointMake(0, 0);
self.canvas.bounds = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
self.canvas.backgroundColor = [UIColor lightGrayColor].CGColor;
[self.canvas setNeedsDisplay];
}
The main problem seems to be in the actual drawing, the code follows, but it doesn't actually run even when calling needsDisplay.
- (void)drawInContext:(CGContextRef)ctx {
[super drawInContext: ctx];
CGContextSaveGState(ctx);
UIGraphicsPushContext(ctx);
// CGRect b = self.bounds;
[self drawOrigin];
UIGraphicsPopContext();
CGContextRestoreGState(ctx);
}
- (void) drawOrigin {
CGPoint centre = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
UIBezierPath *hl = [UIBezierPath bezierPath];
[hl moveToPoint: centre];
[hl addLineToPoint: CGPointMake(centre.x, 0)];
[hl moveToPoint: centre];
[hl addLineToPoint: CGPointMake(centre.x, self.bounds.size.height)];
[hl moveToPoint: centre];
[hl addLineToPoint: CGPointMake(0, centre.y)];
[hl moveToPoint: centre];
[hl addLineToPoint: CGPointMake(self.bounds.size.width, centre.y)];
[hl stroke];
}
I don't know where else to look for this code. Any ideas or directions are welcome.
Related
I have a UIView that I want to be resizable via a UISlider. My UISlider range is set at 1.0-2.0. Currently I am resizing the view using a CGAffineTransform.
Screenshot:
My done in the custom UIView:
- (void)drawRect:(CGRect)rect {
NSLog(#"Draw rect called");
//// Square Drawing
UIBezierPath* squarePath = [UIBezierPath bezierPathWithRect: CGRectMake(8, 8, 50, 50)];
[UIColor.whiteColor setFill];
[squarePath fill];
//// Right top Drawing
UIBezierPath* rightTopPath = [UIBezierPath bezierPathWithRect: CGRectMake(57, 13, 9, 10)];
[UIColor.whiteColor setStroke];
rightTopPath.lineWidth = 1;
[rightTopPath stroke];
//// Top left Drawing
UIBezierPath* topLeftPath = [UIBezierPath bezierPathWithRect: CGRectMake(13, 0, 17.5, 9)];
[UIColor.whiteColor setStroke];
topLeftPath.lineWidth = 1;
[topLeftPath stroke];
//// Top right Drawing
UIBezierPath* topRightPath = [UIBezierPath bezierPathWithRect: CGRectMake(35, 0, 17.5, 9)];
[UIColor.whiteColor setStroke];
topRightPath.lineWidth = 1;
[topRightPath stroke];
//// Right middle Drawing
UIBezierPath* rightMiddlePath = [UIBezierPath bezierPathWithRect: CGRectMake(57, 28, 9, 10)];
[UIColor.whiteColor setStroke];
rightMiddlePath.lineWidth = 1;
[rightMiddlePath stroke];
//// Right bottom Drawing
UIBezierPath* rightBottomPath = [UIBezierPath bezierPathWithRect: CGRectMake(57, 43, 9, 10)];
[UIColor.whiteColor setStroke];
rightBottomPath.lineWidth = 1;
[rightBottomPath stroke];
}
Code for the slider:
- (IBAction)changeValue:(id)sender {
CGAffineTransform transform = CGAffineTransformScale(CGAffineTransformIdentity, self.slider.value, self.slider.value);
self.square.transform = transform;
}
This works okay however there is quality loss in the drawing as I resize because I'm not actually dynamically changing the size of the drawing. What I would actually like to do is change the size of the drawing dynamically. How would I achieve this? Do I need to first resize the frame and then call [self.square setNeedsDisplay] passing in the multiplication factor?
EDIT:
So I tired this method, now when I move the slider I have:
- (IBAction)changeValue:(id)sender {
CGRect newFrame = CGRectMake(20, 20, 72*self.slider2.value, 72*self.slider2.value);
self.square.frame = newFrame;
[self.square resizeAt:self.slider2.value];
}
So I am just changing the frame size now, then passing the slider value into the view and calling:
-(void)resizeAt: (float)multiply{
self.size=multiply;
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, self.size, self.size);
[self setNeedsDisplay];
}
an example of what is happening in drawRect:
UIBezierPath* squarePath = [UIBezierPath bezierPathWithRect: CGRectMake(11, 11, 50, 50)];
[squarePath applyTransform:self.transform];
[UIColor.whiteColor setFill];
[squarePath fill];
However the image doesn't look sharp or "redrawn"
Consider creating your bezier paths with points in the range 0.0 to 1.0 and then applying your transform directly to each bezier path. The scale of your transform would obviously change because you're scaling the paths to the full view size, not to a relative size.
Because you scale the path rather than the already rendered view the path render will still be clean and accurate.
im learning CoreGraphic and want to make a simple game, but stuck on drawing thing with bezier path, i wanted to draw a triangle inside a subview, but it always come out wrong, i want it to fit 1/4 of the square view
My code:
UIBezierPath* trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:CGPointMake(0, 0)];
[trianglePath addLineToPoint:CGPointMake(self.mainView.frame.size.width/2, self.mainView.frame.size.height/2)];
[trianglePath addLineToPoint:CGPointMake(self.mainView.frame.size.width, 0)];
[trianglePath closePath];
CAShapeLayer *triangleMaskLayer = [CAShapeLayer layer];
[triangleMaskLayer setPath:trianglePath.CGPath];
UIView *firstView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.mainView.frame.size.width, self.mainView.frame.size.height)];
firstView.backgroundColor = [UIColor colorWithWhite:.75 alpha:1];
firstView.layer.mask = triangleMaskLayer;
[self.mainView addSubview:firstView];
It look like this:
If the dimensions are incorrect, you probably are creating the triangle before AutoLayout has done its job.
To ensure your self.mainView has the correct size, create your triangle in your controller viewDidLayoutSubviews method.
Also mind that viewDidLayoutSubviews may be called multiple times.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.mainView.subviews.count == 0) {
UIBezierPath* trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:CGPointMake(0, 0)];
[trianglePath addLineToPoint:CGPointMake(self.mainView.frame.size.width/2, self.mainView.frame.size.height/2)];
[trianglePath addLineToPoint:CGPointMake(self.mainView.frame.size.width, 0)];
[trianglePath closePath];
CAShapeLayer *triangleMaskLayer = [CAShapeLayer layer];
[triangleMaskLayer setPath:trianglePath.CGPath];
UIView *firstView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.mainView.frame.size.width, self.mainView.frame.size.height)];
firstView.backgroundColor = [UIColor colorWithWhite:.75 alpha:1];
firstView.layer.mask = triangleMaskLayer;
[self.mainView addSubview:firstView];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
how can I create a label below like this image in iOS (Objective-c)?
My problem is not all corners, because I can round top-left & bottom-right corners by writing codes like this:
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.customView.bounds
byRoundingCorners:UIRectCornerBottomRight | UIRectCornerTopLeft
cornerRadii:(CGSize){7.0, 7.0}].CGPath;
self.customLabel.layer.mask = maskLayer;
and the result is this:
Now how can I write codes for top-right & bottom-left to reach the goal?
Update:
I didn't use the height as a variable in my equations it may affect the results.
Better results
- (void)drawRect:(CGRect)rect
{
CGFloat curveWidth = 40;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
// UIEdgeInsets insets = {0, curveWidth, 0, curveWidth};
// [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
UIBezierPath *path = [UIBezierPath bezierPath];
UIColor *fillColor = [UIColor orangeColor];
[fillColor setFill];
[path moveToPoint:CGPointMake(0, height)];
[path addCurveToPoint:CGPointMake(curveWidth*2, 0) controlPoint1:CGPointMake(curveWidth*0.75, height) controlPoint2:CGPointMake(curveWidth*0.25, 0)];
[path addLineToPoint:CGPointMake(width, 0)];
[path addCurveToPoint:CGPointMake(width-curveWidth*2, height) controlPoint1:CGPointMake(width-curveWidth*0.75, 0) controlPoint2:CGPointMake(width-curveWidth*0.25, height)];
[path closePath];
[path fill];
[super drawRect: rect];
}
Original Answer:
I wrote the code for you. follow the steps:
Create a custom UILabel by subclassing it. (code below)
Add a UILabel to your view using interface builder.
Add your constraints.
In the Identifier inspector tab, change class to your.
--
#implementation CurvedLabel
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGFloat curveWidth = 30;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
// UIEdgeInsets insets = {0, curveWidth, 0, curveWidth};
// [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
UIBezierPath *path = [UIBezierPath bezierPath];
UIColor *fillColor = [UIColor orangeColor];
[fillColor setFill];
[path moveToPoint:CGPointMake(0, height)];
[path addCurveToPoint:CGPointMake(curveWidth*2, 0) controlPoint1:CGPointMake(curveWidth, height) controlPoint2:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(width, 0)];
[path addCurveToPoint:CGPointMake(width-curveWidth*2, height) controlPoint1:CGPointMake(width-curveWidth, 0) controlPoint2:CGPointMake(width, height)];
[path closePath];
[path fill];
[super drawRect: rect];
}
#end
I don't believe there is any simple way of doing that, you could use a png file, if that is not a option you should look in to UIBezierPath, check this link out: ios drawing concepts
Sure you can do it with a UIBezierPath.
I have made a pretty simple setup you can use.
// This is the parallelogram's corner structure
// 1------2
// / /
// / /
// 4------3
int xOffset = 150; // x Offset on the parent view - here self.view
int yOffset = 50; // y Offset on the parent view - here self.view
int flatten = 50; // The difference from center of curve to spike point (try to adjust it and see what it does)
int width = 200;
int height = 100;
UIBezierPath * maskLayer = [UIBezierPath bezierPath];
// Start at corner 1
[maskLayer moveToPoint: CGPointMake(xOffset+flatten, yOffset)];
// Go to corner 2
[maskLayer addLineToPoint: CGPointMake(xOffset+width+flatten, yOffset)];
// Curve it down to corner 3
[maskLayer addCurveToPoint: CGPointMake(xOffset+width-flatten, height+yOffset) controlPoint1: CGPointMake(xOffset+width, yOffset) controlPoint2:CGPointMake(xOffset+width, height+yOffset)];
// Go straight to corner 4
[maskLayer addLineToPoint: CGPointMake(xOffset, height+yOffset)];
// Curve it back to corner 1
[maskLayer addCurveToPoint: CGPointMake(xOffset+(2*flatten), yOffset) controlPoint1:CGPointMake(xOffset+flatten, yOffset+height) controlPoint2: CGPointMake(xOffset+flatten, yOffset)];
// Create the shape and fill it with a color
CAShapeLayer *maskShapeLayer = [CAShapeLayer layer];
maskShapeLayer.path = maskLayer.CGPath;
maskShapeLayer.fillColor = [UIColor redColor].CGColor;
// add the layer to a view's layer (here self.view.layer)
[self.view.layer addSublayer:maskShapeLayer];
If you wanna make the two ´spikes´ more rounded, you can make the y-value in the addCurveToPoint points closer to the parallelogram's y-center-axis (height/2).
You will have to useaddCurveToPoint of UIBezierPath to make it curvy by giving it controlPoint1 and controlPoint2
For reference, you can look at Apple's Documentation
Also, this is a helpful reference too.
You will have to play around with UIBezierPath's properties
Is it possible to draw the following in objective-c? I tried using the image, but It pixelates. So I figured it is best to draw it programatically. Can someone provide me some sample code to achieve this.
Thank you.
Use a CAShapeLayerand assign it a path describing your shape:
- (UIBezierPath *)createBubblePathInFrame:(CGRect) frame{
CGFloat arrowInset = 10;
CGFloat arrowHeight = 10;
CGFloat arrowWidth = 20;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth/2, 0)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, frame.size.height)];
[path addLineToPoint:CGPointMake(0, frame.size.height)];
[path addLineToPoint:CGPointMake(0, arrowHeight)];
return path;
}
Use it like this:
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.frame = CGRectMake(50, 50, 200, 100);
shapelayer.path = [self createBubblePathInFrame:shapelayer.bounds].CGPath;
shapelayer.fillColor = [UIColor grayColor].CGColor;
shapelayer.strokeColor = [UIColor redColor].CGColor;
shapelayer.lineWidth = 1;
[self.view.layer addSublayer:shapelayer];
Or you can draw it yourself in drawRect:
[[UIColor grayColor] setFill];
[bezierPath fill];
[[UIColor redColor] setStroke];
[bezierPath stroke];
You can use this as a triangle view:
#implementation Triangle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor clearColor];
self.alpha = 0.75;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMinY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect)); // top right
CGContextAddLineToPoint(ctx, CGRectGetMidX(rect), CGRectGetMaxY(rect)); // bottom mid
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 25.0/255.0, 25.0/255.0, 25.0/255.0, 1.0);
CGContextFillPath(ctx);
}
#end
Then just subclass a UIView and add an instance of Triangle where appropriate (in your case on the top left corner).
For your purpose you also should rotate this triangle 180 degrees as the code above drwas a triangle that points downwards.
What is the best way to draw a bezier curve, in iOS application, that passes through a set of given points
A little more generic way to do it can be achieved by, for example, looking at the BEMSimpleLineGraph GitHub Project (see here for more info: bemsimplelinegraph). Here I extracted a method to draw a bezier curve through a given list of points.
The header file (BezierLine.h):
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreGraphics/CoreGraphics.h>
#interface BezierLine : NSObject
/*
Draws a bezier curved line on the given context
with points: Array of CGPoint values
*/
-(void) drawBezierCurveInContext:(CGContextRef)context withPoints:(NSArray*)points lineColor:(UIColor*)color lineWidth:(CGFloat)lineWidth;
#end
The implementation (BezierLine.m):
#import "BezierLine.h"
#implementation BezierLine
-(void) drawBezierCurveInContext:(CGContextRef)context withPoints:(NSArray*)points lineColor:(UIColor*)color lineWidth:(CGFloat)lineWidth {
if (points.count < 2) return;
CGPoint CP1;
CGPoint CP2;
// LINE
UIBezierPath *line = [UIBezierPath bezierPath];
CGPoint p0;
CGPoint p1;
CGPoint p2;
CGPoint p3;
CGFloat tensionBezier1 = 0.3;
CGFloat tensionBezier2 = 0.3;
CGPoint previousPoint1;
CGPoint previousPoint2;
[line moveToPoint:[[points objectAtIndex:0] CGPointValue]];
for (int i = 0; i < points.count - 1; i++) {
p1 = [[points objectAtIndex:i] CGPointValue];
p2 = [[points objectAtIndex:i + 1] CGPointValue];
const CGFloat maxTension = 1.0f / 3.0f;
tensionBezier1 = maxTension;
tensionBezier2 = maxTension;
if (i > 0) { // Exception for first line because there is no previous point
p0 = previousPoint1;
if (p2.y - p1.y == p1.y - p0.y) tensionBezier1 = 0;
} else {
tensionBezier1 = 0;
p0 = p1;
}
if (i < points.count - 2) { // Exception for last line because there is no next point
p3 = [[points objectAtIndex:i + 2] CGPointValue];
if (p3.y - p2.y == p2.y - p1.y) tensionBezier2 = 0;
} else {
p3 = p2;
tensionBezier2 = 0;
}
// The tension should never exceed 0.3
if (tensionBezier1 > maxTension) tensionBezier1 = maxTension;
if (tensionBezier2 > maxTension) tensionBezier2 = maxTension;
// First control point
CP1 = CGPointMake(p1.x + (p2.x - p1.x)/3,
p1.y - (p1.y - p2.y)/3 - (p0.y - p1.y)*tensionBezier1);
// Second control point
CP2 = CGPointMake(p1.x + 2*(p2.x - p1.x)/3,
(p1.y - 2*(p1.y - p2.y)/3) + (p2.y - p3.y)*tensionBezier2);
[line addCurveToPoint:p2 controlPoint1:CP1 controlPoint2:CP2];
previousPoint1 = p1;
previousPoint2 = p2;
}
CGContextSetAllowsAntialiasing(context, YES);
CGContextSetStrokeColorWithColor(context, color.CGColor);
CGContextSetLineWidth(context, lineWidth);
CGContextAddPath(context, line.CGPath);
CGContextDrawPath(context, kCGPathStroke);
}
#end
You can use it by for example creating an image context using UIGraphicsBeginImageContext and retrieving the context with UIGraphicsGetCurrentContext().
Otherwise you may want to change the code and assign the resulting Path to a CALayer and add that to an UIView.
Hope this helps.
I know this might be late, but just for anyone who is looking for the right answer. Instead of using addLineToPoint to draw the straight line. You can use addCurveToPoint to draw the curve. e.g.
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addCurveToPoint:CGPointMake(40, 100)
controlPoint1:CGPointMake(20, 0)
controlPoint2:CGPointMake(20, 100)];
[bezierPath addCurveToPoint:CGPointMake(80, 50)
controlPoint1:CGPointMake(60, 100)
controlPoint2:CGPointMake(60, 50)];
// and you may don't want to close the path
// [bezierPath closePath];
It's really up to you to choose the control points of the curve. I just use the x = last_point_x + 20; y = last_point_y for control point one, and x = current_point_x - 20; y = current_point_y;
and you may want to use other value instead of the 20 as you may have different segment width of the curve.
You can easily google some example of how to create bezier curve on the web. I found this short tut as an example.
You can create a close bezier curve for e.g. with the following code snippet:
UIBezierPath* path = [UIBezierPath bezierPath];
[path moveToPoint:pt1];
[path addLineToPoint:pt2];
[path addLineToPoint:pt3];
[path closePath];
I hope it will help as a starting point.
Please try this.
UIImageView *waterLevel = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,200,200)];
UIGraphicsBeginImageContext(waterLevel.frame.size);
[waterLevel.image drawAtPoint:CGPointZero];
//define BezierPath
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addLineToPoint:CGPointMake(waterLevel.frame.size.width, 0)];
[bezierPath addLineToPoint:CGPointMake(waterLevel.frame.size.width, waterLevel.frame.size.height)];
[bezierPath addLineToPoint:CGPointMake(0, waterLevel.frame.size.height)];
[bezierPath closePath];
bezierPath.lineWidth = 15;
//set the stoke color
[[UIColor blackColor] setStroke];
//draw the path
[bezierPath stroke];
// Add to the current Graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context,bezierPath.CGPath);
waterLevel.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:waterLevel];
You can be much more efficient by using the CGPointFromString method:
NSArray *pointArray = #[#"{3.0,2.5}",#"{100.0,30.2}", #"{100.0,200.0}", #"{3.0,200.0}"];
// draw the path
UIBezierPath *aPath = [UIBezierPath bezierPath];
for (NSString *pointString in pointArray) {
if ([pointArray indexOfObject:pointString] == 0)
[aPath moveToPoint:CGPointFromString(pointString)];
else
[aPath addLineToPoint:CGPointFromString(pointString)];
}
[aPath closePath];
UIBezierPath *aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[aPath moveToPoint:CGPointMake(100.0, 0.0)];
// Draw the lines.
[aPath addLineToPoint:CGPointMake(200.0, 40.0)];
[aPath addLineToPoint:CGPointMake(160, 140)];
[aPath addLineToPoint:CGPointMake(40.0, 140)];
[aPath addLineToPoint:CGPointMake(0.0, 40.0)];
[aPath closePath];