How can I move UIView with touch keeping it inside of a circle? - uiview

I want to move a UIView inside of a circle. The UIView moves every point inside the circle but not touch border line of the circle. I am calculating distance between circle and the UIView.
var distance = sqrt(
pow((touchPoint.x - selfCenter.x), 2) + pow((touchPoint.y - selfCenter.y), 2)
)
And limiting the UIView movement towards out of the circle
if distance <= radius {
theUIView.center = touchPoint
}
The problem starts here, if touch move out from circle the UIView stuck at the border, inside the circle. That is why I am trying write else statement as far as I have tried this.
if distance <= radius {
theUIView.center = touchPoint
} else {
theUIView.center = CGPointMake(
touchPoint.x / distance * radius,
touchPoint.y / distance * radius
)
}
Question is, how I can keep the UIView inside the circle and keep moving if touches keep moving. A hint would be great.
There are similar questions here -like this- but did not helped.

Your else case looks wrong. If you want to "project" a point outside of the circle
onto the circle boundary then it should be
if distance <= radius {
theUIView.center = touchPoint
} else {
theUIView.center = CGPointMake(
selfCenter.x + (touchPoint.x - selfCenter.x) / distance * radius,
selfCenter.y + (touchPoint.y - selfCenter.y) / distance * radius
)
}
Remark: The distance can be more easily computed using the hypot() function:
var distance = hypot(touchPoint.x - selfCenter.x, touchPoint.y - selfCenter.y)

Related

Restrict Touch on ColorPicker UIView - iOS

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

convert uitouch dragging points to custom logic of goal selection

Right now i am working on custom control for fitness related app following is the image of control i have developed so far.
the red view can be draggable vertically only from inner most circle to outer circle.
i have so far managed to drag view vertically from inner most circle to outer circle.
here what i want to achieve,
each circle represent 2500 steps for e.g. innermost circle is 0,then 2500,5000 till outer circle 15000
user can select goal in increment of 500 only
so i am stuck at how to convert red view's sliding points to steps
also i have noticed when i swipe fast i got irregular interval of current touch points so steps increment are low on fast swipe & high on low speed swipe.
any help pointing towards solution is highly appreciated.
here is my code so far.
Circle Code
I would use an overlay UIScrollView for the red draggable part. And only allow vertical scrolling and set pagingEnabled to NO. And set its delegate as the viewcontroller.
Then, you can make your custom paging for 500step per page as follows by setting kCellHeigth to the number of pixels corresponding to 500steps in your image:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
CGFloat kMaxIndex = 30; //=15000/500
CGFloat targetY = scrollView.contentOffset.y + velocity.y * 60.0;
CGFloat targetIndex = round(targetY / (kCellHeigth + kCellSpacing));
if (targetIndex < 0)
targetIndex = 0;
if (targetIndex > kMaxIndex)
targetIndex = kMaxIndex;
targetContentOffset->y = targetIndex * (kCellHeigth + kCellSpacing);
}
-- New Solution After looking at your code --
I see that for iPhone5(width=320) the radius delta between adjacent circles are roughly 20 points. And this space corresponds to 2500steps.
So 1 point = 125 steps
Since you want 500 step increments, it corresponds to 4 points in the iPhone5/5s screen. Maybe when the users touch ends, you can try to snap the navigator to the closest 4 point grid:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"%s",__FUNCTION__);
UITouch *touch = [touches anyObject];
touchPoint = [touch locationInView:_navView];
CGPoint newCenter = CGPointMake(_navView.center.x, _navView.center.y + (touchPoint.y - touchStart.y));
CGFloat yDelta = newCenter.y - _navMinPoint.y;
int yDeltaMultiple = yDelta / 4;
int newYDelta = yDeltaMultiple * 4;
newCenter.y = newYDelta;
_navView.center = newCenter;
_labelSteps.center = [self setStepsLabelfromNavView];
}
I didn't write these in Xcode so there might be typos/errors. If this works in iPhone 5/5s, then we will need to change 4 points to a variable.

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

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

Sprite Kit - Move scene and camera with sprite when sprite is going off screen

