UIView with Png image - ignore touch in transparent areas - iphone

I have a PNG of a circle with a transparent background added as a subview. I'm using this type of method to rotate it:
CGPoint location = [touch locationInView:self.view];
if(CGRectContainsPoint(wheelfrom.frame, location))
{
}
the problem is that the transparent area's of the image are registering as part of the UIView. Is there a way to ignore those area's when touched? Is there a better way for me to set up the UIView to recognize the transparency?
thanks!

you can check the rbga pixel colour of the image and see if a (=alpha value) is == 0 (or <= aLowValue)... as suggested by Igor Pchelko...
but in your case it may be easier...
you are using a 2D circle, so just check how the finger click is far from the circle center and see if it's out of its radius... just a Pitagora's theorem application...
EDIT:
ok, so, if you create a new class for your button subclassing UIButton:
in YourButton.h:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface YourButton : UIButton {
}
#end
in YourButton.m just add this code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"Touch x : %f y : %f", touchPoint.x, touchPoint.y);
float circleRadius = self.frame.size.height / 2; // considering a circle inscricted in a quadRect (width = height)
float deltaTouchOnCenterX = touchPoint.x - circleRadius;
float deltaTouchOnCenterY = touchPoint.y - circleRadius;
float touchDistanceFromCenter = sqrt((deltaTouchOnCenterX * deltaTouchOnCenterX) + (deltaTouchOnCenterY * deltaTouchOnCenterY) );
// or: float touchDistanceFromCenter = hypot(deltaTouchOnCenterX,deltaTouchOnCenterY);
NSLog(#"sqrt_val: %f", touchDistanceFromCenter);
NSLog(#"Touch on center x : %f y : %f", deltaTouchOnCenterX, deltaTouchOnCenterY);
if (touchDistanceFromCenter <= circleRadius) {
// your code here
NSLog(#"___ OK: You are inside the circle!!");
}
}

Swift solution:
class RadiusTouchButton: UIButton {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let radius = frame.size * 0.5
let delta = point - CGPoint(x: radius.height, y: radius.width)
return hypot(delta.x, delta.y) <= radius.height
}
}

Related

iPhone: how to quantize the position of a touched object

Hi all you smart people out there!
I want to create a touch interface to an iOS app, that allows the user to drag an object on the screen around.
However, this object should be restricted to move along the perimeter of a circle, so that if the user is trying to drag the object outside that path, it would stick to the nearest point of that circle.
I have done some iPhone programming, but my math is poor. Please help!
All you have to do is set the frame of the view to follow the equation of a circle (of the form: (x-a)^2 + (y-b)^2 = r^2). Once you detect the touch point, you can restrict the view's frame according to the x or the y coordinate of the touch point (both ways are the same).
#define circleRadius 60.0 // r in the above eqtn
#define circlesCenter_X 160.0 // a in the above eqtn
#define circlesCenter_Y 200.0 // b in the above eqtn
#define circleCenter_y(x) sqrtf(circleRadius*circleRadius - (x-circlesCenter_X)*(x-circlesCenter_X))
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event touchesForView:firstPieceView] anyObject];
CGPoint previousLocation = [touch previousLocationInView:self.view];
CGPoint location = [touch locationInView:self.view];
CGFloat delta_x = previousLocation.x - location.x; // constrained by x in this eg.
CGFloat newX = firstPieceView.center.x-delta_x;
// do limit the view's x-coordinate for possible solutions
if(newX<circlesCenter_X - circleRadius)
newX = circlesCenter_X - circleRadius;
if(newX>circlesCenter_X + circleRadius)
newX = circlesCenter_X + circleRadius;
firstPieceView.center = CGPointMake(newX, circleCenter_y(newX)*(location.y>=circlesCenter_Y?1:-1) + circlesCenter_Y);
}
EDIT- Better solution:
#define circleRadius 60.0 // r in the above eqtn
#define circlesCenter_X 160.0 // a in the above eqtn
#define circlesCenter_Y 200.0 // b in the above eqtn
#define slope(x,y) (y-circlesCenter_Y)/(x-circlesCenter_X)
#define pointOnCircle_X(m) circleRadius/(sqrtf(m*m + 1))
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event touchesForView:self.view] anyObject];
CGPoint location = [touch locationInView:self.view];
CGFloat slope;
CGPoint pointOnCircle;
if(location.x==circlesCenter_X){// case for infinite slope
pointOnCircle.x = circlesCenter_X;
if(location.x<circlesCenter_X){
pointOnCircle.y = circlesCenter_Y - circleRadius;
}else{
pointOnCircle.y = circlesCenter_Y + circleRadius;
}
}else{
slope = slope(location.x,location.y);
if(location.x<circlesCenter_X){
pointOnCircle.x = circlesCenter_X - pointOnCircle_X(slope);
}else{
pointOnCircle.x = circlesCenter_X + pointOnCircle_X(slope);
}
pointOnCircle.y = slope * (pointOnCircle.x - circlesCenter_X) + circlesCenter_Y;
}
firstPieceView.center = pointOnCircle;
}
This can be applied similarly for Android, Blackberry, etc too!

