Rotating Around Certain CGPoint - objective-c

How to rotate around a certain CGPoint from the Given UIView.
The following images defines a CAShapeLayer around which there were four UIView's as top, left, bottom and right:
I have added UIRotationGesture to self.view through which I am rotating CAShapeLayer, so I want to move point of the it to move as well along with the CAShapeLayer like shown in the following image:
The following defines the code that I am using to achieve the same but having some issues with it:
- (void)handleRotation:(UIRotationGestureRecognizer *)recognizer
{
if(recognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint touch1 = [recognizer locationOfTouch:0 inView:_imgBackground];
CGPoint touch2 = [recognizer locationOfTouch:1 inView:_imgBackground];
UIBezierPath *bezierpath = [UIBezierPath bezierPath];
bezierpath.CGPath = shapeLayer.path;
}
else if(recognizer.state == UIGestureRecognizerStateChanged)
{
CGPathRef path = createPathRotatedAroundBoundingBoxCenter(shapeLayer.path, rotationGesture.rotation);
shapeLayer.path = path;
CGPathRelease(path);
rotationGesture.rotation = 0;
}
else if (recognizer.state == UIGestureRecognizerStateEnded)
{
NSMutableArray *rotatingLayerPoints = [NSMutableArray array]; // will contain all the points of object that is been rotated
CGPathApply(shapeLayer.path, (__bridge void * _Nullable)(rotatingLayerPoints), CGPathApplier);
degreeOfRotation = DEGREES_TO_RADIANS([self getDegreeFromStartPoint:[[rotatingLayerPoints objectAtIndex:0] CGPointValue] endPoint:[[rotatingLayerPoints objectAtIndex:1] CGPointValue]]); // getting degree of first line's movement
CGRect frame = CGPathGetBoundingBox(shapeLayer.path);
CGPoint rotationPoint = CGPointMake(frame.origin.x + (frame.size.width/2), frame.origin.y + (frame.size.height/2));// The point we are rotating around
CGFloat minX = CGRectGetMinX(topPoint.frame);
CGFloat minY = CGRectGetMinY(topPoint.frame);
CGFloat width = CGRectGetWidth(topPoint.frame);
CGFloat height = CGRectGetHeight(topPoint.frame);
CGPoint anchorPoint = CGPointMake((rotationPoint.x-minX)/width,
(rotationPoint.y-minY)/height);
topPoint.layer.anchorPoint = anchorPoint;
topPoint.layer.position = rotationPoint;
topPoint.transform = CGAffineTransformMakeRotation(degreeOfRotation);
}
}
void CGPathApplier (void *info, const CGPathElement *element) // used to get points from CGPath
{
NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
CGPoint *points = element->points;
CGPathElementType type = element->type;
switch(type)
{
case kCGPathElementMoveToPoint: // contains 1 point
[bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
break;
case kCGPathElementAddLineToPoint: // contains 1 point
[bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
break;
case kCGPathElementAddQuadCurveToPoint: // contains 2 points
[bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
[bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];
break;
case kCGPathElementAddCurveToPoint: // contains 3 points
[bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
[bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];
[bezierPoints addObject:[NSValue valueWithCGPoint:points[2]]];
break;
case kCGPathElementCloseSubpath: // contains no point
break;
}
}

Related

Couldn't resolve variable issue Objective-C

I'm not much of an Objective-C guy I primarily use swift and I need some help from this error
Variable 'indexOfLargestGap' may be uninitialized when used here
and
Variable 'indexOfSmallestGap' may be uninitialized when used here
I have imported the code from Github and created a bridging header and fixed a few errors but I don't know how to fix this one.
The original file from Github https://github.com/jberlana/JBCroppableView
#import "JBCroppableImageView.h"
#import "JBCroppableLayer.h"
#implementation JBCroppableImageView{
JBCroppableLayer *_pointsView;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.userInteractionEnabled = YES;
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPoints:4];
[self addSubview:_pointsView];
UIPanGestureRecognizer *singlePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(singlePan:)];
singlePan.maximumNumberOfTouches = 1;
[self addGestureRecognizer:singlePan];
UIPanGestureRecognizer *doublePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(doublePan:)];
doublePan.minimumNumberOfTouches = 2;
[self addGestureRecognizer:doublePan];
}
return self;
}
-(void)singlePan:(UIPanGestureRecognizer *)gesture{
CGPoint posInStretch = [gesture locationInView:_pointsView];
if(gesture.state==UIGestureRecognizerStateBegan){
[_pointsView findPointAtLocation:posInStretch];
}
if(gesture.state==UIGestureRecognizerStateEnded){
_pointsView.activePoint.backgroundColor = _pointsView.pointColor;
_pointsView.activePoint = nil;
}
[_pointsView moveActivePointToLocation:posInStretch];
// CGPoint translation = [gesture translationInView:self.imageView];
// [gesture setTranslation:CGPointZero inView:self.imageView];
}
-(void)doublePan:(UIPanGestureRecognizer *)gesture{
NSLog(#"double pan");
CGPoint translation = [gesture translationInView:self];
CGPoint newCenter = CGPointMake(self.center.x+translation.x, self.center.y+translation.y);
[self setCenter:newCenter];
[gesture setTranslation:CGPointZero inView:self];
}
-(void)crop{
[_pointsView maskImageView:self];
[_pointsView removeFromSuperview];
}
-(void)reverseCrop{
self.layer.mask = nil;
[self addSubview:_pointsView];
}
-(UIImage *)getCroppedImage{
return [_pointsView getCroppedImageForView:self withTransparentBorders:NO];
}
-(UIImage *)getCroppedImageWithTransparentBorders:(BOOL)transparent{
return [_pointsView getCroppedImageForView:self withTransparentBorders:transparent];
}
- (void)addPoint{
self.layer.mask = nil;
NSMutableArray *oldPoints = [_pointsView.getPoints mutableCopy];
// CGPoint new = CGPointMake((first.x+last.x)/2.0f, (first.y+last.y)/2.0f);
NSInteger indexOfLargestGap; //points to here for first warning
CGFloat largestGap = 0;
for(int i=0; i< oldPoints.count-1; i++){
CGPoint first = [[oldPoints objectAtIndex:i] CGPointValue];
CGPoint last = [[oldPoints objectAtIndex:i+1] CGPointValue];
CGFloat distance = [self distanceBetween:first And:last];
if(distance>largestGap){
indexOfLargestGap = i+1;
largestGap = distance;
}
}
CGPoint veryFirst = [[oldPoints firstObject] CGPointValue];
CGPoint veryLast = [[oldPoints lastObject] CGPointValue];
CGPoint new;
if([self distanceBetween:veryFirst And:veryLast]>largestGap){
indexOfLargestGap = oldPoints.count;
new = CGPointMake((veryFirst.x+veryLast.x)/2.0f, (veryFirst.y+veryLast.y)/2.0f);
} else {
CGPoint first = [[oldPoints objectAtIndex:indexOfLargestGap-1] CGPointValue];//Variable 'indexOfLargestGap' may be uninitialized when used here
CGPoint last = [[oldPoints objectAtIndex:indexOfLargestGap] CGPointValue];
new = CGPointMake((first.x+last.x)/2.0f, (first.y+last.y)/2.0f);
}
[oldPoints insertObject:[NSValue valueWithCGPoint:new] atIndex:indexOfLargestGap];
[_pointsView removeFromSuperview];
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPointsAt:oldPoints];
[self addSubview:_pointsView];
}
- (void)removePoint{
self.layer.mask = nil;
NSMutableArray *oldPoints = [_pointsView.getPoints mutableCopy];
if(oldPoints.count==3) return;
NSInteger indexOfSmallestGap;// points to here for second warning
CGFloat smallestGap = INFINITY;
for(int i=0; i< oldPoints.count; i++){
int firstIndex = i-1;
int lastIndex = i +1;
if(firstIndex<0){
firstIndex = (int)oldPoints.count-1+firstIndex;
} else if(firstIndex>=oldPoints.count){
firstIndex = firstIndex-(int)oldPoints.count;
}
if(lastIndex<0){
lastIndex = (int)oldPoints.count-1+lastIndex;
} else if(lastIndex>=oldPoints.count){
lastIndex = lastIndex-(int)oldPoints.count;
}
CGPoint first = [[oldPoints objectAtIndex:firstIndex] CGPointValue];
CGPoint mid = [[oldPoints objectAtIndex:i] CGPointValue];
CGPoint last = [[oldPoints objectAtIndex:lastIndex] CGPointValue];
CGFloat distance = [self distanceFrom:first to:last throuh:mid];
if(distance<smallestGap){
indexOfSmallestGap = i;
smallestGap = distance;
}
}
[oldPoints removeObjectAtIndex:indexOfSmallestGap]; //Variable 'indexOfSmallestGap' may be uninitialized when used here
[_pointsView removeFromSuperview];
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPointsAt:[NSArray arrayWithArray:oldPoints]];
[self addSubview:_pointsView];
}
-(CGFloat)distanceBetween:(CGPoint)first And:(CGPoint)last{
CGFloat xDist = (last.x - first.x);
if(xDist<0) xDist=xDist*-1;
CGFloat yDist = (last.y - first.y);
if(yDist<0) yDist=yDist*-1;
return sqrt((xDist * xDist) + (yDist * yDist));
}
-(CGFloat)distanceFrom:(CGPoint)first to:(CGPoint)last throuh:(CGPoint)middle{
CGFloat firstToMid = [self distanceBetween:first And:middle];
CGFloat lastToMid = [self distanceBetween:middle And:last];
return firstToMid + lastToMid;
}
-(void)setFrame:(CGRect)frame{
[super setFrame:frame];
[_pointsView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[_pointsView setNeedsDisplay];
}
#end
And I'm getting this from the terminal
index 0 beyond bounds for empty array

Create dashed line in SpriteKit (OS X)

I'm trying to create a dashed line using an SKShapeNode in SpriteKit for Mac OS X in Objective C, not iOS and not in Swift. I've taken a look at many SO articles for this and tried to piece it together. The main issue that a lot of them are in Swift. Here's what I've come up with so far, but it's not showing anything and I'm not sure why:
-(void) drawBarLineDivisions {
...
NSBezierPath *path = [NSBezierPath bezierPath];
CGPoint startPoint = CGPointMake(0, 250);
CGPoint endPoint = CGPointMake(450, 250);
[path moveToPoint:startPoint];
[path lineToPoint:endPoint];
CGFloat pattern[2] = {10.0, 10.0};
CGPathRef dashedLine = CGPathCreateCopyByDashingPath ([self CGPathFromPath:path], nil, 0, pattern, 2);
SKShapeNode *line = [SKShapeNode shapeNodeWithPath:dashedLine];
CGPathRelease(dashedLine);
int x = i/(double)measureDivisions*[self getGridArea]/[self getMeasuresDisplayed];
[line setPosition:CGPointMake(x, 0)];
[line setFillColor:fillColor];
[line setStrokeColor:[NSColor clearColor]];
[self addChild:line];
}
//helper method for converting NSBezierPath into CGPath
- (CGMutablePathRef)CGPathFromPath:(NSBezierPath *)path
{
CGMutablePathRef cgPath = CGPathCreateMutable();
NSInteger n = [path elementCount];
for (NSInteger i = 0; i < n; i++) {
NSPoint ps[3];
switch ([path elementAtIndex:i associatedPoints:ps]) {
case NSMoveToBezierPathElement: {
CGPathMoveToPoint(cgPath, NULL, ps[0].x, ps[0].y);
break;
}
case NSLineToBezierPathElement: {
CGPathAddLineToPoint(cgPath, NULL, ps[0].x, ps[0].y);
break;
}
case NSCurveToBezierPathElement: {
CGPathAddCurveToPoint(cgPath, NULL, ps[0].x, ps[0].y, ps[1].x, ps[1].y, ps[2].x, ps[2].y);
break;
}
case NSClosePathBezierPathElement: {
CGPathCloseSubpath(cgPath);
break;
}
default: NSAssert(0, #"Invalid NSBezierPathElement");
}
}
return cgPath;
}

UIPinchGestureRecognizer trouble

Ok, i've read a few posts on this one (ex. UIImageView Gestures (Zoom, Rotate) Question) but I can't seem to fix my problem.
I have the following setup: an SKScene, an SKNode _backgroundLayer and 9 SKSpriteNodes that are tiles that make up the background and are attached to the _backgroundLayer.
Since these 9 tiles make a 3x3 square and they are quite large, I need to be able to zoom in and look at other SKSpriteNodes that will be on top of these 9 background images.
There are two problems:
1) When I pinch to zoom in or zoom out it seems like it is zooming in/out from location (0,0) of the _backgroundLayer and not from the touch location.
2) I have added some bounds so that the user can not scroll out of the 9 background images. In general it works. However, if I zoom in then move towards the top of the 9 background images and then zoom out the bounding conditions go berserk and the user can see the black space outside the background images. I need a way to limit the amount of zooming out that the user can do depending on where he's at.
Any ideas? Thanks!
I include my code below:
#import "LevelSelectScene.h"
#import "TurtleWorldSubScene.h"
#interface LevelSelectScene ()
#property (nonatomic, strong) SKNode *selectedNode;
#end
#implementation LevelSelectScene
{
SKNode *_backgroundLayer;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
_backgroundLayer = [SKNode node];
_backgroundLayer.name = #"backgroundLayer";
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[_backgroundLayer setScale:0.76];
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
} else {
[_backgroundLayer setScale:0.36];
}
[self addChild:_backgroundLayer];
SKTexture *backgroundTexture = [SKTexture textureWithImageNamed:#"levelSelect"];
int textureID = 0;
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
SKSpriteNode *background = [SKSpriteNode spriteNodeWithTexture:backgroundTexture];
background.anchorPoint = CGPointZero;
background.position = CGPointMake((background.size.width)*i, (background.size.height)*j);
background.zPosition = 0;
background.name = [NSString stringWithFormat:#"background%d", textureID];
textureID++;
[_backgroundLayer addChild:background];
}
}
[TurtleWorldSubScene displayTurtleWorld:self];
}
return self;
}
- (void)didMoveToView:(SKView *)view {
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:panGestureRecognizer];
//UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
// [self.view addGestureRecognizer:tapRecognizer];
UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinch:)];
[[self view] addGestureRecognizer:pinchGestureRecognizer];
}
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
SKNode *node = [self nodeAtPoint:touchLocation];
_selectedNode = node;
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(translation.x, -translation.y);
CGPoint initialPosition = CGPointAdd(_backgroundLayer.position, translation);
_backgroundLayer.position = [self boundLayerPos:initialPosition];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
float scrollDuration = 0.2;
CGPoint velocity = [recognizer velocityInView:recognizer.view];
CGPoint pos = [_backgroundLayer position];
CGPoint p = CGPointMultiplyScalar(velocity, scrollDuration);
CGPoint newPos = CGPointMake(pos.x + p.x, pos.y - p.y);
newPos = [self boundLayerPos:newPos];
[_backgroundLayer removeAllActions];
SKAction *moveTo = [SKAction moveTo:newPos duration:scrollDuration];
[moveTo setTimingMode:SKActionTimingEaseOut];
[_backgroundLayer runAction:moveTo];
}
}
- (void)handlePinch:(UIPinchGestureRecognizer *) recognizer
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if(_backgroundLayer.xScale*recognizer.scale < 0.76) {
//SKSpriteNode *backgroundTile = (SKSpriteNode *)[_backgroundLayer childNodeWithName:#"background0"];
[_backgroundLayer setScale:0.76];
} else if(_backgroundLayer.xScale*recognizer.scale > 2) {
[_backgroundLayer setScale:2.0];
} else {
[_backgroundLayer runAction:[SKAction scaleBy:recognizer.scale duration:0]];
}
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
} else {
if(_backgroundLayer.xScale*recognizer.scale < 0.36) {
[_backgroundLayer setScale:0.36];
} else if(_backgroundLayer.xScale*recognizer.scale > 2) {
[_backgroundLayer setScale:2.0];
} else {
[_backgroundLayer runAction:[SKAction scaleBy:recognizer.scale duration:0]];
}
}
recognizer.scale = 1;
}
- (CGPoint)boundLayerPos:(CGPoint)newPos {
SKSpriteNode *backgroundTile = (SKSpriteNode *)[_backgroundLayer childNodeWithName:#"background0"];
CGPoint retval = newPos;
retval.x = MIN(retval.x, 0);
retval.x = MAX(retval.x, -(backgroundTile.size.width*_backgroundLayer.xScale*3)+self.size.width);
retval.y = MIN(retval.y, 0);
retval.y = MAX(retval.y, -(backgroundTile.size.height*_backgroundLayer.xScale*3)+self.size.height);
return retval;
}

SKSpriteNode rotation not working properly

I'm trying to rotate a SKSpriteNode by using UIRrotationGestureRecognizer. I've implemented a code but sometimes, when I rotate the node it jumps to a rotation that isn't the one it should be. Here you have the code:
- (void) handleRotation:(UIRotationGestureRecognizer *) rotationrecognizer{
CGFloat initialrotation = 0.0;
if (rotationrecognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [rotationrecognizer locationInView:rotationrecognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
[self selectNodeForTouch:touchLocation];
initialrotation = selected.zRotation;
}
else if (rotationrecognizer.state == UIGestureRecognizerStateChanged) {
CGFloat angle = initialrotation + rotationrecognizer.rotation;
selected.zRotation = angle;
}
}
You are resetting initial rotation to 0 on every call... you should move this to be an ivar of your view if you need it to stay resident.
The way you have it written now, the line that sets 'angle' is effectively equal to this:
CGFloat angle = 0 + rotationrecognizer.rotation;
Instead, you should do the following (where initialRotation is defined as a private ivar):
- (void) handleRotation:(UIRotationGestureRecognizer *) rotationrecognizer{
if (rotationrecognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [rotationrecognizer locationInView:rotationrecognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
[self selectNodeForTouch:touchLocation];
_initialrotation = selected.zRotation;
}
else if (rotationrecognizer.state == UIGestureRecognizerStateChanged) {
CGFloat angle = _initialrotation + rotationrecognizer.rotation;
selected.zRotation = angle;
}
}

Recognize a path of user gesture

I'm working on an app with 9 views on screen, and I want the users to connect the views in a way they want, and record their sequence as password.
But I don't know which gesture recognizer I should use.
Should I use CMUnistrokeGestureRecognizer or combination of several swipe gesture or anything else?
Thanks.
You could use a UIPanGestureRecognizer, something like:
CGFloat const kMargin = 10;
- (void)viewDidLoad
{
[super viewDidLoad];
// create a container view that all of our subviews for which we want to detect touches are:
CGFloat containerWidth = fmin(self.view.bounds.size.width, self.view.bounds.size.height) - kMargin * 2.0;
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(kMargin, kMargin, containerWidth, containerWidth)];
container.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:container];
// now create all of the subviews, specifying a tag for each; and
CGFloat cellWidth = (containerWidth - (4.0 * kMargin)) / 3.0;
for (NSInteger column = 0; column < 3; column++)
{
for (NSInteger row = 0; row < 3; row++)
{
UIView *cell = [[UIView alloc] initWithFrame:CGRectMake(kMargin + column * (cellWidth + kMargin),
kMargin + row * (cellWidth + kMargin),
cellWidth, cellWidth)];
cell.tag = row * 3 + column;
cell.backgroundColor = [UIColor lightGrayColor];
[container addSubview:cell];
}
}
// finally, create the gesture recognizer
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(handlePan:)];
[container addGestureRecognizer:pan];
}
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static NSMutableArray *gesturedSubviews;
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == UIGestureRecognizerStateBegan)
{
gesturedSubviews = [NSMutableArray array];
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
CGPoint location = [gesture locationInView:gesture.view];
for (UIView *subview in gesture.view.subviews)
{
if (CGRectContainsPoint(subview.frame, location))
{
if (subview != [gesturedSubviews lastObject])
{
[gesturedSubviews addObject:subview];
// an example of the sort of graphical flourish to give the
// some visual cue that their going over the subview in question
// was recognized
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
subview.alpha = 0.5;
}
completion:^(BOOL finished){
subview.alpha = 1.0;
}];
}
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state == UIGestureRecognizerStateEnded)
{
NSLog(#"We went over:");
for (UIView *subview in gesturedSubviews)
{
NSLog(#" %d", subview.tag);
}
// you might as well clean up your static variable when you're done
gesturedSubviews = nil;
}
}
Obviously, you would create your subviews any way you want, and keep track of them any way you want, but the idea is to have subviews with unique tag numbers, and the gesture recognizer would just see which order you go over them in a single gesture.
Even if I didn't capture precisely what you want, it at least shows you how you can use a pan gesture recognizer to track the movement of your finger from one subview to another.
Update:
If you wanted to draw a path on the screen as the user is signing in, you could create a CAShapeLayer with a UIBezierPath. I'll demonstrate that below, but as a caveat, I feel compelled to point out that this might not be a great security feature: Usually with password entry, you'll show the user enough so that they can confirm that they're doing what they want, but not enough so that someone can glance look over their shoulder and see what the whole password was. When entering a text password, usually iOS momentarily shows you the last key you hit, but quickly turns that into an asterisk so that, at no point, can you see the whole password. Hence my initial suggestion.
But if you really have your heart set on showing the user the path as they draw it, you could use something like the following. First, this requires Quartz 2D. Thus add the QuartzCore.framework to your project (see Linking to a Library or Framework). Second, import the QuartCore headers:
#import <QuartzCore/QuartzCore.h>
Third, replace the pan handler with something like:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static NSMutableArray *gesturedSubviews;
static UIBezierPath *path = nil;
static CAShapeLayer *shapeLayer = nil;
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == UIGestureRecognizerStateBegan)
{
gesturedSubviews = [NSMutableArray array];
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
CGPoint location = [gesture locationInView:gesture.view];
for (UIView *subview in gesture.view.subviews)
{
if (!path)
{
// if the path hasn't be started, initialize it and the shape layer
path = [UIBezierPath bezierPath];
[path moveToPoint:location];
shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 2.0;
[gesture.view.layer addSublayer:shapeLayer];
}
else
{
// otherwise add this point to the layer's path
[path addLineToPoint:location];
shapeLayer.path = path.CGPath;
}
if (CGRectContainsPoint(subview.frame, location))
{
if (subview != [gesturedSubviews lastObject])
{
[gesturedSubviews addObject:subview];
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
subview.alpha = 0.5;
}
completion:^(BOOL finished){
subview.alpha = 1.0;
}];
}
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state == UIGestureRecognizerStateEnded)
{
// assuming the tags are numbers between 0 and 9 (inclusive), we can build the password here
NSMutableString *password = [NSMutableString string];
for (UIView *subview in gesturedSubviews)
[password appendFormat:#"%c", subview.tag + 48];
NSLog(#"Password = %#", password);
// clean up our array of gesturedSubviews
gesturedSubviews = nil;
// clean up the drawing of the path on the screen the user drew
[shapeLayer removeFromSuperlayer];
shapeLayer = nil;
path = nil;
}
}
That yields a path that the user draws as the gesture proceeds:
Rather than showing the path the user draws with each and every movement of the user's finger, maybe you just draw the lines between the center of the subviews, such as:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static NSMutableArray *gesturedSubviews;
static UIBezierPath *path = nil;
static CAShapeLayer *shapeLayer = nil;
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == UIGestureRecognizerStateBegan)
{
gesturedSubviews = [NSMutableArray array];
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
CGPoint location = [gesture locationInView:gesture.view];
for (UIView *subview in gesture.view.subviews)
{
if (CGRectContainsPoint(subview.frame, location))
{
if (subview != [gesturedSubviews lastObject])
{
[gesturedSubviews addObject:subview];
if (!path)
{
// if the path hasn't be started, initialize it and the shape layer
path = [UIBezierPath bezierPath];
[path moveToPoint:subview.center];
shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 2.0;
[gesture.view.layer addSublayer:shapeLayer];
}
else
{
// otherwise add this point to the layer's path
[path addLineToPoint:subview.center];
shapeLayer.path = path.CGPath;
}
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
subview.alpha = 0.5;
}
completion:^(BOOL finished){
subview.alpha = 1.0;
}];
}
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state == UIGestureRecognizerStateEnded)
{
// assuming the tags are numbers between 0 and 9 (inclusive), we can build the password here
NSMutableString *password = [NSMutableString string];
for (UIView *subview in gesturedSubviews)
[password appendFormat:#"%c", subview.tag + 48];
NSLog(#"Password = %#", password);
// clean up our array of gesturedSubviews
gesturedSubviews = nil;
// clean up the drawing of the path on the screen the user drew
[shapeLayer removeFromSuperlayer];
shapeLayer = nil;
path = nil;
}
}
That yields something like:
You have all sorts of options, but hopefully you now have the building blocks so you can design your own solution.
Forgive me Rob, pure Plagiarism here :) Needed the same code in swift 3.0 :) so I translated this great little example you wrote into swift 3.0.
ViewController.swift
import UIKit
class ViewController: UIViewController {
static let kMargin:CGFloat = 10.0;
override func viewDidLoad()
{
super.viewDidLoad()
// create a container view that all of our subviews for which we want to detect touches are:
let containerWidth = fmin(self.view.bounds.size.width, self.view.bounds.size.height) - ViewController.kMargin * 2.0
let container = UIView(frame: CGRect(x: ViewController.kMargin, y: ViewController.kMargin, width: containerWidth, height: containerWidth))
container.backgroundColor = UIColor.darkGray
view.addSubview(container)
// now create all of the subviews, specifying a tag for each; and
let cellWidth = (containerWidth - (4.0 * ViewController.kMargin)) / 3.0
for column in 0 ..< 3 {
for row in 0 ..< 3 {
let cell = UIView(frame: CGRect(x: ViewController.kMargin + CGFloat(column) * (cellWidth + ViewController.kMargin), y: ViewController.kMargin + CGFloat(row) * (cellWidth + ViewController.kMargin), width: cellWidth, height: cellWidth))
cell.tag = row * 3 + column;
container.addSubview(cell)
}
}
// finally, create the gesture recognizer
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
container.addGestureRecognizer(pan)
}
func handlePan(gesture: UIPanGestureRecognizer)
{
var gesturedSubviews : [UIView] = []
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == .began)
{
gesturedSubviews.removeAll()
}
let location = gesture.location(in: gesture.view)
for subview in (gesture.view?.subviews)! {
if (subview.frame.contains(location)) {
if (subview != gesturedSubviews.last) {
gesturedSubviews.append(subview)
subview.backgroundColor = UIColor.blue
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state != .recognized)
{
print("We went over:");
for subview in gesturedSubviews {
print(" %d", (subview as AnyObject).tag);
}
// you might as well clean up your static variable when you're done
}
}
}
}
Update: Almost that is; I tried to translate the update too, but my translation missed something and didn't work, so I searched around SO and crafted a similar if slightly different final solution.
import UIKit
import QuartzCore
class ViewController: UIViewController {
static let kMargin:CGFloat = 10.0;
override func viewDidLoad()
{
super.viewDidLoad()
// create a container view that all of our subviews for which we want to detect touches are:
let containerWidth = fmin(self.view.bounds.size.width, self.view.bounds.size.height) - ViewController.kMargin * 2.0
let container = UIView(frame: CGRect(x: ViewController.kMargin, y: ViewController.kMargin, width: containerWidth, height: containerWidth))
container.backgroundColor = UIColor.darkGray
view.addSubview(container)
// now create all of the subviews, specifying a tag for each; and
let cellWidth = (containerWidth - (4.0 * ViewController.kMargin)) / 3.0
for column in 0 ..< 3 {
for row in 0 ..< 3 {
let cell = UIView(frame: CGRect(x: ViewController.kMargin + CGFloat(column) * (cellWidth + ViewController.kMargin), y: ViewController.kMargin + CGFloat(row) * (cellWidth + ViewController.kMargin), width: cellWidth, height: cellWidth))
cell.tag = row * 3 + column;
container.addSubview(cell)
}
}
// finally, create the gesture recognizer
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
container.addGestureRecognizer(pan)
}
// Here's a Swift 3.0 version based on Rajesh Choudhary's answer:
func drawLine(onLayer layer: CALayer, fromPoint start: CGPoint, toPoint end:CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.fillColor = nil
line.lineWidth = 8
line.opacity = 0.5
line.strokeColor = UIColor.red.cgColor
layer.addSublayer(line)
}
var gesturedSubviews : [UIView] = []
var startX: CGFloat!
var startY: CGFloat!
var endX: CGFloat!
var endY: CGFloat!
func handlePan(gesture: UIPanGestureRecognizer) {
if (gesture.state == .began)
{
gesturedSubviews = [];
let location = gesture.location(in: gesture.view)
print("location \(location)")
startX = location.x
startY = location.y
}
if (gesture.state == .changed) {
let location = gesture.location(in: gesture.view)
print("location \(location)")
endX = location.x
endY = location.y
drawLine(onLayer: view.layer, fromPoint: CGPoint(x: startX, y: startY), toPoint: CGPoint(x:endX, y:endY))
startX = endX
startY = endY
}
if (gesture.state == .ended) {
let location = gesture.location(in: gesture.view)
print("location \(location)")
drawLine(onLayer: view.layer, fromPoint: CGPoint(x: startX, y: startY), toPoint: CGPoint(x:location.x, y:location.y))
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
let location = gesture.location(in: gesture.view)
print("location \(location)")
for subview in (gesture.view?.subviews)! {
if subview.frame.contains(location) {
if (subview != gesturedSubviews.last) {
gesturedSubviews.append(subview)
subview.backgroundColor = UIColor.blue
subview.alpha = 1.0
}
}
}
}
}