How to curve CGMutablePath? - ios

With the following shape:
I was wondering how do you get it to curve like this:
Also similarly:
I'm assuming that the all of the circles / lines are packed into one CGMutablePath, and then some kind of curve, arc, or quad curve, is applied to it, though I'm having trouble coming even close to replicating it. Does anyone know how to do this?

In your first example, you start with a path that has several closed subpaths. Apparently you want to warp the centers of the subpaths, but leave the individual subpaths unwarped relative to their (new) centers. I'm going to ignore that, because the solution even without that is already terribly complex.
So, let's consider how to define the “warp field”. We'll use three control points:
The warp leaves fixedPoint unchanged. It moves startPoint to endPoint by rotation and scaling, not by simply interpolating the coordinates.
Furthermore, it applies the rotation and scaling based on distance from fixedPoint. And not just based on the simple Euclidean distance. Notice that we don't want apply any rotation or scaling to the top endpoints of the “V” shape in the picture, even though those endpoints are a measurable Euclidean distance from fixedPoint. We want to measure distance along the fixedPoint->startPoint vector, and apply more rotation/scaling as that distance increases.
This all requires some pretty heavy trigonometry. I'm not going to try to explain the details. I'm just going to dump code on you, as a category on UIBezierPath:
UIBezierPath+Rob_warp.h
#import <UIKit/UIKit.h>
#interface UIBezierPath (Rob_warp)
- (UIBezierPath *)Rob_warpedWithFixedPoint:(CGPoint)fixedPoint startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
#end
UIBezierPath+Rob_warp.m
Note that you'll need the Rob_forEach category from this answer.
#import "UIBezierPath+Rob_warp.h"
#import "UIBezierPath+Rob_forEach.h"
#import <tgmath.h>
static CGPoint minus(CGPoint a, CGPoint b) {
return CGPointMake(a.x - b.x, a.y - b.y);
}
static CGFloat length(CGPoint vector) {
return hypot(vector.x, vector.y);
}
static CGFloat dotProduct(CGPoint a, CGPoint b) {
return a.x * b.x + a.y * b.y;
}
static CGFloat crossProductMagnitude(CGPoint a, CGPoint b) {
return a.x * b.y - a.y * b.x;
}
#implementation UIBezierPath (Rob_warp)
- (UIBezierPath *)Rob_warpedWithFixedPoint:(CGPoint)fixedPoint startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGPoint startVector = minus(startPoint, fixedPoint);
CGFloat startLength = length(startVector);
CGPoint endVector = minus(endPoint, fixedPoint);
CGFloat endLength = length(minus(endPoint, fixedPoint));
CGFloat scale = endLength / startLength;
CGFloat dx = dotProduct(startVector, endVector);
CGFloat dy = crossProductMagnitude(startVector, endVector);
CGFloat radians = atan2(dy, dx);
CGPoint (^warp)(CGPoint) = ^(CGPoint input){
CGAffineTransform t = CGAffineTransformMakeTranslation(-fixedPoint.x, -fixedPoint.y);
CGPoint inputVector = minus(input, fixedPoint);
CGFloat factor = dotProduct(inputVector, startVector) / (startLength * startLength);
CGAffineTransform w = CGAffineTransformMakeRotation(radians * factor);
t = CGAffineTransformConcat(t, w);
CGFloat factoredScale = pow(scale, factor);
t = CGAffineTransformConcat(t, CGAffineTransformMakeScale(factoredScale, factoredScale));
// Note: next line is not the same as CGAffineTransformTranslate!
t = CGAffineTransformConcat(t, CGAffineTransformMakeTranslation(fixedPoint.x, fixedPoint.y));
return CGPointApplyAffineTransform(input, t);
};
UIBezierPath *copy = [self.class bezierPath];
[self Rob_forEachMove:^(CGPoint destination) {
[copy moveToPoint:warp(destination)];
} line:^(CGPoint destination) {
[copy addLineToPoint:warp(destination)];
} quad:^(CGPoint control, CGPoint destination) {
[copy addQuadCurveToPoint:warp(destination) controlPoint:warp(control)];
} cubic:^(CGPoint control0, CGPoint control1, CGPoint destination) {
[copy addCurveToPoint:warp(destination) controlPoint1:warp(control0) controlPoint2:warp(control1)];
} close:^{
[copy closePath];
}];
return copy;
}
#end
Ok, so how do you use this crazy thing? In the case of a path like the “V” in the example, you could do it like this:
CGRect rect = path.bounds;
CGPoint fixedPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint startPoint = CGPointMake(fixedPoint.x, CGRectGetMaxY(rect));
path = [path Rob_warpedWithFixedPoint:fixedPoint startPoint:startPoint endPoint:endAnchor];
I'm computing fixedPoint as the center of the top edge of the path's bounding box, and startPoint as the center of the bottom edge. The endAnchor is under user control in my test program. It looks like this in the simulator:
A bubble-type path looks like this:
You can find my test project here: https://github.com/mayoff/path-warp

Related

How should I do to get a UIView instance rotation angle when it is in rotation animation status?