How to prevent taps in an area on the screen

I designed an app where the user taps the ball as many times as they can within 30 seconds called iTapping. In the game, the user is able to tap anywhere on the screen for the ball to be tapped. I thought of editing the app so that there would be 'dead spots' where the user would not be able to tap the ball. For example, if the ball is in the upper right-hand corner (lets say in an area of about 100 sq. pts.), and the user taps the ball nothing happens. How would I code this? Please let me know if this is not clear enough.
Here is the .m file:
CGPoint Destination;
CGFloat xamt, yamt;
CGFloat speed21 = 40;
CGFloat xMin21 = 24;
CGFloat xMax21 = 297;
CGFloat yMin21 = 74;
CGFloat yMax21 = 454;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
if ([self Intersecting:location :Ball]) {
number21++;
int xRange = xMax21 - xMin21;
int yRange = yMax21 - yMin21;
int xRand = (arc4random() % xRange) + xMin21;
int yRand = (arc4random() % yRange) + yMin21;
Destination = CGPointMake(xRand, yRand);
xamt = ((Destination.x - Ball.center.x) / speed21);
yamt = ((Destination.y - Ball.center.y) / speed21);
if (number21 == 65) {
[timer invalidate];
}
}
}
-(BOOL)Intersecting:(CGPoint)loctouch:(UIImageView *)enemyimg {
CGFloat x1 = loctouch.x;
CGFloat y1 = loctouch.y;
CGFloat x2 = enemyimg.frame.origin.x;
CGFloat y2 = enemyimg.frame.origin.y;
CGFloat w2 = enemyimg.frame.size.width;
CGFloat h2 = enemyimg.frame.size.height;
if ((x1>x2)&&(x1<x2+w2)&&(y1>y2)&&(y1<y2+h2))
return YES;
else
return NO;
}
I suggest to put the touch management back to the ball itself (see this answer). So whenever a touch is received, you can be sure it's clicking on the ball. All you need to do is to check whether the ball is currently in the dead zone, thus ignore the touch.

iOS: Single Finger rotation and translation of an image

