Eraser for UIBezierPath - objective-c

I am using UIBezierPath for free hand drawing in an iPad app. I want to apply an eraser to a uibezierpath.
However, I want to only erase the drawing in its path. I cannot use the path color as the background color because I have other elements on the background.
Below is how I am creating my free hand drawings:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
lineWidths = 10;
brushPattern = [UIColor greenColor];
pathArray = [[NSMutableArray alloc]init];
bufferArray = [[NSMutableArray alloc]init];
self.multipleTouchEnabled = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect {
for (NSMutableDictionary *dictionary in pathArray) {
UIBezierPath *_path = [dictionary objectForKey:#"Path"];
UIColor *_colors = [dictionary objectForKey:#"Colors"];
[_colors setStroke];
_path.lineCapStyle = kCGLineCapRound;
[_path stroke];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
myPath = [[UIBezierPath alloc]init];
myPath.lineWidth = lineWidths;
CGPoint touchPoint = [[touches anyObject] locationInView:self];
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[myPath addLineToPoint:CGPointMake(touchPoint.x, touchPoint.y)];
dict = #{#"Path": myPath, #"Colors": brushPattern};
[pathArray addObject:dict];
[self setNeedsDisplay];
[undoManager registerUndoWithTarget:self selector:#selector(undoButtonClicked) object:nil];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}

Store a BOOL value for erase: BOOL _erase;
BOOL eraseButtonIsTapped = ...
if eraseButtonIsTapped {
_erase = yes;
} else{
_erase = NO;
}
When drawing:
[myPath strokeWithBlendMode:_erase?kCGBlendModeClear:kCGBlendModeNormal alpha:1.0f];

Just try this
brushPattern = view.backgroundColor;
This will draw a new line with the color what exactly behind your drawn path. And you can use the same pathArray to do this. So that later on you can implement redo/undo operations too. if you want i could explain you more on this.

Related

How to achieve this animation with Spritekit?

Question:
How to achieve this animation with Spritekit?
What I've done:
Problem:
1) I can draw all four petals,but once I lift my finger to draw the circle, it will still create a line from the previous point where I lift my finger to the new touches begin point. refer to gif below:
2) How to remove the solid orange line from the view incrementally (mine is too abrupt)?
3) Need to tune the .sks file properties.
4) https://stackoverflow.com/questions/29792443/set-the-initial-state-of-skemitternode
This is my code:
#import "GameScene.h"
#interface GameScene()
#property (nonatomic) SKEmitterNode* fireEmmitter;
#property (nonatomic) SKEmitterNode* fireEmmitter2;
#end
#implementation GameScene
NSMutableArray *_wayPoints;
NSTimer* myTimer;
-(void)didMoveToView:(SKView *)view {
_wayPoints = [NSMutableArray array];
//Setup a background
self.backgroundColor = [UIColor blackColor];
//setup a fire emitter
NSString *fireEmmitterPath = [[NSBundle mainBundle] pathForResource:#"magic" ofType:#"sks"];
_fireEmmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:fireEmmitterPath];
_fireEmmitter.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2 - 200);
_fireEmmitter.name = #"fireEmmitter";
_fireEmmitter.zPosition = 1;
_fireEmmitter.targetNode = self;
_fireEmmitter.particleBirthRate = 0;
[self addChild: _fireEmmitter];
//setup another fire emitter
NSString *fireEmmitterPath2 = [[NSBundle mainBundle] pathForResource:#"fireflies" ofType:#"sks"];
_fireEmmitter2 = [NSKeyedUnarchiver unarchiveObjectWithFile:fireEmmitterPath2];
_fireEmmitter2.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
_fireEmmitter2.name = #"fireEmmitter";
_fireEmmitter2.zPosition = 1;
_fireEmmitter2.targetNode = self;
_fireEmmitter2.particleBirthRate = 0;
[self addChild: _fireEmmitter2];
//Setup a LightNode
SKLightNode* light = [[SKLightNode alloc] init];
light.categoryBitMask = 1;
light.falloff = 1;
light.ambientColor = [UIColor whiteColor];
light.lightColor = [[UIColor alloc] initWithRed:1.0 green:1.0 blue:0.0 alpha:0.5];
light.shadowColor = [[UIColor alloc] initWithRed:0.0 green:0.0 blue:0.0 alpha:0.3];
[_fireEmmitter addChild:light];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
CGMutablePathRef ref = CGPathCreateMutable();
CGPoint p = touchPoint;
p = [self.scene convertPointToView:p];
CGPathMoveToPoint(ref, NULL, p.x, p.y);
_fireEmmitter.position = CGPointMake(touchPoint.x, touchPoint.y);
_fireEmmitter.particleBirthRate = 2000;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
//On Dragging make the emitter with the attached light follow the position
for (UITouch *touch in touches) {
[self addPointToMove:touchPoint];
CGPoint location = [touch locationInNode:self];
[self childNodeWithName:#"fireEmmitter"].position = CGPointMake(location.x, location.y);
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
_fireEmmitter.particleBirthRate = 0;
[self performSelector:#selector(userHasCompletedTheDrawing) withObject:nil afterDelay:3];
}
- (void)userHasCompletedTheDrawing{
CGMutablePathRef path = CGPathCreateMutable();
if (_wayPoints && _wayPoints.count > 0) {
CGPoint p = [(NSValue *)[_wayPoints objectAtIndex:0] CGPointValue];
//p = [self.scene convertPointToView:p];
CGPathMoveToPoint(path, nil, p.x, p.y);
_fireEmmitter2.position = CGPointMake(p.x,p.y);
_fireEmmitter2.particleBirthRate = 1000;
for (int i = 0; i < _wayPoints.count; ++i) {
p = [(NSValue *)[_wayPoints objectAtIndex:i] CGPointValue];
CGPathAddLineToPoint(path, nil, p.x, p.y);
}
SKAction *followTrack = [SKAction followPath:path asOffset:NO orientToPath:YES duration:1];
[_fireEmmitter2 runAction:followTrack completion:^{
_fireEmmitter2.particleBirthRate = 0;
[_fireEmmitter2 runAction:[SKAction waitForDuration:1] completion:^{
//_fireEmmitter2.particleBirthRate = 0;
}];
}];
}
//myTimer = [NSTimer scheduledTimerWithTimeInterval: 0.01 target: self selector: #selector(removePointToMove) userInfo: nil repeats: YES];
[self performSelector:#selector(removeAllPointToMove) withObject:nil afterDelay:1];
}
- (void)addPointToMove:(CGPoint)point {
[_wayPoints addObject:[NSValue valueWithCGPoint:point]];
}
- (void)removeAllPointToMove{
[_wayPoints removeAllObjects];
}
- (void)removePointToMove{
if ([_wayPoints count]>0) {
[_wayPoints removeObjectAtIndex:0];
}
}
- (void)drawLines {
//1
NSMutableArray *temp = [NSMutableArray array];
for(CALayer *layer in self.view.layer.sublayers) {
if([layer.name isEqualToString:#"line"]) {
[temp addObject:layer];
}
}
[temp makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
//3
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.name = #"line";
lineLayer.strokeColor = [UIColor orangeColor].CGColor;
lineLayer.fillColor = nil;
lineLayer.lineWidth = 3;
lineLayer.lineJoin = kCALineJoinRound; /* The join style used when stroking the path. Options are `miter', `round'
* and `bevel'. Defaults to `miter'. */
lineLayer.zPosition = -1;
//4
CGPathRef path = [self createPathToMove];
lineLayer.path = path;
CGPathRelease(path);
[self.view.layer addSublayer:lineLayer];
}
- (CGPathRef)createPathToMove {
//1
CGMutablePathRef ref = CGPathCreateMutable();
//2
for(int i = 0; i < [_wayPoints count]; ++i) {
CGPoint p = [_wayPoints[i] CGPointValue];
p = [self.scene convertPointToView:p];
//3
if(i == 0 ) {
CGPathMoveToPoint(ref, NULL, p.x, p.y);
} else {
CGPathAddLineToPoint(ref, NULL, p.x, p.y);
}
}
return ref;
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
[self drawLines];
if ([_wayPoints count]==0) {
[myTimer invalidate];
}
}
#end
This is my .sks files properties:
Concerning your first question, you need to split your CGPathRef into multiple subpaths so that no line gets drawn between the petals and the center. Use the CGPathCloseSubpath function when you are done drawing the petals so that you can call CGPathMoveToPoint and CGPathAddLineToPoint afterwards.

How do I get the NAME of a UIColor/SKColor (i.e. [UIColor redColor] --> #"red"

I kinda asked the question in the title but suppose I have a UIColor (i.e. [UIColor redColor]. How do i get an NSString to equal #"red" OR how can i find out is a color is the same as [UIColor redColor]?
For example I have tried,
SKSpriteNode *player = [SKSpriteNode spriteNodeWithImageNamed:#"image"];
[player runAction:[SKAction colorWithColor:[SKColor blueColor] withColorBlendFactor:1 duration:0.01];
[self addChild:player];
Then later:
if (player.color == [SKColor blueColor]) { //Also I have tried 'isEqual' and that didn't work either!
}
PLEASE HELP ME! Thanks in advance!
I assume you do not know about userData as rmaddy already suggested so I will give you some sample code. First though, this is what Apple says about the userData:
You use this property to store your own data in a node. For example, you might store game-specific data about each node to use inside your game logic. This can be a useful alternative to creating your own node subclasses to hold game data.
Sprite Kit does not do anything with the data stored in the node. However, the data is archived when the node is archived.
#implementation MyScene
{
SKSpriteNode *frank;
SKSpriteNode *sally;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
frank = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(50, 50)];
frank.position = CGPointMake(100, 100);
frank.userData = [NSMutableDictionary dictionary];
[frank.userData setValue:#"blue" forKey:#"color"];
[self addChild:frank];
sally = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(50, 50)];
sally.position = CGPointMake(200, 100);
sally.userData = [NSMutableDictionary dictionary];
[sally.userData setValue:#"red" forKey:#"color"];
[self addChild:sally];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
NSMutableDictionary *tempDictionary = node.userData;
NSString *tempString = [NSString stringWithFormat:#"%#",[tempDictionary objectForKey:#"color"]];
if([tempString isEqualToString:#"blue"])
NSLog(#"My color is blue");
if([tempString isEqualToString:#"red"])
NSLog(#"My color is red");
}

Stretchy UIBezierPath line?

I want to draw a straight line with my finger that automatically sizes based upon how far away I am from the point of origin.
So if I touch the screen in the middle and slide my finger out a line appears to 'stretch' and pivot around the point of orgin as my finger moves on the screen. WHhen I lift my finger. The Destination Point should finalize and create a line. I can drag my finger across the screen and 'Draw' on the screen but that's not what I am wanting to do.
I thought UIBeizerPath moveToPoint would help but it just messes things up.
What am I doing wrong?
- (id)initWithFrame:(CGRect)frame
{
//default line properties
myPath=[[UIBezierPath alloc]init];
myPath.lineCapStyle=kCGLineCapRound;
myPath.miterLimit=0;
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
lastPoint = curPoint;
[myPath moveToPoint:lastPoint];
[pathArray addObject:myPath];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor redColor]; //red to show it hasn't been added yet.
[myPath moveToPoint:tempPoint];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor]; //finalize the line with black color
[myPath addLineToPoint:curPoint];
[self setNeedsDisplay];
}
Here's one concept. Draws a line from where you start dragging your finger until where you let go, animating it as you drag your finger around. It does this by making a CAShapeLayer, resetting the path as you move your finger around.
This should demonstrate the basic idea:
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:gesture];
}
- (CAShapeLayer *)createShapeLayer:(UIView *)view {
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.0;
[view.layer addSublayer:shapeLayer];
return shapeLayer;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
static CAShapeLayer *shapeLayer;
static CGPoint origin;
if (gesture.state == UIGestureRecognizerStateBegan) {
shapeLayer = [self createShapeLayer:gesture.view];
origin = [gesture locationInView:gesture.view];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:origin];
CGPoint location = [gesture locationInView:gesture.view];
[path addLineToPoint:location];
shapeLayer.path = path.CGPath;
} else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateFailed ||
gesture.state == UIGestureRecognizerStateCancelled) {
shapeLayer = nil;
}
}
Or, in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(pan)
}
private func createShapeLayer(for view: UIView) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)
return shapeLayer
}
private var shapeLayer: CAShapeLayer!
private var origin: CGPoint!
func handlePan(_ gesture: UIPanGestureRecognizer) {
if gesture.state == .began {
shapeLayer = createShapeLayer(for: gesture.view!)
origin = gesture.location(in: gesture.view)
} else if gesture.state == .changed {
let path = UIBezierPath()
path.move(to: origin)
path.addLine(to: gesture.location(in: gesture.view))
shapeLayer.path = path.cgPath
} else if gesture.state == .ended || gesture.state == .failed || gesture.state == .cancelled {
shapeLayer = nil
}
}
If you don't use CAShapeLayer, but you want to keep track of previous paths, you'll have to maintain an array for those old paths, and build a path that consists of all of the old paths, perhaps something like:
#interface CustomView ()
#property (nonatomic) CGPoint originPoint;
#property (nonatomic) CGPoint currentPoint;
#property (nonatomic) NSMutableArray *previousPaths;
#end
#implementation CustomView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configure];
}
return self;
}
- (id)init {
return [self initWithFrame:CGRectZero];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self configure];
}
return self;
}
- (void)configure {
_previousPaths = [[NSMutableArray alloc] init];
}
- (void)drawRect:(CGRect)rect {
[[UIColor redColor] setStroke];
UIBezierPath *drawPath = [UIBezierPath bezierPath];
drawPath.lineCapStyle = kCGLineCapRound;
drawPath.miterLimit = 0;
drawPath.lineWidth = 3.0;
for (UIBezierPath *path in self.previousPaths)
[drawPath appendPath:path];
UIBezierPath *path = [self pathForCurrentLine];
if (path)
[drawPath appendPath:path];
[drawPath stroke];
}
- (UIBezierPath *)pathForCurrentLine {
if (CGPointEqualToPoint(self.originPoint, CGPointZero) && CGPointEqualToPoint(self.currentPoint, CGPointZero))
return nil;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.originPoint];
[path addLineToPoint:self.currentPoint];
return path;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.originPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([event respondsToSelector:#selector(predictedTouchesForTouch:)]) {
touch = [[event predictedTouchesForTouch:touch] lastObject] ?: touch;
}
self.currentPoint = [touch locationInView:self];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.currentPoint = [[touches anyObject] locationInView:self];
[self.previousPaths addObject:[self pathForCurrentLine]];
self.originPoint = self.currentPoint = CGPointZero;
[self setNeedsDisplay];
}
#end
UIBezierPath is building a path from your instructions. Imagine a pen. When you say, "moveToPoint:" it moves the pen to that point. When you say "lineToPoint:" it puts the pen down and moves it from the current location to the new point. And so on.
To get the effect you desire, you will need to create a new path whenever the touches move, drawing a line from the original point to the current touch position.

UIBezierPath to draw straight lines

Here is the relevant .m that I am currently using.
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor] setStroke];
for (UIBezierPath *_path in pathArray)
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
#pragma mark -
#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
myPath=[[UIBezierPath alloc]init];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
if([ud objectForKey:#"lineThickness"] == nil) {
myPath.lineWidth=5;
}
else {
float thicknessFloat = [ud floatForKey:#"lineThickness"];
myPath.lineWidth= 10. * thicknessFloat;
}
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[pathArray addObject:myPath];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
if([ud objectForKey:#"lineThickness"] == nil) {
myPath.lineWidth=5;
}
else {
float thicknessFloat = [ud floatForKey:#"lineThickness"];
myPath.lineWidth= 10. * thicknessFloat;
}
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
It works great, but since this is tutorial code that is slightly modified by me, I do not know how to approach the problem of wanting to draw lines between two points, and have the framework connect the the points each time a point is added.
Can anyone please point me in a good direction on how to accomplish this please?
The particulars of how to implement this depend upon the effect that you're looking for. If you're just tapping on a bunch of points and want to add them to a UIBezierPath you can do something like the following in your view controller:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
CGPoint location = [mytouch locationInView:self.view];
// I'm assuming you have a myPath UIBezierPath which is an ivar which is
// initially nil. In that case, we'll check if it's nil and if so, initialize
// it, otherwise, it's already been initialized, then we know we're just
// adding a line segment.
if (!myPath)
{
myPath = [UIBezierPath bezierPath];
[myPath moveToPoint:location];
shapeLayer = [[CAShapeLayer alloc] initWithLayer:self.view.layer];
shapeLayer.lineWidth = 1.0;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
[self.view.layer addSublayer:shapeLayer];
}
else
{
[myPath addLineToPoint:location];
shapeLayer.path = myPath.CGPath;
}
}
If you wanted something where you can draw with your finger (e.g. dragging your finger draws), then it might look something like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
CGPoint location = [mytouch locationInView:self.view];
myPath = [UIBezierPath bezierPath];
[myPath moveToPoint:location];
shapeLayer = [[CAShapeLayer alloc] initWithLayer:self.view.layer];
shapeLayer.lineWidth = 1.0;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
[self.view.layer addSublayer:shapeLayer];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
CGPoint location = [mytouch locationInView:self.view];
[myPath addLineToPoint:location];
shapeLayer.path = myPath.CGPath;
}
I would not use UIBezierPath since it is intended more for drawing curved paths.
The most efficient way to accomplish this would be to use core graphics draw commands within drawRect while using an array to store the points you want to draw; this array is appended to in your touch methods.
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat black[4] = {0, 0,
0, 1};
CGContextSetStrokeColor(c, black);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 100, 100);
CGContextAddLineToPoint(c, 100, 200); //call this in a loop that goes through the point array
CGContextStrokePath(c);
}
There is much more information here: Quartz 2D Programming Guide
Hope this helps!

How can I get equivalent functionality across all iterations of a programmatically created series of UILabel objects

Advaced Question: How can I get equivalent functionality across all iterations of a programmatically created series of UILabel objects in UIView (to function across CGIntersect with a second series of UILabels)?
Can all instances of a programmatically created UILabel series possess the equivalent title (self.MYUILABEL) and retain equivalent functionality?
I made a series of equivalently classed UILabels programmatically with a for loop, but only the last made instance of UILabel is assigned the UILabel title.
How can I get equivalent functionality across all iterations of a programmatically created series of UILabel objects in UIView?
The goal is to get one series of UILabels to be moved by touch to a second series of UILabels.
Here is how I made the UILabels (which act as goal zones) in my view.
for (int i=0;i<characterCount ;i++){
self.myBottomLabel=[[UILabel alloc] initWithFrame: CGRectMake((i*60.0)+10, 200.0, 50.0, 50.0)];
self.myBottomLabel.backgroundColor = [UIColor whiteColor];
self.myBottomLabel.text= [myword objectAtIndex:i];
self.myBottomLabel.userInteractionEnabled = NO;
self.myBottomLabel.tag=300+[[letterToNumber objectForKey:[myword objectAtIndex:i]] integerValue];
[self.view insertSubview: self.myBottomLabel atIndex:(500)];
}
When I attempt to use CGRectIntersectsRect using self.myBottomLabel only the last UILabel made will function correctly with the self.myBottomLabel title used by thisCGRect:
theReceivingCard = [self.myBottomLabel convertRect:[self.myBottomLabel frame] toView:self.view];
Here is almost all of the implementation. Do I need to create multiple CGRects (Which I don't know how to do)? Or is there someway to find exactly what the tag is of these UILabel (which are made interactive when you move a corresponding UILabel over the top of them?)
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray* myword=[NSArray arrayWithObjects:#"h",#"e",#"l",#"l",#"o",nil];
NSDictionary *letterToNumber;
letterToNumber = [NSDictionary dictionaryWithObjectsAndKeys:
#"0", #"a",
#"1", #"b",
#"2", #"c",
#"3", #"d",
#"4", #"e",
#"5", #"f",
#"6", #"g",
#"7", #"h",
#"8", #"i",
#"9", #"j",
#"10", #"k",
#"11", #"l",
#"12", #"m",
#"13", #"n",
#"14", #"o",
#"15", #"p",
#"16", #"q",
#"17", #"r",
#"18", #"s",
#"19", #"t",
#"20", #"u",
#"21", #"v",
#"22", #"w",
#"23", #"x",
#"24", #"y",
#"25", #"z",
nil];
NSUInteger characterCount = [myword count];
//moveable
for (int i=0;i<characterCount ;i++){
self.myTopLabel=[[UILabel alloc] initWithFrame: CGRectMake((i*60.0)+10, 100.0, 50.0, 50.0)];
self.myTopLabel.backgroundColor = [UIColor whiteColor];
self.myTopLabel.text= [myword objectAtIndex:i];
self.myTopLabel.userInteractionEnabled = YES;
self.myTopLabel.tag=100+[[letterToNumber objectForKey:[myword objectAtIndex:i]] integerValue];
[self.view insertSubview: self.myTopLabel atIndex:(1)];
}
//receiver
for (int i=0;i<characterCount ;i++){
self.myBottomLabel=[[UILabel alloc] initWithFrame: CGRectMake((i*60.0)+10, 200.0, 50.0, 50.0)];
self.myBottomLabel.backgroundColor = [UIColor whiteColor];
self.myBottomLabel.text= [myword objectAtIndex:i];
self.myBottomLabel.userInteractionEnabled = NO;
self.myBottomLabel.tag=300+[[letterToNumber objectForKey:[myword objectAtIndex:i]] integerValue];
[self.view insertSubview: self.myBottomLabel atIndex:(500)];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
UIView* viewYouWishToObtain = [self.view hitTest:locationPoint withEvent:event];
[[viewYouWishToObtain superview] bringSubviewToFront:viewYouWishToObtain];
if ([touch view] != viewYouWishToObtain && viewYouWishToObtain.tag >= 100 && viewYouWishToObtain.tag <= 200) {
if ([touch tapCount] == 2) {
}
return;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
UIView* viewYouWishToObtain = [self.view hitTest:locationPoint withEvent:event];
if ([touch view] == viewYouWishToObtain && viewYouWishToObtain.tag >= 100 && viewYouWishToObtain.tag <= 200) {
CGPoint location = [touch locationInView:self.view];
viewYouWishToObtain.center = location;
theMovingCard = [viewYouWishToObtain convertRect:[viewYouWishToObtain frame] toView:self.view];
theReceivingCard = [self.myBottomLabel convertRect:[self.myBottomLabel frame] toView:self.view];
CGRect theZone = CGRectMake(theReceivingCard.origin.x, theReceivingCard.origin.y,theReceivingCard.size.width*2 , theReceivingCard.size.height*2);
CGRect theCard = CGRectMake(theMovingCard.origin.x, theMovingCard.origin.y,theMovingCard.size.width*2 , theMovingCard.size.height*2);
if(CGRectIntersectsRect(theCard, theZone))
{
NSLog(#"intersect");
}
return;
}
}
The problem is that right there at the end of all the code, the intersect only functions on the last made UILabel. I need the intersect to work across all of the series of UILabels that act as receivers. Super super super super thanks in advance.
You're making a ton of labels in your loop but you only have a pointer to the last one you created, since you assign them all to the same property. I'm not totally clear on the rest of your question but I think you should be adding those labels to an array, and iterating through that array when you are hit testing etc.
Here is the answer of "how to programmatically create multiple CGRect values for multiple CGIntersects for UIView subset matching" by iteration through a for loop.
This is the complete file.
It is an iOS touch enabled foundation for a card game (like solitaire) or as match game (children's education).
The program makes 10 cards where the top row has to match the bottom row. If the top row card has text equivalent to the bottom row card then the CGIntersect will 'hit' and you can execute any additional methods.
There should be a simpler way to make variable CGRect, but I can't find the answer. If someone can figure out how to NOT use a NSMutableArray and a for-loop to generate the CGrect code, then I would be greatly obliged if you would share.
.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property NSString* currentCard;
#property NSArray* myWord;
#property NSString* myCurrentLetter;
#property NSMutableArray* myMoverMutableArray;
#property NSMutableArray* myReceiverMutableArray;
#end
.m
#import "ViewController.h"
#interface ViewController ()
{
int myLetterTagNumber;
}
#end
#implementation ViewController
#synthesize currentCard = _currentCard;
#synthesize myWord = _myWord;
#synthesize myCurrentLetter = _myCurrentLetter;
#synthesize myMoverMutableArray = _myMoverMutableArray;
#synthesize myReceiverMutableArray = _myReceiverMutableArray;
- (void)viewDidLoad
{
[super viewDidLoad];
[self writer];
}
-(void)writer{
self.myWord=[NSArray arrayWithObjects:#"l",#"i",#"l",#"l",#"y",nil];
NSUInteger characterCount = [self.myWord count];
self.myMoverMutableArray = [NSMutableArray arrayWithCapacity:characterCount];
self.myReceiverMutableArray = [NSMutableArray arrayWithCapacity:characterCount];
//moveable
for (int i=0;i<characterCount ;i++){
UILabel*myTopLabel;
myTopLabel=[[UILabel alloc] initWithFrame: CGRectMake((i*60.0)+10, 100.0, 50.0, 50.0)];
myTopLabel.backgroundColor = [UIColor whiteColor];
myTopLabel.text= [self.myWord objectAtIndex:i];
myTopLabel.userInteractionEnabled = YES;
myTopLabel.textAlignment = UITextAlignmentCenter;
myTopLabel.tag=100+i;
[self.myMoverMutableArray addObject:[self.myWord objectAtIndex:i]];
[self.view insertSubview: myTopLabel atIndex:(100)];
}
//receiver
for (int i=0;i<characterCount ;i++){
UILabel*myBottomLabel;
myBottomLabel=[[UILabel alloc] initWithFrame: CGRectMake((i*60.0)+10, 200.0, 50.0, 50.0)];
myBottomLabel.backgroundColor = [UIColor redColor];
myBottomLabel.text= [self.myWord objectAtIndex:i];
myBottomLabel.userInteractionEnabled = YES;
myBottomLabel.textAlignment = UITextAlignmentCenter;
myBottomLabel.tag=300+i;
[self.myReceiverMutableArray addObject:[self.myWord objectAtIndex:i]];
[self.view insertSubview: myBottomLabel atIndex:(300)];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
UIView* viewYouWishToObtain = [self.view hitTest:locationPoint withEvent:event];
//clipping
[[viewYouWishToObtain superview] bringSubviewToFront:viewYouWishToObtain];
myLetterTagNumber = viewYouWishToObtain.tag;
UILabel * myTempUILabel = (UILabel*)[self.view viewWithTag:(myLetterTagNumber)];
self.myCurrentLetter = myTempUILabel.text;
NSLog (#"text: %#",myTempUILabel.text);
NSLog (#"Tag: %d",myLetterTagNumber);
if ([touch view] != viewYouWishToObtain && viewYouWishToObtain.tag >= 100 && viewYouWishToObtain.tag <= 200) {
if ([touch tapCount] == 2) {
}
return;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
UIView* viewYouWishToObtain = [self.view hitTest:locationPoint withEvent:event];
if ([touch view] == viewYouWishToObtain && viewYouWishToObtain.tag >= 100 && viewYouWishToObtain.tag <= 200) {
CGPoint location = [touch locationInView:self.view];
viewYouWishToObtain.center = location;
//moving card rec
CGRect theMovingCard;
theMovingCard = [viewYouWishToObtain convertRect:[viewYouWishToObtain frame] toView:self.view];
CGRect themovingRect = CGRectMake(theMovingCard.origin.x, theMovingCard.origin.y,theMovingCard.size.width*2 , theMovingCard.size.height*2);
NSString *myTempString;
NSUInteger characterCount = [self.myWord count];
for (int i=0;i<characterCount ;i++){
myTempString= [self.myReceiverMutableArray objectAtIndex:i];
if (myTempString == self.myCurrentLetter){
UILabel * mytouchMoveUILabel = (UILabel*)[self.view viewWithTag:(i+300)];
CGRect theReceivingCard;
theReceivingCard = [mytouchMoveUILabel convertRect:[mytouchMoveUILabel frame] toView:self.view];
CGRect spaceToHitRect = CGRectMake(theReceivingCard.origin.x, theReceivingCard.origin.y,theReceivingCard.size.width*2 , theReceivingCard.size.height*2);
if(CGRectIntersectsRect(themovingRect, spaceToHitRect))
{
NSLog (#"Intersect: %d",myLetterTagNumber);
}
}//if
}//for
return;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
// NSLog(#"3");
CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
UIView* viewYouWishToObtain = [self.view hitTest:locationPoint withEvent:event];
if ([touch view] == viewYouWishToObtain && viewYouWishToObtain.tag >= 100 && viewYouWishToObtain.tag <= 200) {
return;
}
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
#end