How to draw polygons in cocoa touch using UIBezierPath - iphone

I want to draw polygon of different sides (4-12). What is the logic for drawing a polygon. For e.g. if user selects 6 side, it should draw a hexagon, if user enters 8 sides it should draw a octagon. I have found the following code but i also want to resize the UIView in which i am drawing a polygon so that the shape inside of the view also grows along with the view. Any body can help me please. Following is the code i am using currently but it is not positioned at the center also when i resize the view that shape moves to another position in the view.
int radius = MINIMUM(widht, height)*0.4 ;
for (int i = 0; i < _numberOFsides; i++){
CGPoint point = CGPointMake(widht/2+radius *cosf(i*2*M_PI/_numberOFsides), widht/2+radius*sinf(i*2*M_PI/_numberOFsides));
if (i==0) {
[_shapePath moveToPoint:point];
}
else{
[_shapePath addLineToPoint:point];
[_shapePath stroke];
}
}

now to resize ur UIBazierPath you can add below code,
CGRect bazierRect = CGPathGetBoundingBox(bezierpath.CGPath)
CGFloat scaleX = view.frame.size.width / bazierRect.frame.size.width;
CGFloat scaleY = view.frame.size.height / bazierRect.frame.size.height;
CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY);
CGPathRef newPath = CGPathCreateCopyByTransformingPath(bezierpath.CGPath, &transform);
bezierPath.CGPath = newPath;
CFRelease(newPath);