Currently i am following this Link to perform Single finger rotation on an image and its working fine.But i am wondering after performing rotation, how to perform translation operation if i want to move the image from one postion of screen to any other position.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// We can look at any touch object since we know we
// have only 1. If there were more than 1 then
// touchesBegan:withEvent: would have failed the recognizer.
UITouch *touch = [touches anyObject];
// A tap can have slight movement, but we're not interested
// in a tap. We want more movement. So if a tap is detected
// fail the recognizer.
if ([self state] == UIGestureRecognizerStatePossible) {
[self setState:UIGestureRecognizerStateBegan];
} else {
[self setState:UIGestureRecognizerStateChanged];
}
// To rotate with one finger, we simulate a second finger.
// The second figure is on the opposite side of the virtual
// circle that represents the rotation gesture.
UIView *view = [self view];
CGPoint center = CGPointMake(CGRectGetMidX([view bounds]), CGRectGetMidY([view bounds]));
CGPoint currentTouchPoint = [touch locationInView:view];
CGPoint previousTouchPoint = [touch previousLocationInView:view];
// use the movement of the touch to decide
// how much to rotate the carousel
CGPoint line2Start = currentTouchPoint;
CGPoint line1Start = previousTouchPoint;
CGPoint line2End = CGPointMake(center.x + (center.x - line2Start.x), center.y + (center.y - line2Start.y));
CGPoint line1End = CGPointMake(center.x + (center.x - line1Start.x), center.y + (center.y - line1Start.y));
//////
// Calculate the angle in radians.
// (From: http://iphonedevelopment.blogspot.com/2009/12/better-two-finger-rotate-gesture.html )
CGFloat a = line1End.x - line1Start.x;
CGFloat b = line1End.y - line1Start.y;
CGFloat c = line2End.x - line2Start.x;
CGFloat d = line2End.y - line2Start.y;
CGFloat line1Slope = (line1End.y - line1Start.y) / (line1End.x - line1Start.x);
CGFloat line2Slope = (line2End.y - line2Start.y) / (line2End.x - line2Start.x);
CGFloat degs = acosf(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));
CGFloat angleInRadians = (line2Slope > line1Slope) ? degs : -degs;
//////
[self setRotation:angleInRadians];
}
I am not sure that my answer will help you, but I fear that you cannot do "one-touch" rotation and "one-touch" translation at the same time. Indeed, your code always interprets a single touch as for rotation, by "simulating" the presence of a second finger touching the display.
What you could do is trying and differentiate two single-finger gestures so to associate one with rotation and the other with translation. For example, the gesture for translation could be a "long touch" (i.e., you touch and keep touching for some time, then you move the object).
Hope this helps, anyway...

removing subview from superview when user moves very fast