I am trying to make a scene that extends up off the screen for a certain distance, and I want the camera to stay centered on the player node. When the player reaches the top of the screen, I want the bottom nodes to disappear below the screen bounds and the new off screen part of the scene to become visible. Like Mario!
I create the scene size like this:
CGSize screenSize = CGSizeMake(skView.bounds.size.width, skView.bounds.size.height + (skView.bounds.size.height/3));//+ (skView.bounds.size.height/3)
scene = [GameScene sceneWithSize:screenSize];
And then, add all of my nodes to a WorldNode as in This question.
I have modified it to be:
- (void)didSimulatePhysics
{
CGFloat margin = self.size.height/3;// previously defined
// make sure the cat's position is defined in scene coordinates
CGPoint catPosition = [self convertPoint:cat.position fromNode:cat.parent];
if (catPosition.y > (self.size.height - margin))
{
CGPoint worldPosition = worldNode.position;
worldPosition.y -= catPosition.y - (self.size.height - margin);
worldNode.position = worldPosition;
}
else if (catPosition.y < (self.size.height - margin))
{
CGFloat maxWorldHeight = self.size.height;
CGPoint worldPosition = worldNode.position;
// this keeps the cat on the margin line
if (worldPosition.y != worldStartPosition.y)
{
worldPosition.y += (self.size.height - margin) - catPosition.y;
}
if (worldPosition.y > maxWorldHeight) // assume maxWorldHeight is defined
{
worldPosition.y = maxWorldHeight;
}
worldNode.position = worldPosition;
}
}
Ok, I have it almost working. I can't seem to stop the world at the top boarder of the scene, and the character does not fall when he reaches below the margin, as in the world keeps following him down past the worlds initial start point. How can I get the screen to only move up and down if the character is above the margin, and then if the character is below the margin, the screen does not move?
You want to move your world node down by an amount equal to how far the cat is above where you want it to be. Your didSimulatePhysics method would end up looking something like this:
- (void)didSimulatePhysics
{
CGFloat margin; // set this to be whatever you want it to be
// make sure the cat's position is defined in scene coordinates
CGPoint catPosition = [self convertPoint:cat.position fromNode:cat.parent];
if (catPosition.y > (self.size.height - margin))
{
// the cat is above the margin, we need to move the world node down until the
// cat is at the margin
// pull world position into local variable so you can modify the y component
CGPoint worldPosition = worldNode.position;
// move the world down so the cat is on the margin
worldPosition.y -= catPosition.y - (self.size.height - margin);
worldNode.position = worldPosition;
}
else
{
// the cat is below the margin, we need to move the world node up, but only
// until the world node is at its starting position
CGPoint worldPosition = worldNode.position;
// move the cat up to the margin line
worldPosition.y += (self.size.height - margin) - catPosition.y;
// if this caused the world to be too high, move it back down
if (worldPosition.y > maxWorldHeight)
{
worldPosition.y = maxWorldHeight;
}
worldNode.position = worldPosition;
}
}
One thing to take note of is that maxWorldHeight is not how tall the world is, but rather how far above the bottom of the screen it's allowed to be. You should set it to the y-component of the word node's position when you first place it in the scene.
- (void)didSimulatePhysics
{
CGPoint target = [self pointToCenterViewOn:cat.position];
CGPoint newPosition = _worldNode.position;
newPosition.x += (target.x - worldNode.position.x) * 0.1f;
newPosition.y += (target.y - worldNode.position.y) * 0.1f;
worldNode.position = newPosition;
}
- (CGPoint)pointToCenterViewOn:(CGPoint)centerOn
{
CGFloat x = Clamp(centerOn.x, self.size.width / 2, worldNode.size.width - self.size.width / 2);
CGFloat y = Clamp(centerOn.y, self.size.height / 2, worldNode.size.height - self.size.height/2);
return CGPointMake(-x, -y);
}

How to detect Collision between 2 UIViews

I have a UIImageView which contains my main Character, and I have made the UIImageView appear circle see below code for creating my character
copter = [[UIImageView alloc] initWithFrame:CGRectMake(100, 500, 90, 90)];
[copter setContentMode:UIViewContentModeScaleAspectFit];
copter.layer.cornerRadius = roundf(copter.frame.size.width/2.0);
copter.layer.masksToBounds = YES;
[copter startAnimating];
[[self view] addSubview:copter];
[self setBat:copter];
I am having trouble with my collision between my character and other objects in the game. The collision is being detected on the rectangle rather than the circle. I have searched everywhere for an answer to fix this but no luck.
Here is my collision code I am using:
self.batVelocity += [link duration]*FBDownardBatAccelerationIpad;
[[self copter] setFrame:CGRectOffset([[self copter] frame], 0, [self batVelocity])];
UIView *removeBlock = nil;
for (UIView *block in [self blocks]) {
[block setFrame:CGRectOffset([block frame], [link duration]*FBSidewaysVelocityIpad, 0)];
if (CGRectIntersectsRect([[self copter] frame], [block frame])) {
[self failed];
So basically I need the circle bounds of the character to collide with a rectangle object, not the rectangle bounds of which the circle is in. I hope that makes sense.
Thanks in advance
I'd use a CGPathRef or CGMutablePathRef. So as #user3386109 suggests, check for collision between rects first, then if you have a pathRef for your sprite or whatever it is you can use CGPathContainsPoint(). Quartz has a few other comparison functions for CGPathRef for Rects as well, and for comparing a path against a second path, check the docs. I find CGPathRef to be quite efficient, but don't forget to release it with a CGPathRelease() to match every CGPathCreate or CGPathCopy() function or the leaks can add up real fast..
Right, so first you check for overlapping rectangles (which you've already done). When you find an overlap, then you need to refine the collision test. For each corner of the offending rectangle, compute the distance from the center of your character to the corner of the offending rectangle. If the distance from the center to any of the corners is less than the radius of the circle, then you have a collision. To optimize the code a little bit, compute (dx * dx + dy * dy) and compare that to the radius squared. That way you don't have to compute any square roots.
Upon further review, you also need to do an edge check in addition to the corner check. For example, if the top y value is above the center of the circle and the bottom y value is below the center of the circle, then compute the difference in x between the rectangle left edge and the center of the circle, and if that distance is less than the radius, then a collision has occurred. Likewise for the other three edges of the rectangle.
Here's some pseudo-code for the corner checking
int dx, dy, radius, radiusSquared;
radiusSquared = radius * radius;
for ( each rectangle that overlaps the player rectangle )
{
for ( each corner of the rectangle )
{
dx = corner.x - center.x;
dy = corner.y - center.y;
if ( dx * dx + dy * dy < radiusSquared )
Collision!!!
}
}

Resources