Drag and sacale a rectangle drawn using UIBezierPath IOS/Swift - ios

I am developing a program which identifies a rectangle in an image and draws a path on the border of that identified rectangle. Now I want to reposition that path in case if it is not on the exact position. For an example look at this image
In cases like this i need to drag the corners of the path and reposition it as it fits the rectangle.
To draw the path I used CAShapeLayer and UIBezierPath. Here is the code I used to draw the path.
// imgView is the UIImageView which contains the image with the rectangle
let line: CAShapeLayer = CAShapeLayer();
line.frame = imgView.bounds;
let linePath: UIBezierPath = UIBezierPath();
linePath.moveToPoint(CGPointMake(x1, y1);
linePath.addLineToPoint(CGPointMake(x2, y2);
linePath.addLineToPoint(CGPointMake(x3, y3);
linePath.addLineToPoint(CGPointMake(x4, y4);
linePath.addLineToPoint(CGPointMake(x1, y1);
linePath.closePath();
line.lineWidth = 5.0;
line.path = linePath.CGPath;
line.fillColor = UIColor.clearColor().CGColor;
line.strokeColor = UIColor.blueColor().CGColor;
imgView.layer.addSublayer(line);
The thing is I tried to add a gesture to UIBezierPath. But there is nothing like that as I noticed. Couldn't find anything regarding this. So can someone please let me know a way to get my work done. Any help would be highly appreciated.

You are right that there is no way to attach a gesture recognizer to a UIBezierPath. Gesture recognizers attach to UIView objects, and a UIBezierPath is not a view object.
There is no built-in mechanism to do this. You need to do it yourself. I would suggest building a family of classes to handle it. Create a rectangle view class. It would use a bezier path internally, as well as placing 4 corner point views on the vertexes and installing pan gesture recognizers each corner point view.
Note that Cocoa rectangles (CGRects) can't be rotated. You'll need to use a series of connected line segments and write logic that forces it to stay square.

Related

Draw a plus (+) sign using CALayer

I am not good with CALayer but I need to draw a plus (+) sign and I don't want to use an image as I want to animate the drawing. Any help?
After all the down votes, I was able to do it myself. Here's how for others who might need this
CGFloat height = 2.f;
CGFloat width = 3.f;
CGFloat cornerRadius = 1.f;
CALayer *hLayer = [CALayer layer];//this is the left - right stroke
hLayer.frame = CGRectMake(0, CGRectGetMidY(self.bounds)-(height/2), width, height);
hLayer.cornerRadius = cornerRadius;
CALayer *vLayer = [CALayer layer];// this is the top - bottom stroke
vLayer.frame = CGRectMake(CGRectGetMidX(self.bounds) - (height/2), -3,height, width);
vLayer.cornerRadius = cornerRadius;
[self.layer addSublayer:hLayer];
[self.layer addSublayer:vLayer];
As luk2302 says, use a CAShapeLayer.
You can install a CGPath into a CAShapeLayer. You can get a CGPath from any UIBezierPath. (It has a CGPath property that lets you get to the underlying CGPath object for any UIBezierPath.)
I suggest reading up on UIBezierPath. It has methods moveToPoint and addLineToPoint.
You'd move to the top of your plus, add a line down, then move to the left of your plus and add a line across.
Note that you can also animate images, depending on the type of animation you are after. What kind of animation do you need to do?
Another simple way would be to use two UIView with the same background color and add them as a subview to another UIView. Then you can use the UIView animateWithDuration:... method to do your simple animations.

Draw gradient below a CAShapeLayer graph

I'm drawing a graph using a CGPath applied to a CAShapeLayer. The graph itself is drawn just fine, but I want to add a gradient underneath it afterwards. My problem is that the path is closed with a straight line going from the last point to the first point (see below) – this would make a gradient fill look totally ridiculous.
As far as I can see, the only way to circumvent this issue is to draw two additional lines: one from the last point of the graph to the bottom-right corner, and from there, another one to the bottom-left corner. This would close the path off nicely, but it would add a bottom line to the graph, which I don't want.
If I were using CGContext, I could easily solve this by changing the stroke color to transparent for the last two lines. However, with the code below, I don't see how that would be possible.
CGMutablePathRef graphPath = CGPathCreateMutable();
for (NSUInteger i = 0; i < self.coordinates.count; i++) {
CGPoint coordinate = [self.coordinates[i] CGPointValue];
if (!i) {
CGPathMoveToPoint(graphPath, NULL, coordinate.x, coordinate.y);
} else {
CGPathAddLineToPoint(graphPath, NULL, coordinate.x, coordinate.y);
}
}
CAShapeLayer *graphLayer = [CAShapeLayer new];
graphLayer.path = graphPath;
graphLayer.strokeColor = [UIColor whiteColor].CGColor;
graphLayer.fillColor = [UIColor redColor].CGColor;
[self.layer addSublayer:graphLayer];
I hope you guys can help me out!
Update: You suggest that I could create a CAGradientLayer, and then apply the graph layer as its mask. I don't see how that would work, though, when the graph/path looks the way it does. I have replaced the image above with another graph that hopefully illustrates the problem better (note that I've given the CAShapeLayer a red fill). As I see it, if I were to apply above layer as the mask of a CAGradientLayer, some of the gradient would lie above the graph, some it below. What I want is for all of the gradient to be placed right beneath the graph.
Maybe I'm not understanding the problem correctly, but if you're looking to add a consistent gradient, couldn't you create a gradient layer and then make your graphLayer be that layer's mask?
Figure out whatever the min max bounds of your coordinates, create a CAGradientLayer that size, configure it however you might like and then, apply your graphLayer as it's mask. Then add the new CAGradientLayer to your self.layer.
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
// ... Configure the gradientLayer colors / locations / size / etc...
gradientLayer.mask = graphLayer;
[self.layer addSubLayer:gradientLayer];
This doesn't take into account the stroke, but it shouldn't be difficult to apply that as well if that's important.
I solved the problem by creating two separate paths: One for the graph (as shown in my original post), and one that starts in the lower-right corner, moves in a straight line to the lower-left corner, and from there follows the same path as the graph. By doing so, the path gets closed off nicely, since the graph ends at the same x-coordinate as where the path started.
From there, I applied the second path to a CAShapeLayer, and then used this layer as the mask of a gradient layer.

UICollisionBehavior - custom shape for UIView collision

I'm trying to figure out how to use UIKit Dynamics to successfully collide two UIViews which have custom boundary shapes.
The most basic example I can think of to explain my question is to have two circles collide (taking in to account their round corners) instead of their square boundary.
I'm sure I've seen this somewhere but I can't find any documentation or discussion on the subject from any official source.
I'd like to do this too, but I don't think you can do it under the current UIKit Dynamics for iOS 7. Items added to the animator must adopt the UIDynamicItem protocol (UIView does). The protocol only specifies their boundaries to be rectangular, via the bounds property, which is a CGRect. There's no custom hit test.
However, you can add a fixed Bezier path to the collision set, and it could be circular or any shape you can make with a path, but it would act like a curved wall that other rectangular objects bounce off of. You can modify the DynamicsCatalog sample code in Xcode to see the use of a curved boundary which doesn't move.
Create a new view file called BumperView, subclass of UIView.
In BumperView.m, use this drawRect:
#define LINE_WIDTH 2.0
- (void)drawRect:(CGRect)rect
{
UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, LINE_WIDTH/2, LINE_WIDTH/2)];
[[UIColor blueColor] setStroke];
[[UIColor lightGrayColor] setFill];
ovalPath.lineWidth = LINE_WIDTH;
[ovalPath stroke];
[ovalPath fill];
}
In the storyboard for the Item Properties page, add a View somewhere below the boxes and change its class to BumperView, and change its background color to clear.
Create an outlet named bumper to it in APLItemPropertiesViewController.m, but give it class BumperView.
Add the following in the viewDidAppear function, after collisionBehavior has been created:
UIBezierPath *bumperPath = [UIBezierPath bezierPathWithOvalInRect:self.bumper.frame];
[collisionBehavior addBoundaryWithIdentifier:#"Bumper" forPath:bumperPath];
Run it and go to the Item Properties page to see the rectangles bounce off the oval.

How to rotate a CAShapeLayer along with its Pattern?

I have a CAShapeLayer, I am able to rotate it using UIRotationGestureRecognizer. But when rotating, it only rotate its frame not the filled pattern inside it.
CAShapeLayer *shapeLayer = [finalShapeLayersArray objectAtIndex:indextobeRotated];
CGPathRef path = createPathRotatedAroundBoundingBoxCenter(shapeLayer.path,recognizer.rotation);
shapeLayer.path = path;
CGPathRelease(path);
recognizer.rotation = 0;
Bu doing this, it only rotates the boundary of the CAShapeLayer. I have filled a pattern with an image. I just want that pattern should also be rotated.
You need to set a rotation transform on your layer using CALayer's transform property instead (you do not rotate the the shape path then as the whole layer will be rotated).
See this answer on how to create such a transform. Also see the Core Animation Function Reference for all the functions that can create/modify transformations.

Drawing custom shape and animating custom properties using CALayers?

I have a custom shape that i want to draw using UIBezierPaths and i want to use this drawing as a CALayer inside my view. The bezier path i use works if i draw it directly onto the UIView (inside drawRect). I want to know as to how i can use the same bezier drawing and perform the drawing inside my CALayer. I would then add this layer as a sub layer inside my view!
For instance lets say I'm drawing a concentric circle with my bezier paths and i want to draw this using a CALayer, how would i go about animating custom properties of the path, like its center, radius, startAngle and endAngle?
Specifically i want to know how i should
Arrange my CALayer (initializing, drawing , upadte drawing ,etc)
How do i draw the bezier inside my CALayer?
How do i interact between the layer and the view that contains it?
Any help is appreciated!
Here's how you create & draw a UIBezierPath in a CALayer. Using a CAShapeLayer is the easiest way:
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:...];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.path = circlePath.CGPath;
[self.view.layer addSublayer:circleLayer];
Here's a good tutorial on creating animatable properties in CALayers.

Resources