Is There Any Shorthand Way to Write Cgpoint, Cgrect, etc

Is there any shorthand way to write CGPoint, CGRect, etc?

As already suggested in the comments you can create your own extension.

Extension

You can add an initializer that accepts 2 CGFloat(s) and doesn't require external param names

extension CGPoint {
init(_ x: CGFloat, _ y: CGFloat) {
self.x = x
self.y = y
}
}

let point = CGPoint(1, 2)

Infix Operator

You can also define your infix operator

infix operator ° {}
func °(x: CGFloat, y: CGFloat) -> CGPoint {
return CGPoint(x: x, y: y)
}

let point = 1.1 ° 1.1

How to create shorthands for CGPoint & CGVector?

extension CGPoint {
init(_ x: CGFloat, _ y: CGFloat) {
self.init(x: x, y: y)
}
}

extension CGVector {
init(_ dx: CGFloat, _ dy: CGFloat) {
self.init(dx: dx, dy: dy)
}
}

typealias P = CGPoint
typealias V = CGVector

let p = P(10, 10)
let v = V(10, 10)

But no idea about the • and ` part - I replaced them with P and V :)

How to create shorthands for CGPoint & CGVector?

extension CGPoint {
init(_ x: CGFloat, _ y: CGFloat) {
self.init(x: x, y: y)
}
}

extension CGVector {
init(_ dx: CGFloat, _ dy: CGFloat) {
self.init(dx: dx, dy: dy)
}
}

typealias P = CGPoint
typealias V = CGVector

let p = P(10, 10)
let v = V(10, 10)

But no idea about the • and ` part - I replaced them with P and V :)

CGRect syntax I haven't seen before

That's C99 initializer syntax. You can use it with any structure.

The main advantage to an Objective-C is that it gives you some very Objective-C like syntax, where the fields are close to the values rather than implied by positioning. (That's not to say this is intentionally similar, or that it's the only advantage. But it is nice.)

It's sometimes slightly more typing, but I use it everywhere now.

Consider:

CGRect a = CGRectMake(a+c/2, b+d/2, c, d);

In order to understand this, you need to understand the order of the parameters. You also need to be able to catch the commas easily with your eyes. In this case, that's pretty easy, but if the expressions were more complicated you'd probably be storing them in a temporary variable first.

The C99 way:

CGRect a = (CGRect){
.origin.x = a+c/2,
.origin.y = b+d/2,
.size.width = c,
.size.height = d
};

It's longer, but it's more explicit. It's also very easy to follow what is assigned to what, no matter how long the expression are. It's also more like an Objective-C method. After all, if CGRect was a class, it would probably look like this:

CGRect *a = [[CGRect alloc] initWithOriginX:x originY:y width:w height:h];

You can also do things like this:

CGRect a = (CGRect){
.origin = myOrigin,
.size = computedSize
};

Here, you're building a rectangle using a CGPoint and CGSize. The compiler understands that .origin expects a CGPoint, and .size expects a CGSize. You've provided that. All's gravy.

The equivalent code would be CGRectMake(myOrigin.x, myOrigin.y, size.width, size.height). By using CGRectMake you're no longer expressing the same kind of meaning to the compiler. It can't stop you from assigning part of the size to the origin. It also won't stop you from assigning the width to the height. It doesn't even give you a good clue about which is the X and Y; if you've used APIs that provide vertical coordinates first, you'll get it wrong.

You can assign part from a structure and part from floats as well:

CGRect a = (CGRect){
.origin = myOrigin,
.size.width = c,
.size.height = d
};

The CGRectMake function predates C99. I have no evidence to this effect, but I think if C99 had come first CGRectMake probably wouldn't exist at all; it's the sort of crusty function you write when your language has no direct way to perform the initialization. But now it does.

Basically, if you use it for a while, you'll probably come to prefer C99 syntax. It's more explicit, more flexible, more Objective-C-like and harder to screw up.

Unfortunately, as of 4.6 Xcode will not autocomplete structure field names when in the C99 field initializer list.

How do I create a CGRect from a CGPoint and CGSize?

Two different options for Objective-C:

CGRect aRect = CGRectMake(aPoint.x, aPoint.y, aSize.width, aSize.height);

CGRect aRect = { aPoint, aSize };

Swift 3:

let aRect = CGRect(origin: aPoint, size: aSize)

Convert a CGPoint from a UIView coordinate system to a CALayer coordinate system

