How does CALayer convert point from and to its sublayers? - ios

Lets assume we have a 2D space (to simplify situation), and layer S and layer C, where C is sublayer of S.
The conversion process must affect bounds, position of C, transform of C, sublayersTransform of S, anchorPoint of C. My guess was the next:
CGAffineTransform transformToChild(CALayer *S, CALayer *C) {
CGFloat txa = - C.bounds.origin.x - C.bounds.size.width * C.anchorPoint.x;
CGFloat tya = - C.bounds.origin.y - C.bounds.size.height * C.anchorPoint.y;
CGFloat txb = C.position.x;
CGFloat tyb = C.position.y;
CGAffineTransform sublayerTransform = CATransform3DGetAffineTransform(S.sublayerTransform);
CGAffineTransform fromS = CGAffineTransformTranslate(sublayerTransform, txb, tyb);
fromS = CGAffineTransformConcat(fromS, C.affineTransform);
fromS = CGAffineTransformTranslate(fromS, txa, tya);
return fromS;
}
But this is not working when transform of the child layer is not identity (e.g. in case of rotation to M_PI_2 angle).
Whole code with layers:
CALayer *l1 = [CALayer new];
l1.frame = CGRectMake(-40, -40, 80, 80);
l1.bounds = CGRectMake(40, 40, 80, 80);
CALayer *l2 = [CALayer new];
l2.frame = CGRectMake(50, 40, 20, 20);
l2.bounds = CGRectMake(40, 40, 20, 20);
CGAffineTransform t2 = CGAffineTransformMakeRotation(M_PI / 2);
l2.affineTransform = t2;
[l1 addSublayer:l2];
CGAffineTransform toL2 = transformToChild(l1, l2);
CGPoint p = CGPointApplyAffineTransform(CGPointMake(70, 50), toL2);
NSLog(#"Custom Point %#", [NSValue valueWithCGPoint:p]);
p = [l1 convertPoint:CGPointMake(70, 50) toLayer:l2];
NSLog(#"CoreAnimation Point %#", [NSValue valueWithCGPoint:p]);
Comparison to system results:
Custom Point NSPoint: {-50, 80}
CoreAnimation Point NSPoint: {50, 40}

There's an old mailing list thread with some details about this here:
http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00086.html
http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00087.html
those messages are quite old, so e.g. they don't include the effects of the geometryFlipped property, which was added more recently, but that would just add another term onto the merged matrix.

So, I found this way of point conversion to and from sublayer's coordinate space, which works correctly with sublayer's non-identity transformation. sublayersTransform of the super layer is not covered here, but I think it would be not hard to extend these functions to support it.
CGPoint pointToChild(CALayer *C, CGPoint p) {
CGFloat txa = - C.bounds.origin.x - C.bounds.size.width * C.anchorPoint.x;
CGFloat tya = - C.bounds.origin.y - C.bounds.size.height * C.anchorPoint.y;
CGFloat txb = C.position.x;
CGFloat tyb = C.position.y;
p.x -= txb;
p.y -= tyb;
p = CGPointApplyAffineTransform(p, CGAffineTransformInvert(C.affineTransform));
if (C.isGeometryFlipped) {
CGAffineTransform flip = CGAffineTransformMakeScale(1.0f, -1.0f);
flip = CGAffineTransformTranslate(flip, 0, C.bounds.size.height * (2.0f * C.anchorPoint.y - 1.0f));
p = CGPointApplyAffineTransform(p, CGAffineTransformInvert(flip));
}
p.x -= txa;
p.y -= tya;
return p;
}
CGPoint pointFromChild(CALayer *C, CGPoint p) {
CGFloat txb = - C.bounds.origin.x - C.bounds.size.width * C.anchorPoint.x;
CGFloat tyb = - C.bounds.origin.y - C.bounds.size.height * C.anchorPoint.y;
CGFloat txa = C.position.x;
CGFloat tya = C.position.y;
p.x += txb;
p.y += tyb;
if (C.isGeometryFlipped) {
CGAffineTransform flip = CGAffineTransformMakeScale(1.0f, -1.0f);
flip = CGAffineTransformTranslate(flip, 0, C.bounds.size.height * (2.0f * C.anchorPoint.y - 1.0f));
p = CGPointApplyAffineTransform(p, flip);
}
p = CGPointApplyAffineTransform(p, C.affineTransform);
p.x += txa;
p.y += tya;
return p;
}

Related

How to draw line chart with smooth curve in iOS?

I have some points like
(x1,y1), (x2,y2), (x3,y3)...
Now I want to draw a chart with smooth curve?
I'm trying to draw as below
-(void)drawPrices
{
NSInteger count = self.prices.count;
UIBezierPath *path = [UIBezierPath bezierPath];
path.lineCapStyle = kCGLineCapRound;
for(int i=0; i<count-1; i++)
{
CGPoint controlPoint[2];
CGPoint p = [self pointWithIndex:i inData:self.prices];
if(i==0)
{
[path moveToPoint:p];
}
CGPoint nextPoint, previousPoint, m;
nextPoint = [self pointWithIndex:i+1 inData:self.prices];
previousPoint = [self pointWithIndex:i-1 inData:self.prices];
if(i > 0) {
m.x = (nextPoint.x - previousPoint.x) / 2;
m.y = (nextPoint.y - previousPoint.y) / 2;
} else {
m.x = (nextPoint.x - p.x) / 2;
m.y = (nextPoint.y - p.y) / 2;
}
controlPoint[0].x = p.x + m.x * 0.2;
controlPoint[0].y = p.y + m.y * 0.2;
// Second control point
nextPoint = [self pointWithIndex:i+2 inData:self.prices];
previousPoint = [self pointWithIndex:i inData:self.prices];
p = [self pointWithIndex:i + 1 inData:self.prices];
m = zeroPoint;
if(i < self.prices.count - 2) {
m.x = (nextPoint.x - previousPoint.x) / 2;
m.y = (nextPoint.y - previousPoint.y) / 2;
} else {
m.x = (p.x - previousPoint.x) / 2;
m.y = (p.y - previousPoint.y) / 2;
}
controlPoint[1].x = p.x - m.x * 0.2;
controlPoint[1].y = p.y - m.y * 0.2;
[path addCurveToPoint:p controlPoint1:controlPoint[0] controlPoint2:controlPoint[1]];
}
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.path = path.CGPath;
lineLayer.lineWidth = LINE_WIDTH;
lineLayer.strokeColor = _priceColor.CGColor;
lineLayer.fillColor = [UIColor clearColor].CGColor;
[self.layer addSublayer:lineLayer];
}
but in some situation, the line will "go back" like
Is there any better way to do that?
I've got you another solution which I used to success.
It requires SpriteKit, which has a fantastic tool called SKKeyFrameSequence which is capable of spline interpolation as shown in this tutorial provided by Apple.
So the idea is that you'll create the correct SKKeyFrameSequence object like this (assuming your data is in an array of (CGFloat, CGFloat) (x, y) tuples):
let xValues = data.map { $0.0 }
let yValues = data.map { $0.1 }
let sequence = SKKeyFrameSequence(keyFrameValues: yValues,
times: xValues.map { NSNumber($0) })
sequence.interpolationMode = .spline
let xMin = xValues.min()!
let xMax = xValues.max()!
Then, if you divide the interpolated spline into 200 pieces (change this value if you want, but for me this resulted in smooth waves to human eyes), you can draw a path consisting of small lines.
var splinedValues = [(CGFloat, CGFloat)]()
stride(from: xMin, to: xMax, by: (xMax - xMin) / 200).forEach {
splinedValues.append((CGFloat($0),
sequence.sample(atTime: CGFloat($0)) as! CGFloat))
}
Then finally the path (I will use SwiftUI, but you can use UIKit the same way too.):
Path { path in
path.move(to: CGPoint(x: splinedValues[0].0, y: splinedValues[0].1))
for splineValue in splinedValues.dropFirst() {
path.addLine(to: CGPoint(x: splineValue.0, y: splineValue.1))
}
}
For the values
[(1, 4), (2, 6), (3, 7), (4, 5), (5, 3), (6, -1), (7, -2), (8, -2.5), (9, -2), (10, 0), (11, 4)]
I've gotten a graph like this with the method described above: (I added the points too to evaluate the result better)
I find the answer at Draw Graph curves with UIBezierPath
And try implement with the code
+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points
{
UIBezierPath *path = [UIBezierPath bezierPath];
NSValue *value = points[0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:p1];
if (points.count == 2) {
value = points[1];
CGPoint p2 = [value CGPointValue];
[path addLineToPoint:p2];
return path;
}
for (NSUInteger i = 1; i < points.count; i++) {
value = points[i];
CGPoint p2 = [value CGPointValue];
CGPoint midPoint = midPointForPoints(p1, p2);
[path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
[path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];
p1 = p2;
}
return path;
}
static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) {
CGPoint controlPoint = midPointForPoints(p1, p2);
CGFloat diffY = abs(p2.y - controlPoint.y);
if (p1.y < p2.y)
controlPoint.y += diffY;
else if (p1.y > p2.y)
controlPoint.y -= diffY;
return controlPoint;
}
Thanks user1244109 ^_^.

iphone: Core Animation: Reversing animation after hitting view boundary

I am trying hands at Core Animation on iphone.
Details of what I have done so far:
I am using layers and CAKeyFrameAnimation using path.
I have created a layer with Contents set to a bitmap file of a fly which I want to animate on a spiral path. The centre point of the spiral path is at CGPoint (150,150). The end point of the spiral path is a radius of 100.0f.
What I want to achieve:
I further want to increase the radius to a value so that the spiral can go beyond the bounds of the view frame, but when it reaches the bound, I desire the fly to trace back the path.
EDIT: (adding code):
-(IBAction) clickToAnimate:(id) sender
{
//create a path to animate the fly
CGFloat minRadius = 5.0f;
CGFloat maxRadius = 100.0f;
CGFloat radiusOffset = 5.0f;
float i;
int count =1;
int remainder;
curvePath = CGPathCreateMutable();
//this looping mimics the "spiral path"
for (i = minRadius; i <= maxRadius; i = i + 5.0f)
{
remainder = count % 2;
if (remainder == 0)
{
CGPathAddArc(curvePath, NULL, currentPoint.x, currentPoint.y, i, M_PI / 2, 1.5 * M_PI, NO);
}
else
{
CGPathAddArc(curvePath, NULL, currentPoint.x, currentPoint.y + radiusOffset, i, 1.5 * M_PI , M_PI / 2, NO);
}
count = count +1;
}
//add timer
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(checkCoordinatesOnTimer) userInfo:self repeats:YES];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.calculationMode = kCAAnimationPaced;
animation.path = curvePath;
animation.duration = 25.0f;
animation.autoreverses = YES;
animation.fillMode = kCAFillModeFrozen;
CGPathRelease(curvePath);
[animationLayer addAnimation:animation forKey:#"position"];
}
Approach taken:
I set up a timer, which would check the co-ordinates of the animation layer every half second. The co-ordinates would be checked against the bounding x and y co-ordinates. If the co-ordinates are found to cross the boundary, we will disable the timer and start with the reverse animation process.
The reverse animation process would first check the angle from horizontal at which the animation is when it crosses the boundary. Using this angle and radius from center point, create a path for reverse animation. Details can be found in code below.
- (void) checkCoordinatesOnTimer
{
if (!presentLayer)
{
presentLayer =[[CALayer alloc] init];
}
presentLayer = [animationLayer presentationLayer];
CGPoint curPt;
curPt.x = presentLayer.position.x;
curPt.y = presentLayer.position.y;
currRadius = sqrt(pow(fabsf(curPt.x - centerPoint.x), 2) + pow(fabsf(curPt.y - centerPoint.y), 2));
if ((curPt.x >= CGRectGetMaxX(self.view.frame)) || (curPt.y >= CGRectGetMaxY(self.view.frame)))
{
[timer invalidate];
[self reverseAnimation:curPt];
}
}
-(void) reverseAnimation:(CGPoint)curPoint
{
CGFloat angle = (CGFloat)[self calculateAngleInRadians:curPoint];
maxRadius = currRadius;
float i;
int count =1;
int remainder;
BOOL firstLap = YES;
CGPathRelease(curvePath);
curvePath = CGPathCreateMutable();
for (i = maxRadius; i >= minRadius; i = i - 5.0f)
{
remainder = count % 2 ;
if (firstLap)
{
if ((angle >= (M_PI/2)) && (angle <= 1.5 *M_PI))
//2nd and third quard,
//angle to change from final angle to 90
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, angle, 1.5 * M_PI, FALSE);
}
else
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, angle, M_PI / 2 , FALSE);
}
firstLap = NO;
continue;
}
else
{
if (remainder == 0)
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, 1.5* M_PI, M_PI / 2, FALSE);
}
else
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y + radiusOffset, i, M_PI / 2 ,1.5* M_PI, FALSE);
}
}
count = count +1;
}
animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.calculationMode =kCAAnimationPaced; // for even pacing of animation in segments.
animation.path = curvePath;
animation.duration = 40.0f;
animation.fillMode = kCAFillModeForwards;
CGPathRelease(curvePath);
[animationLayer addAnimation:animation forKey:#"position"];
}
-(CGFloat) calculateAngleInRadians:(CGPoint) pt
{
CGFloat xLen = fabsf(centerPoint.x - pt.x);
CGFloat yLen = fabsf(centerPoint.y - pt.y);
double value = yLen/xLen;
double angle = atan(value);
double angleInDeg = ( 180 * angle/ M_PI);
double finalAngle;
if ((pt.x > centerPoint.x) || (pt.y > centerPoint.y)) {
finalAngle = angle;
}
else if ((centerPoint.x > pt.x) || (pt.y > centerPoint.y))
{
finalAngle = M_PI/2 + angle;
}
else if ((centerPoint.x > pt.x) || (centerPoint.y > pt.y))
{
finalAngle = M_PI + angle;
}
else {
finalAngle = (1.5 * M_PI) + angle;
}
return finalAngle;
}

