How to detect a touch in a subview that was dragged there (or fastest way to get which subview is underneath a tap in its superview) - ios

I've got a Super View, and its got a bunch of subviews. I'd like a user to be able to drag their finger inside the super view and when they drag over the subviews, that subview changes.
I need this to be very efficient, because changes can happen really fast.
So far what i've tried, is in the super view I can get the touch events from touchesBegan:withEvent: and touchesMoved:withEvent: These two combined give me all the touches I need. On each call of these methods, I have to iterate through all of the subviews and test like so
for (UIView *view in self.subviews) {
if (CGRectContainsPoint(view.frame, touchPoint) {
return view;
}
}
Then change that view however I want to. Problem is, from my benchmarking this process is just two slow.
My immediate thought is to have the subview's themselves detect the touch and then just post a notification or something like that when they are tapped. This way I don't have to iterate through them all inside the superview every freaking touch event.
The problem I am encountering with this, is that I haven't found a way to get the subviews themselves to detect that they are touched when they are just dragged over and the touch actually originated on a different UIView.
I'm open to any suggestions that either speed up the first process, or have a different way to accomplish this.

Since your subviews are on a Grid and if they are equally sized, you should be able to calculate the highlighted one directly. You just need to store their references on creation in a 2D array, for performance I suggest to use a c-array.
int x = touchPoint.x/viewWidth;
int y = touchPoint.y/viewHeight;
return views[x][y];
Depending on your Layout, some border margins or spacings between the views should be taken into account.
This way you do not need to iterate the subviews array and you do not need to perform all these CGRectContainsPoint calculations.

Should be easy:
MyViewController.h
#import <UIKit/UIKit.h>
#interface MyViewController : UIViewController
#end
MyViewController.m
#import "MyViewController.h"
#define SIDE_LENGTH 60
#define NUM_SQUARES 15
#implementation MyViewController
{
UIView *_selectedView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self populateView];
}
- (void)populateView
{
for (int i = 0; i < 15; ++i)
{
CGRect frame = CGRectMake(drand48() * self.view.frame.size.width - SIDE_LENGTH,
drand48() *self.view.frame.size.height - SIDE_LENGTH,
SIDE_LENGTH,
SIDE_LENGTH);
UIView *view = [[UIView alloc] initWithFrame:frame];
view.backgroundColor = [UIColor colorWithRed:drand48()
green:drand48()
blue:drand48()
alpha:1.0f];
view.layer.cornerRadius = SIDE_LENGTH / 2.0f; // cuz circles r pretty
[self.view addSubview:view];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
for (UIView *view in self.view.subviews)
{
if (CGRectContainsPoint(view.frame, location)) {
_selectedView = view;
break;
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
_selectedView.center = location;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
_selectedView.center = location;
}
#end
Of course, for you, just get rid of the populateView method. I'm just using it to demonstrate how this works as a drop-in class.
If you're looking for speed, then subclass UIView and do this:
MyView.h
#import <UIKit/UIKit.h>
#class MyView;
#interface MyView : UIView
#end
MyView.m
#implementation MyView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
self.center = location;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
self.center = location;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
self.center = location;
}
#end
Then MyViewController.m becomes much simpler and faster:
#import "MyViewController.h"
#import "MyView.h"
#define SIDE_LENGTH 60
#define NUM_SQUARES 15
#implementation MyViewController
{
UIView *_selectedView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self populateView];
}
- (void)populateView
{
for (int i = 0; i < 15; ++i)
{
CGRect frame = CGRectMake(drand48() * self.view.frame.size.width - SIDE_LENGTH,
drand48() *self.view.frame.size.height - SIDE_LENGTH,
SIDE_LENGTH,
SIDE_LENGTH);
MyView *view = [[MyView alloc] initWithFrame:frame];
view.backgroundColor = [UIColor colorWithRed:drand48()
green:drand48()
blue:drand48()
alpha:1.0f];
view.layer.cornerRadius = SIDE_LENGTH / 2.0f;
[self.view addSubview:view];
}
}
#end

Related

Touched Move UILabel works until the value is fix - Bug or Features?

a very strange result.
Start a Single View Application
Add a UILabel
Put my Code in
import "ViewController.h"
#interface ViewController ()
{
float oldX, oldY;
bool dragging;
__weak IBOutlet UILabel *textLable;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// self.textLable.backgroundColor = [UIColor clearColor];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// get touch event
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if ([touch view] == textLable)
{
int intValue = (int)((touchLocation.y * 24.0)/300.0);
NSString *anyValue = [NSString stringWithFormat:#"%d",intValue];
textLable.center= touchLocation;
// RUN WITHOUT THIS PART .... tochedMoved workt great
//
// Run With this PART ..... touchedMoved is damaged!!!!!
//textLable.text = anyValue;
NSLog(#"%f %f", touchLocation.y, textLable.frame.origin.y);
}
}
#end
Connect label with
__weak IBOutlet UILabel *textLable;
let it RUN
Now you can move the label by touching moving.
Ok...and NOW!!!!
change the change
// RUN WITHOUT THIS PART .... tochedMoved workt great
//
// Run With this PART ..... touchedMoved is damaged!!!!!
//textLable.text = anyValue;
to
// RUN WITHOUT THIS PART .... tochedMoved workt great
//
// Run With this PART ..... touchedMoved is damaged!!!!!
textLable.text = anyValue; //<------------------------------
Run the app and try the move!!! you will see, label jumps between new and start position, if you start touched moving.
I tried by using a UIView as container....same thing: Once you change the value of moved object (UIButton same), moving is not working right.
Report to Appel is already send...no answere!!!
Is it a Bug or a features???
The idea is about your frame and your location touch. You are centering Label to your touch. So nothing is about setting the text.
Try this code, detecting distance between touch and centre text when first touch and adding the center label to this point :
#interface ViewController ()
{
float distanceTouchX, distanceTouchY;
bool dragging;
CGPoint firstLocation;
__weak IBOutlet UILabel *textLable;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
distanceTouchX = textLable.center.x - touchLocation.x;
distanceTouchY = textLable.center.y - touchLocation.y;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// get touch event
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if ([touch view] == textLable)
{
int intValue = (int)((touchLocation.y * 24.0)/320.0);
NSString *anyValue = [NSString stringWithFormat:#"%d",intValue];
CGPoint newTouchLocation = touchLocation;
newTouchLocation.x = newTouchLocation.x + distanceTouchX;
newTouchLocation.y = newTouchLocation.y + distanceTouchY;
textLable.center= newTouchLocation;
textLable.text = anyValue;
NSLog(#"%f %f", touchLocation.y, textLable.frame.origin.y);
}
}
#end

IOS touch tracking code

I'm making an app for iPhone in Xcode, and It requires a box to follow my finger only on the X axis. I couldn't find any solution online to this, and my coding knowledge isn't that great.
I've been trying to use touchesBegan and touchesMoved.
Could someone please write me up some code please?
First you need the UIGestureRecognizerDelegate on your ViewController.h file:
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>
#end
Then you declare an UIImageView on your ViewController.m, like so, with a BOOL to track if a touch event is occurring inside your UIImageView:
#interface ViewController () {
UIImageView *ballImage;
BOOL touchStarted;
}
Then you initialize the UIImageView on viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:#"ball.png"];
ballImage = [[UIImageView alloc]initWithImage:image];
[ballImage setFrame:CGRectMake(self.view.center.x, self.view.center.y, ballImage.frame.size.width, ballImage.frame.size.height)];
[self.view addSubview:ballImage];
}
After that you can start doing your modifications to what's best for you using these methods:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touch_point = [touch locationInView:ballImage];
if ([ballImage pointInside:touch_point withEvent:event])
{
touchStarted = YES;
} else {
touchStarted = NO;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count]==1 && touchStarted) {
UITouch *touch = [touches anyObject];
CGPoint p0 = [touch previousLocationInView:ballImage];
CGPoint p1 = [touch locationInView:ballImage];
CGPoint center = ballImage.center;
center.x += p1.x - p0.x;
// if you need to move only on the x axis
// comment the following line:
center.y += p1.y - p0.y;
ballImage.center = center;
NSLog(#"moving UIImageView...");
}
}

how to stop my object to skip by clicking and move just by touch

I have some object on my screen view which moves by touch, but the problem is if I click somewhere else on my screen which has no object on, the last moved object skipped to that clicked position, anyone who know how I can be able to stop that? PostView contains code about my objects
in .h file
#property (weak, nonatomic) IBOutlet PostView *pv;
and .m file
- (void)viewDidLoad
{
[super viewDidLoad];
viewArray = [NSMutableArray arrayWithObjects: nil];
pv.frame = CGRectMake(10, 10, 100, 100);
pv.backgroundColor = [UIColor blackColor];
[viewArray insertObject:pv atIndex:0];
[self.view addSubview:pv];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint firstTouch = [touch locationInView:self.view];
for (PostView *view in viewArray) {
if (CGRectContainsPoint(view.frame, firstTouch)) {
toMove = view;
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
toMove.center = location;
// [_delegate dragViewDidMove:self];
// toMove.center = currentTouch;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
currentTouch = [touch locationInView:self.view];
toMove.center = currentTouch;
}
Set toView to null at the start of touchesBegan:. This ensures that if the user makes a touch that touches nothing (and so toView doesn't get set to anything deliberately), nothing will happen when the touch moves (the setCenter: message will be sent to null which will return null and have no side effects.)

Calligraphy Drawing IOS Coregraphics

I am looking to have something which is marked in red, as marker but don't know whether it can be done or not.
It looks like marking using a calligraphy pen.
Trying out the following code as answered by "Andrey", I get the following output with spaces while drawing.
Brush image can be found here used is
Making further changes to the code, I found still I am not able to get the expected result.
Here I am trying to fill the path which exists between the current point and the next point.
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGFloat w=5.0;
CGFloat h=2.0;
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
for(int i=0;i<self.positions.count;i++){
CGPoint point=[[self.positions objectAtIndex:i] CGPointValue];
CGFloat x=point.x-(w/2.0);
CGFloat y=point.y-(h/2.0);
CGContextFillRect(context, CGRectMake(x, y, w, h));
if(i+1<self.positions.count){
CGPoint nextPoint=[[self.positions objectAtIndex:(i+1)] CGPointValue];
CGMutablePathRef myPath=CGPathCreateMutable();
CGPathMoveToPoint(myPath, NULL, x, y);
CGFloat x1=nextPoint.x-(w/2.0);
CGFloat y1=nextPoint.y-(h/2.0);
CGPathAddLineToPoint(myPath, NULL, x1, y1);
CGPathAddLineToPoint(myPath, NULL, w, y1);
CGPathAddLineToPoint(myPath, NULL, w, y);
CGPathCloseSubpath(myPath);
CGContextFillPath(context);
}
}
}
Those picture can be done via drawing an image in UIView subclass with overridden drawRect: message. You also need to add some kind of touch handler for catching touches. So here is some code:
#interface DrawingView ()
#property (nonatomic, strong) NSMutableArray *positions;
#end
#implementation DrawingView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self awakeFromNib];
}
return self;
}
- (void)awakeFromNib
{
self.positions = [[NSMutableArray alloc] init];
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
UIImage *brushImage = [UIImage imageNamed:#"brush.png"];
for (NSValue *object in self.positions) {
CGPoint point = [object CGPointValue];
[brushImage drawAtPoint:CGPointMake(point.x - brushImage.size.width / 2.0, point.y - brushImage.size.height / 2.0)];
}
}
- (void)addPoint:(CGPoint)point
{
[self.positions addObject:[NSValue valueWithCGPoint:point]];
[self setNeedsDisplay];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[self addPoint:[touch locationInView:self]];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[self addPoint:[touch locationInView:self]];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[self addPoint:[touch locationInView:self]];
}
#end
You only need to create a proper brush.png and place those view anywhere you want.

Detect objects, touchesmoved and UITouch

I have a UIView on where i add balls with a ID. When i touch the ball with the ID 1 i draw a dashed line follows my finger.
The problem if when i move the finger to the other balls, i don't know how to detect these balls and check the ID they have. The what i need to happen is when i touch the next ball with the ID 2, the line finish draw and stay from ball 1 to ball 2, and create new one from 2 to finger, next.. 3 etc...
The code:
#import "DrawView.h"
#implementation DrawView
- (id)init
{
self = [super initWithFrame:CGRectMake(0, 0, 1024, 768)];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.opaque = YES;
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:315 posY:138 andNumber:1];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:706 posY:138 andNumber:2];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:315 posY:526 andNumber:3];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:706 posY:526 andNumber:4];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
}
return self;
}
- (Line *)drawLine {
return drawLine;
}
- (void)setDrawLine:(Line *)line
{
drawLine = line;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CheckPointCircle * checkTouch = (CheckPointCircle *)touch.view;
if (touch.view.class == checkPointCircle.class) {
if ([checkTouch getObjectID] == 1) {
startTouchPoint = CGPointMake([checkTouch center].x, [checkTouch center].y);
endTouchPoint = [touch locationInView:self];
}
}
self.drawLine = [[Line alloc] initWithPoint:[touch locationInView:self]];
[self setNeedsDisplay];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Cancelado");
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
UITouch *secondaryTouch = (UITouch *)[[[event touchesForView:checkPointCircle] allObjects] objectAtIndex: 0];
NSLog(#"Que toco: %# ", secondaryTouch.view);
if (touch.view.class == checkPointCircle.class) {
CheckPointCircle * checkTouch = (CheckPointCircle *)touch.view;
if ([checkTouch getObjectID] == 1) {
endTouchPoint = [touch locationInView:self];
}
}
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CheckPointCircle * checkTouch = (CheckPointCircle *)touch.view;
if (touch.view.class == checkPointCircle.class) {
if ([checkTouch getObjectID] == 1) {
endTouchPoint = [touch locationInView:self];
}
}
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[drawLine drawLineFrom:startTouchPoint to:endTouchPoint];
}
#end
How to detect the other balls to get the ID.
Can anybody help me?
Thanks!
The Apple documentation of class UIView (https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/hitTest:withEvent:) lists two methods which will determine which view contains a hit/point:
– hitTest:withEvent:
– pointInside:withEvent:
Probably hitTest will help most: Its description reads "Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.", so it even converts coordinates as needed.
If you check [self hitTest: [touch.locationInView self] withEvent: event] (or similar, no code completion here ;-)= in your touchesMoved: method, you should be able to detect when the finger is over any of the other circles.
Hope this helps, nobi

Resources