You can set the transform property of CALayers to the appropriate transform if you want to flip their coordinate system, but note that this will probably flip their drawing of the contents as well (I have not tested that, but it makes sense that this would be true). My assertion that the CALayer associated with a UIView shares the same coordinate system could in fact be entirely erroneous. It could also be that CALayers use the same coordinate system as UIViews (i.e. they're never flipped vertically), but I thought they were since CoreGraphics uses a flipped coordinate system relative to UIKit.

A simple way to test would be to add a screen-sized CALayer as the sublayer of a view's layer, then add another small CALayer as a sublayer of that. You could set it to show up at (0, 0, 320, 100) and see if it shows up on the top or the bottom of the iPhone's screen. This will tell you in which direction the Y axis goes for CoreAnimation.

- (void)viewDidLoad {
[super viewDidLoad];
CALayer *rootLayer = [CALayer layer];
rootLayer.frame = self.view.layer.bounds;
CALayer *smallLayer = [CALayer layer];
smallLayer.frame = CGRectMake(0, 0, rootLayer.bounds.size.width, 50);
smallLayer.backgroundColor = [UIColor blueColor].CGColor;
[rootLayer addSublayer:smallLayer];
[self.view.layer addSublayer:rootLayer];
}

I just performed this test myself, and it appears CALayers actually use the same coordinate system as UIViews, so my assertion that CALayer's flip the Y axis is definitely wrong. However, if you do drawing with CoreGraphics directly, be aware that CoreGraphics does use a flipped Y axis (though when drawing in a UIView subclass or, I assume, a CALayer delegate, the CoreGraphics context has already been flipped to match the UIView's (or CALayer's) coordinate system).

So the short answer, if you made it this far, is the coordinate system for CALayer should match the coordinate system for its corresponding UIView.

Cocoa consistency

Your first two examples are real objects, so they must be instantiated with alloc/init (or the shorthand form, new.

An NSInteger is just a typedef to int or long, depending on the platform, that is, it is just another name for either int or long. Those are both so-called primitive types. They're not real objects, but something much simpler. They don't have any methods, iVars, etc. - in fact, they really are just numbers, while an object representing a number (for example an [NSNumber numberWithInt:1]) is much more.

Primitive types are used when speed and memory efficiency are important, and integers are used so often that those considerations are important here. By the way, Smalltalk (the language that inspired Objective-C's syntax and object model) did not have primitive types. Even integers, even boolean values were objects. This was much more consistent (and more powerful in some regards), but it was one of the factors that made Smalltalk quite slow in the seventies and early eighties.

NSDecimal is also not an object, but a struct. A struct is essentially a list of named primitive values. Other often-used structs in Objective-C where this might be more obvious are CGPoint (A struct containing two floats, x and y), CGSize (again a struct containing two floats, this time called width and height), CGRect (a struct containing a CGPoint struct named origin, and a CGSize struct named size).

what is wrong with this pan gesture recognizer action?

I was unclear as to some of your custom variables and the like, so rather than debugging your code, I thought I'd show you how I'd do the dragging of a view using a UIPanGestureRecognizer.

Thus, assuming (a) the gesture has been added to the view being dragged; and (b) that view is contained within a superview that you want to constrain the movement of the view, the code might look like:

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static CGRect originalFrame;

if (gesture.state == UIGestureRecognizerStateBegan)
{
originalFrame = gesture.view.frame;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint translate = [gesture translationInView:gesture.view.superview];
CGRect newFrame = CGRectMake(originalFrame.origin.x + translate.x,
originalFrame.origin.y + translate.y,
originalFrame.size.width,
originalFrame.size.height);

if (CGRectContainsRect(gesture.view.superview.bounds, newFrame))
gesture.view.frame = newFrame;
}
}

While the above enjoys a certain simplicity, I think we can improve upon the user experience. Notably, the above code will not do anything if the user drags the view outside of it's superview (e.g. drag past the left border and it freezes there, even as you move your finger up and down; not very elegant). I think that the following is a more graceful user interface (but a tiny bit more complicated to read the code), where as the user drags their finger, we'll drag the object to the closest point in the superview that corresponds to where the user's finger is.

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static CGRect originalFrame;

if (gesture.state == UIGestureRecognizerStateBegan)
{
originalFrame = gesture.view.frame;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint translate = [gesture translationInView:gesture.view.superview];
CGRect newFrame = CGRectMake(fmin(gesture.view.superview.frame.size.width - originalFrame.size.width, fmax(originalFrame.origin.x + translate.x, 0.0)),
fmin(gesture.view.superview.frame.size.height - originalFrame.size.height, fmax(originalFrame.origin.y + translate.y, 0.0)),
originalFrame.size.width,
originalFrame.size.height);

gesture.view.frame = newFrame;
}
}

Place images along a bezier path

I guess you want something like this:

arrows demo app

You can find my complete demo app project in this github repository.

Anyway, this is an interesting little problem.

You need to generate an array of points along the path, and I assume you want them to be equally spaced. Generating this points isn't trivial.

Fortunately, Core Graphics contains a function that will do it for you, but it's not obvious which one. The function is CGPathCreateCopyByDashingPath.

First, let's make a UIBezierPath category that creates a dashed copy:

UIBezierPath+Rob_dash.h

#import <UIKit/UIKit.h>

@interface UIBezierPath (Rob_dash)

- (instancetype)Rob_dashedPathWithPattern:(NSArray *)pattern phase:(CGFloat)phase;

@end

UIBezierPath+Rob_dash.m

#import "UIBezierPath+Rob_dash.h"

@implementation UIBezierPath (Rob_dash)

- (instancetype)Rob_dashedPathWithPattern:(NSArray *)pattern phase:(CGFloat)phase {
CGFloat lengths[pattern.count];
size_t i = 0;
for (NSNumber *number in pattern) {
lengths[i++] = number.doubleValue;
}
CGPathRef dashedCGPath = CGPathCreateCopyByDashingPath(self.CGPath, NULL, phase, lengths, pattern.count);
UIBezierPath *dashedPath = [self.class bezierPathWithCGPath:dashedCGPath];
CGPathRelease(dashedCGPath);
return dashedPath;
}

@end

Once we have a dashed path, we need to enumerate the elements of the path (the individual commands like moveToPoint:, addLineToPoint:, and so on). The only way to do that is using another Core Graphics function CGPathApply. Let's write another UIBezierPath category that uses blocks to make it easier. This one's a bit longer:

UIBezierPath+Rob_forEach.h

#import <UIKit/UIKit.h>

typedef void (^Rob_UIBezierPath_moveBlock)(CGPoint destination);
typedef void (^Rob_UIBezierPath_lineBlock)(CGPoint destination);
typedef void (^Rob_UIBezierPath_quadBlock)(CGPoint control, CGPoint destination);
typedef void (^Rob_UIBezierPath_cubicBlock)(CGPoint control0, CGPoint control1, CGPoint destination);
typedef void (^Rob_UIBezierPath_closeBlock)(void);

@interface UIBezierPath (Rob_forEach)

- (void)Rob_forEachMove:(Rob_UIBezierPath_moveBlock)moveBlock line:(Rob_UIBezierPath_lineBlock)lineBlock quad:(Rob_UIBezierPath_quadBlock)quadBlock cubic:(Rob_UIBezierPath_cubicBlock)cubicBlock close:(Rob_UIBezierPath_closeBlock)closeBlock;

@end

UIBezierPath+Rob_forEach.m

#import "UIBezierPath+Rob_forEach.h"

struct ForEachBlocks {
__unsafe_unretained Rob_UIBezierPath_moveBlock moveBlock;
__unsafe_unretained Rob_UIBezierPath_lineBlock lineBlock;
__unsafe_unretained Rob_UIBezierPath_quadBlock quadBlock;
__unsafe_unretained Rob_UIBezierPath_cubicBlock cubicBlock;
__unsafe_unretained Rob_UIBezierPath_closeBlock closeBlock;
};

static void applyBlockToPathElement(void *info, const CGPathElement *element) {
struct ForEachBlocks *blocks = info;
switch (element->type) {
case kCGPathElementMoveToPoint:
if (blocks->moveBlock != nil) {
blocks->moveBlock(element->points[0]);
}
break;
case kCGPathElementAddLineToPoint:
if (blocks->lineBlock != nil) {
blocks->lineBlock(element->points[0]);
}
break;
case kCGPathElementAddQuadCurveToPoint:
if (blocks->quadBlock) {
blocks->quadBlock(element->points[0], element->points[1]);
}
break;
case kCGPathElementAddCurveToPoint:
if (blocks->cubicBlock) {
blocks->cubicBlock(element->points[0], element->points[1], element->points[2]);
}
break;
case kCGPathElementCloseSubpath:
if (blocks->closeBlock) {
blocks->closeBlock();
}
break;
}
}

@implementation UIBezierPath (Rob_forEach)

- (void)Rob_forEachMove:(Rob_UIBezierPath_moveBlock)moveBlock line:(Rob_UIBezierPath_lineBlock)lineBlock quad:(Rob_UIBezierPath_quadBlock)quadBlock cubic:(Rob_UIBezierPath_cubicBlock)cubicBlock close:(Rob_UIBezierPath_closeBlock)closeBlock {
struct ForEachBlocks blocks = {
.moveBlock = moveBlock,
.lineBlock = lineBlock,
.quadBlock = quadBlock,
.cubicBlock = cubicBlock,
.closeBlock = closeBlock
};
CGPathApply(self.CGPath, &blocks, applyBlockToPathElement);
}

@end

OK, now we want to use these two categories together to dash the path, then walk along the dashes and emit the point at the end of each dash. Note that a dash might consist of multiple contiguous line/curve segments. We need to watch for move commands to know when a dash ends. Also, to draw each arrow at the correct angle, we need to know the tangent of the curve at each point, so we'll compute that also, as a unit vector. In the case of a straight line segment, the tangent vector is parallel to the line segment. In the case of curves, the control point immediately prior to the endpoint of the curve determines the tangent at the endpoint.

UIBezierPath+Rob_points.h

#import <UIKit/UIKit.h>

@interface UIBezierPath (Rob_points)

- (void)Rob_forEachPointAtInterval:(CGFloat)interval perform:(void (^)(CGPoint point, CGVector vector))block;

@end

UIBezierPath+Rob_points.m

#import "UIBezierPath+Rob_points.h"
#import "UIBezierPath+Rob_dash.h"
#import "UIBezierPath+Rob_forEach.h"
#import <tgmath.h>

static CGVector vectorFromPointToPoint(CGPoint tail, CGPoint head) {
CGFloat length = hypot(head.x - tail.x, head.y - tail.y);
return CGVectorMake((head.x - tail.x) / length, (head.y - tail.y) / length);
}

@implementation UIBezierPath (Rob_points)

- (void)Rob_forEachPointAtInterval:(CGFloat)interval perform:(void (^)(CGPoint, CGVector))block {
UIBezierPath *dashedPath = [self Rob_dashedPathWithPattern:@[ @(interval * 0.5), @(interval * 0.5) ] phase:0];
__block BOOL hasPendingSegment = NO;
__block CGPoint pendingControlPoint;
__block CGPoint pendingPoint;
[dashedPath Rob_forEachMove:^(CGPoint destination) {
if (hasPendingSegment) {
block(pendingPoint, vectorFromPointToPoint(pendingControlPoint, pendingPoint));
hasPendingSegment = NO;
}
pendingPoint = destination;
} line:^(CGPoint destination) {
pendingControlPoint = pendingPoint;
pendingPoint = destination;
hasPendingSegment = YES;
} quad:^(CGPoint control, CGPoint destination) {
pendingControlPoint = control;
pendingPoint = destination;
hasPendingSegment = YES;
} cubic:^(CGPoint control0, CGPoint control1, CGPoint destination) {
pendingControlPoint = control1;
pendingPoint = destination;
hasPendingSegment = YES;
} close:nil];
if (hasPendingSegment) {
block(pendingPoint, vectorFromPointToPoint(pendingControlPoint, pendingPoint));
}
}

@end

Now we can find points along a path, and the unit tangent vector at each point. Let's make a custom view that uses this ability in drawRect::

ArrowView.h

#import <UIKit/UIKit.h>

@interface ArrowView : UIView

@property (nonatomic) CGFloat interval;

@end

ArrowView.m

#import "ArrowView.h"
#import "UIBezierPath+Rob_figureEight.h"
#import "UIBezierPath+Rob_points.h"

@implementation ArrowView

- (void)setInterval:(CGFloat)interval {
_interval = interval;
[self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
UIImage *arrow = [UIImage imageNamed:@"right233.png"];
UIBezierPath *path = [UIBezierPath Rob_figureEightInRect:CGRectInset(self.bounds, 40, 40)];
// [path stroke];
[path Rob_forEachPointAtInterval:self.interval perform:^(CGPoint point, CGVector vector) {
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc); {
CGContextTranslateCTM(gc, point.x, point.y);
CGContextConcatCTM(gc, CGAffineTransformMake(vector.dx, vector.dy, -vector.dy, vector.dx, 0, 0));
CGContextTranslateCTM(gc, -0.5 * arrow.size.width, -0.5 * arrow.size.height);
// UIRectFrame((CGRect){ CGPointZero, arrow.size });
[arrow drawAtPoint:CGPointZero];
} CGContextRestoreGState(gc);
}];
}

@end

That's all there is to it, if you want to draw arrow images along a path.

There's a little bonus in my demo app repository. If you go back to the first commit, I implemented a different solution also: a category that takes a path and “arrowizes” it, putting an arrowhead at the end of each subpath. If you combine that with dashing (as I did in that version of the project), you get arrows along the path. But it ended up not looking as nice as using arrow images.



Related Topics



Leave a reply



Submit