If you want to make a regular polygon with any number of sides the following code will give you the vertices of each edge and it is easy to rescale in size and number of sides:
int n = 10; //number of edges
float j = 20; //length of each edge
float x = 130;
float y = 250;//the point 130,250 will be at the bottom of the figure
float angle = 2*M_PI;
for (int i = 0; i < n; i++) {
CGRect frame = CGRectMake(x, y, 2, 2);//put a dot on x,y
NSLog(#"%f | %f, %f", angle, x, y);
x = x + j*cosf(angle);
y = y + j*sinf(angle); //move to the next point
angle = angle - 2*M_PI/n; //update the angle
//display the dot
UIView *rect = [[UIView alloc] initWithFrame:frame];
rect.backgroundColor = [UIColor blueColor];
[self.view addSubview:rect];
}
Hope this helps. If you have any questions feel free to ask and have a great day!
~Deadly Porcupine

Related

Can't position SKShapeNode (rectangle) in grid precisely

Being new to the obj-c language and Sprite kit I'm having difficulties positioning a rectangle in a grid...
There's an offset in the rectangles I create - if I manage to get the code to position a rectangle in a chosen field another rectangle will be offset...
I have tried several different approches and I can get it to work using JavaFX. What am I doing wrong?
The photo below shows my problem clearly.
My code is rather simple and can be seen here:
#import "MyScene.h"
#implementation MyScene
const int ROWS = 10;
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor darkGrayColor];
[self createBoardWithRows:ROWS];
[self createBoxPositionX:1 positionY:1];
[self createBoxPositionX:3 positionY:3];
[self createBoxPositionX:5 positionY:5];
}
return self;
}
-(void) createBoardWithRows: (int) rows{
for (int i = 1; i < rows; i++){
//Horisontal lines
int yPos = self.size.height/rows * i;
SKShapeNode *lineH = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, 0, yPos);
CGPathAddLineToPoint(pathToDraw, NULL, self.size.width, yPos);
lineH.path = pathToDraw;
lineH.lineWidth = 1.0;
[lineH setStrokeColor:[UIColor blackColor]];
//Vertical Lines
int xPos = self.size.width/rows * i;
SKShapeNode *lineV = [SKShapeNode node];
CGPathMoveToPoint(pathToDraw, NULL, xPos, 0);
CGPathAddLineToPoint(pathToDraw, NULL, xPos, self.size.height);
lineV.path = pathToDraw;
lineV.lineWidth = 1.0;
[lineV setStrokeColor:[UIColor blackColor]];
//Add lines
[self addChild:lineH];
[self addChild:lineV];
}
}
-(void) createBoxPositionX:(int) fieldIndexX positionY:(int) fieldIndexY{
int width = self.size.width/ROWS;
int height = self.size.height/ROWS;
int x = (width * fieldIndexX);
int y = (height * fieldIndexY);
CGRect box = CGRectMake(x, y, width, height);
SKShapeNode *shapeNode = [[SKShapeNode alloc] init];
shapeNode.path = [UIBezierPath bezierPathWithRect:box].CGPath;
shapeNode.fillColor = SKColor.yellowColor;
//Stroke settings
shapeNode.strokeColor = [SKColor clearColor];
shapeNode.lineWidth = 0;
[self addChild:shapeNode];
//Alternative rectangle
//SKSpriteNode spriteNodeWithColor:CGSize:
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
It almost seems like you're not taking into account the line width of the border in each box - so when you create the yellow boxes they are displaced by the number of points equivalent to the number of "borders".
To fix this, change these two lines:
int x = (width * fieldIndexX);
int y = (height * fieldIndexY);
to:
int x = (width * fieldIndexX) + fieldIndexX;
int y = (height * fieldIndexY) + fieldIndexY;
If you are getting 1 pixel shifts into any direction it might be anchor point issue.
By default it is 0.5,0.5 - at center of the sprite.
So if your sprite has even length for example 4, the middle can fall on either 2 or 3 and you will get 1 pixel off positioning.
The fix for this is clear, set the anchor point of node to 0,0, like so:
node.anchorPoint = CGPointMake(0,0);
What this does is that the sprite is pinned to it parent with lower left corner instead of center. Since the lower left corner always starts at 0,0 pixel you will have correct positions regardless of pixel count of your nodes.
But you will have to adjust the nodes position to accomodate this change.
Hope this helps.

adding the new view according to the finger touch location in iPhone using UITapGestureRecognizer

To all
I am using the
UITapGestureRecognizer*singleTap = [[[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doSingleTap:)] autorelease];
singleTap.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:singleTap
for getting the touch
And in doSingleTap: method i have this
-(void)doSingleTap:(UITapGestureRecognizer *)recognizer
{
CGPoint point = [recognizer locationOfTouch:0 inView:self.view];
NSLog(#"location X =>%f,location =>%f ",point.x,point.y);
CGPoint point1 = [recognizer locationOfTouch:1 inView:self.view];
NSLog(#"location X =>%f,location =>%f ",point1.x,point1.y);
NSLog(#"location X =>%f,location =>%f ",x,y);
UIView *test1 = [[UIView alloc]initWithFrame:CGRectMake(point.x, point1.y, x, y)];
test1.backgroundColor= [UIColor greenColor];
[self.view addSubview:test1];
}
getting the problem in adding the new view on the main view according to the finger location or position View is properly getting add on the view according to the position .
I want that view get add according to the two finger and automatically adjust their (x,y,w,h).
I need help if any one help me
Thanks in advance I google on this but didn't get any help
Compare with point.x and point1.x take lesser 1 as x and do same for y (compare with point.y and point1.y smaller 1 as y). take the difference between point.x and point1.x as width and do same for height (difference between point.y and point1.y) will sove the issue
float x = (point.x < point1.x) ? point.x : point1.x;
float y = (point.y < point1.y) ? point.y : point1.y;
float width = fabsf(point.x - point1.x);
float height = fabsf(point.y - point1.y);
UIView *test1 = [[UIView alloc]initWithFrame:CGRectMake(x, y,width,height)];
Try to calculate rect of view with the next:
float x = MIN(point.x, point1.x);
float y = MIN(point.y, point1.y);
float width = fabsf(point.x - point1.x);
float height = fabsf(point.y - point1.y);
CGRect frame = CGRectMake(x, y, width, height);

Draw circle on rectangle

I have UIView class and in method I want to draw first rectangles and sometimes circle
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
if ([WhatToDraw isEqual:#"Fields"]) {
[self DrawField:context];
}
if ([WhatToDraw isEqual:#"Ball"]) {
[self DrawBall:context x:20 y:20];
}
}
-(void)DrawBall:(CGContextRef)context x:(float) x y:(float) y
{
UIGraphicsPushContext(context);
CGRect rect = CGRectMake(x, y, 25, 25);
CGContextClearRect(context, rect);
CGContextFillEllipseInRect(context, rect);
}
-(void)DrawField:(CGContextRef)context
{
columns = 6;
float offset = 5;
float boardWidth = self.frame.size.width;
float allOffset = (columns + 2) * offset;
float currentX = 10;
float currentWidth = (boardWidth - allOffset) / columns;
float currentHeight = currentWidth;
self.fieldsArray = [[NSMutableArray alloc] init];
//create a new dynamic button board
for (int columnIndex = 0; columnIndex<columns; columnIndex++) {
float currentY = offset;
for (int rowIndex=0; rowIndex<columns; rowIndex++) {
UIGraphicsPushContext(context);
//create new field
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextBeginPath(context);
CGRect rect = CGRectMake(currentX, currentY, currentWidth, currentHeight);
CGContextAddRect(context, rect);
CGContextFillPath(context);
currentY = currentY + offset + currentHeight;
}
currentX = currentX + offset + currentWidth;
}
}
I also have method changing what to draw
-(void)Draw:(NSString*)Thing
{
self.WhatToDraw = Thing;
[self setNeedsDisplay];
}
Drawing rectangles (Fields) is ok, but when I click button to draw circle all rectangles disappear and only circle was drawn.
How can I draw circle on existing rectangle ?
The Problem
Your problem is that when a UIView redraws a region as marked by setNeedsDisplay or setNeedsDisplayInRect it will completely clear that region before executing your drawing code. This means that unless you draw both the rectangles and circle in a single drawing operation within drawRect you will never see the two both drawn in the area you choose to redraw, whether it be the entire view bounds with setNeedsDisplay or a specific area with setNeedsDisplayInRect.
The Solutions
There's no reason why you can't draw both the rectangles and circle each time within drawRect and optimise the performance of the drawing by only redrawing the regions necessary with setNeedsDisplayInRect.
Alternatively you could break up the content using CALayers and have the rectangles in one layer and the circle in another. This would allow you to leverage the animation capabilities of Core Animation. Core animation provides a simple and effective way to manipulate onscreen layers with implicit animations such as moving, resizing, changing colour etc.
my guess, the CGContextClearRect call in your DrawBall method is the responsable of rectangles disappearing... From Apple documentation: If the provided context is a window or bitmap context, Quartz effectively clears the rectangle.

How restrict movement in animateWithDuration

I'm creating app where UILabel object will move over the screen. My question is how I can restrict the label to go over the specific border. For example if the label have a message, I want have the whole message visible, not just the first part of it.
This is the code:
#define kHeight 320.0
#define kWidth 400.0
#define kTransitionDuration 1.50
#define kTopPlacement 80.0
- (void)myMover {
for (UIView *view in self.view.subviews) {
if( [view isKindOfClass:[UILabel class]]){
[UIView animateWithDuration:4.0 animations:^{
//set the point from where the move will start
[self setRandomLocationForLabel:view];
}];
}
}
}
- (void) setRandomLocationForView:(UIView *)view
{
[view sizeToFit];
CGRect messageViewBounds = CGRectMake(round((self.view.bounds.size.width - kWidth) / 2.0),
200, kWidth, kHeight);
CGRect newFrame = CGRectMake( 0, 300, 100 , 20 );
while (view.frame.size.width > kWidth) {
newFrame.size.width /= 2;
newFrame.size.height /= 2;
}
view.frame = newFrame;
CGFloat x = (CGFloat) (arc4random() % (int) messageViewBounds.size.width + view.frame.size.width/2);
CGFloat y = (CGFloat) (arc4random() % (int) messageViewBounds.size.height + view.frame.size.height/2);
view.center = CGPointMake (x,y);
}
Thank you for any advice!
Since you have the view you know its size. By knowing the width you know how far you need to position the center of the view from left or right (half the width). By knowing the height you know how from top or bottom (half the height).
Now you can calculate the rectangle that contains only the valid center points for that view by taking the full view and create an inset rectangle with half the width of your view subtracted on both left and right and half the height of your view subtracted on both top and bottom.
CGSize viewSize = view.bounds.size; // The view you are positioning
CGRect rectOfValidCenters = CGRectInset(self.view.bounds, // The view you are placing it in
viewSize.width/2.0, // subtract the width/2 from left and right
viewSize.height/2.0); // subtract the height/2 form top and bottom
CGFloat randomX = // generate random value from 0.0 to 1.0
CGFloat randomY = // generate random value from 0.0 to 1.0
// Random valid center point is:
// ( minX + randomX * width , minY + randomY * height)
//
// if x and y are zero then the view's center will be in the upper left
// of the rect of valid centers (making its upper left corner be in the
// top left of the view it's placed in).
// if x and y are one then the view's center will be in the lower right
// of the rect of valid centers (making its lower right corner be in the
// lower right of the view it's placed in).
CGPoint randomValidPoint = CGPointMake(CGRectGetMinX(rectOfValidCenters) + randomX * CGRectGetWidth(rectOfValidCenters),
CGRectGetMinY(rectOfValidCenters) + randomY * CGRectGetHeight(rectOfValidCenters));
view.center = randomValidPoint;
My question is how I can restrict the label to go over the specific border. For example if the label have a message, I want have the whole message visible, not just the first part of it.
You will need to take into account the length/width and height of the label when determining the random position it should land on. As such, your random selection should land in the area of
CGSize labelSize = [messageString sizeWithFont:messageString.font
constrainedToSize:maximumLabelSize
lineBreakMode:messageString.lineBreakMode];
CGFloat minX = 0;
CGFloat minY = 0;
CGFloat maxX = self.view.frame.size.width - labelSize.width;
CGFloat maxY = self.view.frame.size.height - labelSize.height;
// Use minX/Y and maxX/Y in your random co-ordinate algorithm

Add a UIImage on top of CATiledLayer

Is it possible to draw a UIImage on top of the CATiledLayer. The main idea is to note the position on the view. I have used PhotoScroller example from Apple Library and I am trying to add an UIImage on top of the tileRect. Any help will be appreciated.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
/**** Trying to add UIImage on top of CGRect rect. But not working.****/
CGRect pointRect = CGRectMake(100,100,32,32);
UIImage *image = [UIImage imageNamed:#"map-pointer32.png"];
[image drawInRect:pointRect];
// get the scale from the context by getting the current transform matrix, then asking for
// its "a" component, which is one of the two scale components. We could also ask for "d".
// This assumes (safely) that the view is being scaled equally in both dimensions.
CGFloat initialScale = CGContextGetCTM(context).a;
NSString *value = [NSString stringWithFormat:#"%.3f", initialScale];
CGFloat scale = [value floatValue];
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
CGSize tileSize = tiledLayer.tileSize;
// Even at scales lower than 100%, we are drawing into a rect in the coordinate system of the full
// image. One tile at 50% covers the width (in original image coordinates) of two tiles at 100%.
// So at 50% we need to stretch our tiles to double the width and height; at 25% we need to stretch
// them to quadruple the width and height; and so on.
// (Note that this means that we are drawing very blurry images as the scale gets low. At 12.5%,
// our lowest scale, we are stretching about 6 small tiles to fill the entire original image area.
// But this is okay, because the big blurry image we're drawing here will be scaled way down before
// it is displayed.)
tileSize.width /= scale;
tileSize.height /= scale;
// calculate the rows and columns of tiles that intersect the rect we have been asked to draw
int firstCol = floorf(CGRectGetMinX(rect) / tileSize.width);
int lastCol = floorf((CGRectGetMaxX(rect)-1) / tileSize.width);
int firstRow = floorf(CGRectGetMinY(rect) / tileSize.height);
int lastRow = floorf((CGRectGetMaxY(rect)-1) / tileSize.height);
for (int row = firstRow; row <= lastRow; row++) {
for (int col = firstCol; col <= lastCol; col++) {
UIImage *tile = [self tileForScale:scale row:row col:col];
CGRect tileRect = CGRectMake(tileSize.width * col, tileSize.height * row,
tileSize.width, tileSize.height);
// if the tile would stick outside of our bounds, we need to truncate it so as to avoid
// stretching out the partial tiles at the right and bottom edges
tileRect = CGRectIntersection(self.bounds, tileRect);
[tile drawInRect:tileRect];
if (self.annotates) {
// [[UIColor whiteColor] set];
CGContextSetLineWidth(context, 6.0 / scale);
CGContextStrokeRect(context, tileRect);
}
}
}
}
Probably the best solution would be to add the image in separate view, without any catiledlayer.
You can add an empty view, and add the view with the the catiledlayer an the uiimageview to that view.