Changing the speed of a CAKeyframeAnimation mid-animation in iOS

I am trying to allow the user to change the speed of my animation. I am making a CAKeyframeAnimation with a bezier path, and I can get it to display and run correctly.
I try to change the speed by creating a new animation path with a different duration. The planes go back to the beginning (which I haven't tried to fix yet) and do speed up. The path that they are being drawn on goes away at the time it would have had the animation never changed speeds. When the planes finish, another appears at the point at which the animation was paused in the first place. I have no idea what I'm doing wrong. My question is similar to this one modifying dynamically the duration of CAKeyframeAnimation, but I don't understand what the OP said about finally using blocks.
//The first two methods are in a class subclassing UIView
/** Pause each plane's animation */
- (void)pauseAnimation
{
CFTimeInterval pausedTime = [[self layer] convertTime:CACurrentMediaTime() fromLayer:nil];
[self layer].speed = 0.0;
[self layer].timeOffset = pausedTime;
}
/** Resume each plane's animation */
- (void)resumeAnimation
{
CFTimeInterval pausedTime = [[self layer] timeOffset];
[self layer].speed = 1.0;
[self layer].timeOffset = 0.0;
CFTimeInterval timeSincePause = [[self layer] convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
for(SEPlane *plane in planes){
plane.planeAnimationPath.speedMultiplier = 5;
[plane.planeAnimationPath beginAnimation:self];
}
//[self layer].beginTime = timeSincePause;
}
//This method is in the class of planeAnimationPath
/** Begin animating plane along given path */
- (void)beginAnimation:(UIView *) view
{
planeAnimation = nil;
// Create animation layer for animating plane
planeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
planeAnimation.path = [bezierPath CGPath];
planeAnimation.duration = approximateLength/(ANIMATION_SPEED * self.speedMultiplier);
planeAnimation.calculationMode = kCAAnimationPaced;
planeAnimation.fillMode = kCAFillModeForwards;
planeAnimation.rotationMode = kCAAnimationRotateAuto;
planeAnimation.removedOnCompletion = YES;
[planeAnimation setDelegate:self];
// Add animation to image-layer
[imageLayer addAnimation:planeAnimation forKey:animationKey];
// Add image-layer to view
[[view layer] addSublayer:imageLayer];
}
Unlike default animations, which animate from the current position to the target position, CAKeyframeAnimations don't (as far as I can tell). Besides how would you interpret an animation where the current position is not on the path?
The simplest option that I can think of is to do the following in the setter of speedMultiplier:
Create a new animation with the desired path.
Set the duration as if speedMultiplier was 1
Set the speed to speedMultiplier
Set the timeOffset to duration * "percent of new animation already complete"
Add the animation to the layer.
As you might have guessed, the tricky part is step 4. For simple paths this is easy but for arbitrary paths, it will get a bit more complicated. As a starting point, you will need the formula for bezier quadratic and cubic curves. Search for "distance parametrization of bezier curves" and you will find tons of stuff.
Here is a code sample for a simple rectangular path. The window simply has a MPView and a slider:
#implementation MPView {
IBOutlet NSSlider *_slider; // Min=0.0, Max=5.0
CALayer *_hostLayer;
CALayer *_ballLayer;
CAKeyframeAnimation *_ballPositionAnimation;
double _speed;
}
- (void) awakeFromNib
{
CGRect bounds = self.bounds;
[CATransaction begin];
[CATransaction setDisableActions:YES];
_speed = 1.0;
_hostLayer = [CALayer layer];
_hostLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
self.layer = _hostLayer;
self.wantsLayer = YES;
_ballLayer = [CALayer layer];
_ballLayer.bounds = CGRectMake(0, 0, 32, 32);
_ballLayer.position = CGPointMake(40, 40);
_ballLayer.backgroundColor = CGColorGetConstantColor(kCGColorWhite);
_ballLayer.cornerRadius = 16;
_hostLayer.sublayers = #[_ballLayer];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, _ballLayer.position.x, _ballLayer.position.y);
CGPathAddRect(path, NULL, CGRectInset(bounds, 40, 40));
CGPathCloseSubpath(path);
_ballPositionAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
_ballPositionAnimation.path = path;
_ballPositionAnimation.duration = 6;
_ballPositionAnimation.repeatCount = HUGE_VALF;
CGPathRelease(path);
[_ballLayer addAnimation:_ballPositionAnimation forKey:_ballPositionAnimation.keyPath];
[CATransaction commit];
[_slider bind:NSValueBinding toObject:self withKeyPath:#"speed" options:#{NSContinuouslyUpdatesValueBindingOption:#YES}];
}
- (double) speed
{
return _speed;
}
- (void) setSpeed:(double)speed
{
_speed = speed;
CGPoint pos = [(CALayer*)_ballLayer.presentationLayer position];
[CATransaction begin];
[CATransaction setDisableActions:YES];
_ballPositionAnimation.speed = _speed;
_ballPositionAnimation.duration = 5.0;
_ballPositionAnimation.timeOffset = _ballPositionAnimation.duration * [self percentOfPathCompleted:pos];
[_ballLayer addAnimation:_ballPositionAnimation forKey:_ballPositionAnimation.keyPath];
[CATransaction commit];
}
- (double) percentOfPathCompleted:(CGPoint)p
{
CGRect rect = CGRectInset(self.bounds, 40, 40);
double minX = NSMinX(rect);
double minY = NSMinY(rect);
double maxX = NSMaxX(rect);
double maxY = NSMaxY(rect);
double offset = 0.0;
if (p.x == minX && p.y == minY)
return 0.0;
else if (p.x > minX && p.y == minY)
offset = (p.x - minX) / rect.size.width * 0.25;
else if (p.x == maxX && p.y < maxY)
offset = (p.y - minY) / rect.size.height * 0.25 + 0.25;
else if (p.x > minX && p.y == maxY)
offset = (1.0 - (p.x - minX) / rect.size.width) * 0.25 + 0.50;
else
offset = (1.0 - (p.y - minY) / rect.size.height) * 0.25 + 0.75;
NSLog(#"Offset = %f",offset);
return offset;
}
#end

Path 2.0 menu animation xcode

I've downloaded Menu Path 2.0 from https://github.com/yourabi/PathMenuExample/downloads.
The "Add" button make expanding & collapsing an array of menu items (animated menu drawn along a curve).
But I want to make those button expanding/collapsing in a straight line.
Here is code
ExpandableNavigation.m:
- (void) expand {
transition = YES;
[UIView animateWithDuration:self.speed animations:^{
self.mainButton.transform = CGAffineTransformMakeRotation( 45.0 * M_PI/180 );
}];
for (UIView* view in self.menuItems) {
int index = [self.menuItems indexOfObject:view];
CGFloat oneOverCount = self.menuItems.count + 50<=1?1.0:(1.0/(self.menuItems.count-1));
CGFloat indexOverCount = index *oneOverCount;
CGFloat rad =(1.0 - indexOverCount) * 90.0 * M_PI/180;
CGAffineTransform rotation = CGAffineTransformMakeRotation( rad ) ;
CGFloat x = (self.radius + self.bounce * self.radius ) * rotation.a;
CGFloat y = (self.radius + self.bounce * self.radius ) * rotation.c;
CGPoint center = CGPointMake( view.center.x + x , view.center.y + y);
For anyone needs in future, change the angle with your desired angle of transformation.
- (void) expand {
transition = YES;
[UIView animateWithDuration:self.speed animations:^{
self.mainButton.transform = CGAffineTransformMakeRotation( 45.0 * M_PI/180 );
}];
for (UIView* view in self.menuItems) {
int index = [self.menuItems indexOfObject:view];
CGFloat oneOverCount = self.menuItems.count + 50<=1?1.0:(1.0/(self.menuItems.count-1));
CGFloat indexOverCount = index *oneOverCount;
CGFloat rad =(1.0 - indexOverCount) * 90.0 * M_PI/180;
CGAffineTransform rotation = CGAffineTransformMakeRotation( rad ) ;
CGFloat x = yourDesired_X; //should be same for all menu items
CGFloat y = (view.frame.size.hieght+8.0)*index; //Y should be increasing or decreasing for every item
CGPoint center = CGPointMake(x , y);

Turn two CGPoints into a CGRect

How can I, given two different CGPoints, turn them into an CGRect?
Example:
CGPoint p1 = CGPointMake(0,10);
CGPoint p2 = CGPointMake(10,0);
How can I turn this into a CGRect?
This will take two arbitrary points and give you the CGRect that has them as opposite corners.
CGRect r = CGRectMake(MIN(p1.x, p2.x),
MIN(p1.y, p2.y),
fabs(p1.x - p2.x),
fabs(p1.y - p2.y));
The smaller x value paired with the smaller y value will always be the origin of the rect (first two arguments). The absolute value of the difference between x values will be the width, and between y values the height.
A slight modification of Ken's answer. Let CGGeometry "standardize" the rect for you.
CGRect rect = CGRectStandardize(CGRectMake(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y));
Swift extension:
extension CGRect {
init(p1: CGPoint, p2: CGPoint) {
self.init(x: min(p1.x, p2.x),
y: min(p1.y, p2.y),
width: abs(p1.x - p2.x),
height: abs(p1.y - p2.y))
}
}
Assuming p1 is the origin and the other point is the opposite corner of a rectangle, you could do this:
CGRect rect = CGRectMake(p1.x, p1.y, fabs(p2.x-p1.x), fabs(p2.y-p1.y));
This function takes any number of CGPoints and gives you the smallest CGRect back.
CGRect CGRectSmallestWithCGPoints(CGPoint pointsArray[], int numberOfPoints)
{
CGFloat greatestXValue = pointsArray[0].x;
CGFloat greatestYValue = pointsArray[0].y;
CGFloat smallestXValue = pointsArray[0].x;
CGFloat smallestYValue = pointsArray[0].y;
for(int i = 1; i < numberOfPoints; i++)
{
CGPoint point = pointsArray[i];
greatestXValue = MAX(greatestXValue, point.x);
greatestYValue = MAX(greatestYValue, point.y);
smallestXValue = MIN(smallestXValue, point.x);
smallestYValue = MIN(smallestYValue, point.y);
}
CGRect rect;
rect.origin = CGPointMake(smallestXValue, smallestYValue);
rect.size.width = greatestXValue - smallestXValue;
rect.size.height = greatestYValue - smallestYValue;
return rect;
}
This will return a rect of width or height 0 if the two points are on a line
float x,y,h,w;
if (p1.x > p2.x) {
x = p2.x;
w = p1.x-p2.x;
} else {
x = p1.x;
w = p2.x-p1.x;
}
if (p1.y > p2.y) {
y = p2.y;
h = p1.y-p2.y;
} else {
y = p1.y;
h = p2.y-p1.y;
}
CGRect newRect = CGRectMake(x,y,w,h);
let r0 = CGRect(origin: p0, size: .zero)
let r1 = CGRect(origin: p1, size: .zero)
let rect = r0.union(r1).standardized

Resources