I want to do a demo like the game called aa. So I need to get the real-time angle about the view rotation. The following codes just can obtain final angel:
CGFloat rotationAngle = atan2f(_view.transform.b, _view.transform.a);
CGFloat degreeAngle = rotationAngle * (180 / M_PI);
In my mind, if I can know coordinates of the point insert pin during the central view is rotation, I maybe finish this demo. For example,this point can make like this:
So I want to know how to rotate the view of the current has obtained is how many degrees, or other way to realize game aa.
After some times testing, these codes can solve this problem:
- (CGFloat)transformAngleWithRotationDirection:(BOOL)clockwise {
return clockwise ? [self transformRotationAngle] : 2.0f * M_PI - [self transformRotationAngle];
}
- (CGFloat)transformRotationAngle {
CGFloat degreeAngle = - atan2f(self.presentationLayer.transform.m21, self.presentationLayer.transform.m22);
if (degreeAngle < 0.0f) {
degreeAngle += (2.0f * M_PI);
}
return degreeAngle;
}

Restrict Touch on ColorPicker UIView - iOS

I am using third party Color picker wheel (ISColorWheel- https://github.com/justinmeiners/ios-color-wheel) to pick a color and display it on screen. I need to restrict selecting blue color if particular button is enabled.
When i see the color picker library class, they have implemented following code to restrict the Knob view to move around the color picker.
- (void)setTouchPoint:(CGPoint)point
{
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
CGPoint center = CGPointMake(width / 2.0, height / 2.0);
// Check if the touch is outside the wheel
if (ISColorWheel_PointDistance(center, point) < _radius)
{
//NSLog(#"Distance is %f and Radius is %f",ISColorWheel_PointDistance(center, point),_radius);
_touchPoint = point;
}
else
{
// If so we need to create a drection vector and calculate the constrained point
CGPoint vec = CGPointMake(point.x - center.x, point.y - center.y);
float extents = sqrtf((vec.x * vec.x) + (vec.y * vec.y));
vec.x /= extents;
vec.y /= extents;
_touchPoint = CGPointMake(center.x + vec.x * _radius, center.y + vec.y * _radius);
NSLog(#"Touch Point is %f %f",_touchPoint.x,_touchPoint.y);
}
[self updateKnob];
}
The above code restrict the user to move knobView away from the circle. In my case i need to restrict the user not to select Blue color of ColorPicker. How can i implement it. How to find the trajectory of Blue color.
You should define a triangle that defines the color blue as you see it (How much green dose it contain in one side how much purple on the other) then look for you Point inside that triangle. One way to do it is here: https://stackoverflow.com/a/9755252/1870192

Check view is completely covered by other views

I have a UIView that I have adjusted its layer to make it appear as a circle. (view.layer.cornerRadius = view.frame.size.height/2)
There is also n other smaller circles created this way.
The aim of the user is to completely cover the first circle with the smaller circles by dragging and dropping them over the circle.
How can I check that the large circle has been completely covered?
I have looked at this question Determine whether UIView is covered by other views? but I am unsure of how to obtain the UIBezierPath of the views layer.
Any help is appreciated, Thanks!
You can construct accumulatedPath with this answer Determine whether UIView is covered by other views? using this method:
+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect
Then you can enumerate some points on you view circle and ask path about:
- containsPoint:
Example code:
- (BOOL)isCircleView:(UIView *)view coveredWith:(UIBezierPath *)path
{
if (![path containsPoint:view.center])
return NO;
CGFloat r = CGRectGetWidth(view.bounds)/2;
for (CGFloat angle = 0; angle < 360; angle += 0.5) {
CGFloat alpha = angle/180*M_PI;
CGFloat x = view.center.x + r*cos(alpha);
CGFloat y = view.center.y + r*sin(alpha);
if (![path containsPoint:CGPointMake(x,y)])
return NO;
}
return YES;
}
This algorithm uses 720 points on your circle view bounds and center point. More points you'll use – more accurate result you will get.
But there is possible situation when border line is hidden and center is hidden but some part are visible. So we can add one more loop to this method before return YES;:
for (CGFloat x = view.center.x - r; x < view.center.x + r; x += 4)
for (CGFloat y = view.center.y - r; y < view.center.y + r; y += 4)
{
// Comparing distance to center with radius
if (pow(x-view.center.x,2)+pow(y-view.center.y,2) > pow(r,2))
continue;
if (![path containsPoint:CGPointMake(x,y)])
return NO;
}
You can also configure grid step for more accurate result.
UPDATE:
Here is more common method to check if one UIBezierPath is fully overlapped with another UIBezierPath. Third argument will help you to get more accurate result, try to use values like 10, 100.
- (BOOL)isPath:(UIBezierPath *)path overlappedBy:(UIBezierPath *)superPath granularity:(NSInteger)granularity
{
for (NSInteger i = 0; i < granularity; i++)
for (NSInteger j = 0; j < granularity; j++)
{
CGFloat x = CGRectGetMinX(path.bounds) + i*CGRectGetWidth(path.bounds)/granularity;
CGFloat y = CGRectGetMinY(path.bounds) + j*CGRectGetHeight(path.bounds)/granularity;
if (![path containsPoint:CGPointMake(x,y)])
continue;
if (![superPath containsPoint:CGPointMake(x,y)])
return NO;
}
return YES;
}
For circles case I recommend to use first solution, for random shapes – second solution.
To get the UIBezierPath of the views you can use this method:
+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect
using the views frame as the rect. As your views have a square shape, you'll get an UIBezierPath that corresponds to your circle.
You then combine all your path into one that you compare with the original circle path.
There would be another possible solution to get bezier path of a view. You should create a UIView class. Make this class a parent of all your circle view. Also declare a UIBezierPath as a property. When you create any circle view assign bezier path.
Next time when you touch your view, you can get its path by accessing its property.

How to set specific areas of uiimage to process touch gestures?

I'm making a photo hunt style app. I've got a number of X-Rays and I need to set specific areas of the uiimage to process touch events as correct and others as incorrect.
I understand that I can use the code below to get the tap location in the image view but how do I declare an area on the image view as correct and compare it to the tap location value?
CGPoint tapLocation = [gesture locationInView:self.imagePlateA];
Any help much appreciated!
So you have to programmatically create "regions" and test to see whether or not they're in that region after you get that point. For example:
//Get the tap location
CGPoint tapLocation = [gesture locationInView:self.imagePlateA];
if ([self checkIfTap:tapLocation inRegionWithCenter:CGPointMake(someX, someY) radius:radius]) {
//YAY WE'RE WITHIN THE BOUNDS OF A CIRCLE AT POINT (someX, someY)
//THAT HAS A RADIUS OF radius
}
and the method of checkIfTap: inRegionWithCenter: radius: can be defined like this:
- (BOOL)checkIfTap:(CGPoint)tapLocation inRegionWithCenter:(CGPoint)center radius:(CGFloat)radius {
CGFloat dx = tapLocation.x - center.x;
CGFloat dy = tapLocation.y - center.y;
//Pythagorean theorem
if (sqrt(dx * dx + dy * dy) < radius) {
return YES;
} else {
return NO;
}
}
If the correct locations of the image is a CGRect rather than points, you could use CGRectContainsPoint()
CGGeometry Reference

How to detect building tap by getting alpha from tile?

I've been trying to get my MKMapView to detect whether or not a tap was on a tile with alpha > 0. I'm quite new at ObjC and Xcode as well so this functionality is a bit over my head. All help will me greatly appreciated!
So far I've tried many different strategies but always come up short. We have custom classes to replace MKOverlay and MKOverlayView that implement each respectively so I've been trying to grab the tiles when they're created and save them to an array to later reference in the MKMapViewController when the map is touched.
- (NSArray *)tilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale
{
NSInteger z = zoomScaleToZoomLevel(scale);
// Number of tiles wide or high (but not wide * high)
NSInteger tilesAtZ = pow(2, z);
NSInteger minX = floor((MKMapRectGetMinX(rect) * scale) / TILE_SIZE);
NSInteger maxX = floor((MKMapRectGetMaxX(rect) * scale) / TILE_SIZE);
NSInteger minY = floor((MKMapRectGetMinY(rect) * scale) / TILE_SIZE);
NSInteger maxY = floor((MKMapRectGetMaxY(rect) * scale) / TILE_SIZE);
NSMutableArray *tiles = nil;
for (NSInteger x = minX; x <= maxX; x++) {
for (NSInteger y = minY; y <= maxY; y++) {
// As in initWithTilePath, need to flip y index to match the gdal2tiles.py convention.
NSInteger flippedY = abs(y + 1 - tilesAtZ);
NSString *tileKey = [[NSString alloc] initWithFormat:#"%d/%d/%d", z, x, flippedY];
if ([tilePaths containsObject:tileKey]) {
if (!tiles) {
tiles = [NSMutableArray array];
}
MKMapRect frame = MKMapRectMake((double)(x * TILE_SIZE) / scale,
(double)(y * TILE_SIZE) / scale,
TILE_SIZE / scale,
TILE_SIZE / scale);
NSString *path = [[NSString alloc] initWithFormat:#"%#/%#.png", tileBase, tileKey];
ImageTile *tile = [[ImageTile alloc] initWithFrame:frame path:path];
[tiles addObject:tile];
[myTiles addObject:tile];
[path release];
[tile release];
}
[tileKey release];
}
}
return tiles;
}
That's where I populate the array which is a "class variable". If I comment out the [tiles addObject:tile]; I get the background of the map drawn but no buildings so I think adding specifically those tiles is correct.
Then in the mapviewController gesture handler function I check if the touch is in the tile.frame which is is for 8 out of 32 (it can be 0 if you click far from the buildings and the total changes when you zoom around, but always gets bigger)which seems like an odd number. But pretending that that works correctly I check the alpha at that point using a modified version of this answerer's function: how to get the RGBA value of UIImage in the specific clicked point
but I don't know if that works for mapView's like it would for imageViews. I think I might need to translate the context but I've never worked with contexts before...
Sorry for so much text! Maybe this isn't even possible? I'll add more code if clarification is needed. Any input would help!

Resources