how to remove subviews very fast according to user touched point or moving point in the view.I am able to get the user touched point and moved point in the view and also i can remove the subview according with the user touched point using [subview removefromsuperview];.
When the user moves very fast some subviews getting deleted and some of the images not getting deleted.If the user moves slowly then subviews getting deleted exactly.i am placing some of my code is below
`
-(void)deleteSubView:(CGPoint)userCurrentPoint{
for(int i=0;i<[subviews count];i++){
if (CGRectContainsPoint([[subviews objectAtIndex:i] CGRectValue], userCurrentPoint)) {
[[superview viewWithTag:i ] removeFromSuperview];
}
}
`
is there any way to delete subviews according with the user fast moving points.i need your suggestion.
Thanks all
Put this in your header file:
#interface YourView : UIView
#property (nonatomic, assign) CGPoint lastTouchLocation;
#end
This should be the implementation:
#implementation YourView
#synthesize lastTouchLocation;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
self.lastTouchLocation = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
CGPoint oldLocation = self.lastTouchLocation;
CGPoint newLocation = [[touches anyObject] locationInView:self];
for (UIView *subview in self.subviews)
{
if (LineIntersectsRect(oldLocation, newLocation, subview.frame))
{
[subview removeFromSuperview];
}
}
self.lastTouchLocation = newLocation;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
self.lastTouchLocation = CGPointZero;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
self.lastTouchLocation = CGPointZero;
}
#end
What this does: we check whether or not a line between the last received touch and the current one intersects the frame of a subview. Why? See deanWombourne's answer.
Here's the intersection code, originally by habjan - you should put in in the header, too.
static inline BOOL LineIntersectsLine(CGPoint l1p1, CGPoint l1p2, CGPoint l2p1, CGPoint l2p2)
{
CGFloat q = (l1p1.y - l2p1.y) * (l2p2.x - l2p1.x) - (l1p1.x - l2p1.x) * (l2p2.y - l2p1.y);
CGFloat d = (l1p2.x - l1p1.x) * (l2p2.y - l2p1.y) - (l1p2.y - l1p1.y) * (l2p2.x - l2p1.x);
if(d == 0)
{
return false;
}
CGFloat r = (q / d);
q = (l1p1.y - l2p1.y) * (l1p2.x - l1p1.x) - (l1p1.x - l2p1.x) * (l1p2.y - l1p1.y);
CGFloat s = (q / d);
if((r < 0) || (r > 1) || (s < 0) || (s > 1))
{
return false;
}
else
{
return true;
}
}
static inline BOOL LineIntersectsRect(CGPoint p1, CGPoint p2, CGRect r)
{
if (CGRectContainsPoint(r, p1) || CGRectContainsPoint(r, p2))
{
return YES;
}
else
{
CGPoint topLeft = CGPointMake(r.origin.x, r.origin.y);
CGPoint topRight = CGPointMake(r.origin.x + r.size.width, r.origin.y);
CGPoint bottomLeft = CGPointMake(r.origin.x, r.origin.y + r.size.height);
CGPoint bottomRight = CGPointMake(r.origin.x + r.size.width, r.origin.y + r.size.height);
return (LineIntersectsLine(p1, p2, topLeft, topRight) ||
LineIntersectsLine(p1, p2, topRight, bottomRight) ||
LineIntersectsLine(p1, p2, bottomRight, bottomLeft) ||
LineIntersectsLine(p1, p2, bottomLeft, topLeft));
}
}
It's not to do with your enumeration :)
If the user moves their finger quickly, you don't get a list of every pixel they touched, you get some of them. If they were to move fast enough, you might only get the start and end points.
You can't just use the point that their finger is dragged to, you have to work out which subviews have been touched between the last event and this one :)
Think of it as drawing a line between the start and end points and working out which subviews overlap with that line.
If I understand your question correctly, you want to remove all the subviews that the user touches as he drags his finger across your view.
Your problem is that touchesMoved:withEvent: fires at a limited frequency. It isn't guaranteed to fire for every pixel the user touches. As a result, when the user moves his finger quickly, there are gaps between the points reported in touchesMoved:withEvent. If the user moves his finger quickly enough, these gaps may be large enough that they skip over some subviews entirely.
To solve this problem your code needs to remember the position of the previous point, and then test the line segment formed by the previous point and the current point to see if it intersects with the frame of the subview. An efficient algorithm for this is described in: How to test if a line segment intersects an axis-aligned rectange in 2D?

Rotate UIView using UITouch

I have a question on how I should approach rotating a UIView with touch based events.
Quite simply, I want to rotate a UIView around an anchorpoint, depending on where I touch on and off within that view. Imagine a volume knob on a hifi, which you turn with one finger.
I've built a rotation method using CAKeyframeAnimation which carries out a transform.rotation.z, however what I am not sure is if (a) I should use that in conjunction with touch methods, and (b) how I can get the coordinate/angle of the touch in relation to the underlying UIView, and subsequently turn it to point to the touch.
I hope I am making senseā€¦ Can anyone make any suggestions?
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint newLocationPoint = [[touches anyObject] locationInView:self.superview];
int x = self.superview.center.x;
int y = self.superview.center.y;
float dx = newLocationPoint.x - x;
float dy = newLocationPoint.y - y;
double angle = atan2(-dx,dy);
self.layer.position = self.superview.center;
self.layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
NSLog(#"%f,%f ", dx,dy);
}
Put this in your UIView Subclass and Voila!
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint Location = [[touches anyObject] locationInView:self];
int x = ImageView.center.x;
int y = ImageView.center.y;
float dx = Location.x - x;
float dy = Location.y - y;
double a = atan2(-dx,dy);
ImageView.layer.position = diagramImageView.center;
ImageView.layer.transform = CATransform3DMakeRotation(a, 0, 0, 1);
}