I'm working on a few basic apps/utilities/games with the iPhone to get a better grip on it.
I currently have a setup where a CGRect
moves wherever the finger moves, however I don't want the CGRect
to go outside the bounds
of the view
.
Original Method
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
startLocation = [touch locationInView:self];
if (startLocation.x < 33){
touchLocation.x = 33;
//NSLog(@"Touch At:%f, %f", touchLocation.x, touchLocation.y);
[self setNeedsDisplay];
}
if (startLocation.x > 288){
touchLocation.x = 288;
//NSLog(@"Touch At:%f, %f", touchLocation.x, touchLocation.y);
[self setNeedsDisplay];
}
if (startLocation.y < 84){
touchLocation.y = 84;
//NSLog(@"Touch At:%f, %f", touchLocation.x, touchLocation.y);
[self setNeedsDisplay];
}
if (startLocation.y > 460){
touchLocation.y = 460;
//NSLog(@"Touch At:%f, %f", touchLocation.x, touchLocation.y);
[self setNeedsDisplay];
}
else {
touchLocation = startLocation;
[self setNeedsDisplay];
}
}
Problem
I've tried a few different methods of this, including using &&
and making it just one statement. The boundaries
are obviously hard coded, and I'm not sure that manually setting the touchLocation
is the best way to keep it in bounds
.
This all seems silly to me when I can easily self.view.bounds
and get the bounding CGRect
. How do I make use of bounds
to do this? Is there a better way to prevent it from happening other than setting touchLocation
manually? Can I somehow place a hard limit on it instead?
What about a switch
statement? Would that work better than the attempted-if
s?
Attempt
I am now attempting to utilize @Brad Goss's method posted below, however it isn't working?
Touch Started: (319.000000,350.000000)
Touch Result: (319.000000,350.000000)
Boundaries calculated: X:(0.000000,288.000000) Y(0.000000,328.000000)
Touch Started is the actual starting touch. Touch Result should be the changed result.
The above log is showing it outside of the defined bounds
, aka not working. I'm not going to repost the code he wrote because it is the exact same.
Attempt Problem
I've discovered the problem. This is the same type situation as above, but with logging for the individual values as they are set.
Touch Started: (293.000000,341.000000)
min x:288.000000
max x:293.000000
min y:328.000000
max y:341.000000
Touch Result: (293.000000,341.000000)
Boundaries calculated: X:(0.000000,288.000000) Y(0.000000,328.000000)
I haven't figured it out, I posted it here as soon as I discovered it. Otherwise I'd forget to update at all, ADD heh.
I'm going to go back and see exactly what's going on in each of the possible situations.
Mistake
Found another problem, the code that calculates the boundaries
subtracts
from the maximums
, but doesn't add
to the minimums
. This is important as this is what the boundary
is for, to stop the rect
from going off the screen.
maxX = b.origin.x + b.size.width/* - [box bounds].size.width */;
minX = b.origin.x/* + [box bounds].size.width */;
maxY = b.origin.y + b.size.height/* - [box bounds].size.height */;
minY = b.origin.y?/* + [box bounds].size.height */;
With this fixed, I can continue working on the original problem.
Mistake 2
Alright, I've fixed another problem. I was adding to the y
value when it was being displayed. I forgot to add it into this new code/mention it. The code now looks like this:
maxX = b.origin.x + b.size.width/* - [box bounds].size.width */;
minX = b.origin.x/* + [box bounds].size.width */;
maxY = b.origin.y + b.size.height/* - [box bounds].size.height + 20*/;
minY = b.origin.y/* + [box bounds].size.height + 20*/;
The size of the circle being displayed is inside of a 64px CGRect
, so 20px
was sufficient to display the circle just above the thumb. Though this is fixed, it still hasn't helped to solve our original problem.
Upper Left corner:
Touch Started: (14.000000,20.000000)
min x:14.000000
max x:32.000000
min y:20.000000
max y:84.000000
Touch Result: (32.000000,84.000000)
Boundaries calculated: X:(32.000000,288.000000) Y(84.000000,276.000000)
It works as it should, but I am confused as to why the correct values are coming out of MAX
instead of MIN
. Something is mixed up here.
Upper right corner:
Touch Started: (303.000000,21.000000)
min x:288.000000
max x:303.000000
min y:21.000000
max y:84.000000
Touch Result: (303.000000,84.000000)
Boundaries calculated: X:(32.000000,288.000000) Y(84.000000,276.000000)
It's preventing me from going off the top of the screen, again from MAX
instead of MIN
. I am still able to fly off the right, as shown above.
Bottom right corner:
Touch Started: (317.000000,358.000000)
min x:288.000000
max x:317.000000
min y:276.000000
max y:358.000000
Touch Result: (317.000000,358.000000)
Boundaries calculated: X:(32.000000,288.000000) Y(84.000000,276.000000)
It's failing on both directions. Now the actual values are coming from MAX
, and the maximum values are coming from MIN
. WTF?
Bottom left corner:
Touch Started: (8.000000,354.000000)
min x:8.000000
max x:32.000000
min y:276.000000
max y:354.000000
Touch Result: (32.000000,354.000000)
Boundaries calculated: X:(32.000000,288.000000) Y(84.000000,276.000000)
Predictably, it only worked for the minimum value. I'm still very confused as to what these functions are supposed to do. Insight would be much appreciated!
SOLVED!
Our long road draws to a close, thankfully. So, because I had no clue, here is what MIN
and MAX
do. It wasn't initially obvious to me due to the confusion that had taken place.
MAX(x,y) will output the larger of the two numbers
// MAX(13,37) = 37
MIN(x,y) will output the smallest of the two numbers
// MIN(13,37) = 13
So this is what you have to do to make use of these functions correctly in this situation:
1. Calculate the Maximum & Minimum values for X and Y.
The lowest value for each is calculated as
view.origin + keepOnScreenRect.width
The highest value of each is calculated as
view.origin + view.size.height/width - keepOnScreenRect.height/width
Don't forget the offset for the finger!
If you are doing what I did, and have the keepOnScreenRect positioned slightly above the user's finger, you should only be adding the extra offset to the Maximum value of Y, as the minimum value of Y is the bottom of the screen/view, which you are physically unable to go lower than 0 at.
If you're wondering why this is done, it is because the user cannot see through his/her finger.
2. Get the location of the touch on touchesBegan:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
startTouchLocation = [touch locationInView:self];
NSLog(@"Touch Started: (%f,%f)", startTouchLocation.x, startTouchLocation.y);
3. Apply the mathematic constraints:
First, we get the MAX
, or highest value, of both the lowest possible value according to our limits, minX
, and startTouchLocation
, for X
. This calculates the limit for the left side of the screen. The result of this is set to currentTouchLocation
.
currentTouchLocation.x = MAX(minX, startTouchLocation.x);
Second, we get the MIN
, or lowest value, of both the highest possible value according to our limits, maxX
, and currentTouchLocation
, for X
. Note that we aren't using the location of the original touch, but instead the resulting location of the MAX
function we just used. This value has already been checked for the left side of the screen, so we check the right.
currentTouchLocation.x = MIN(maxX, currentTouchLocation.x);
That gives us an X
location that is guaranteed to be within our limits. We then perform the same on Y
.
currentTouchLocation.y = MAX(minY, startTouchLocation.y);
currentTouchLocation.y = MIN(maxY, currentTouchLocation.y);
With all this finished, we can now tell the view to redraw itself and no longer fear having our lovely little rectangle cut off in any way.
[self setNeedsDisplay];
Finished Product
/* Generates the limits based on the view's size, taking into account
the width/height of the rect being displayed in it. This should only
be run once, unless the size of the view changes, in which case limits
would have to be recalculated. The same goes for if you were to change
the size of the displaying rect after calculation */
- (void)boundsCalculation {
CGRect viewBounds = [self bounds];
// calculate constraints
maxX = viewBounds.origin.x + viewBounds.size.width - 32;
minX = viewBounds.origin.x + 32;
maxY = viewBounds.origin.y + viewBounds.size.height - 32;
minY = viewBounds.origin.y + 84;
NSLog(@"Boundaries calculated: X:(%f,%f) Y(%f,%f)", minX, maxX, minY, maxY);
}
/* Magic goodness that happens when the user touches the screen.
Note that we don't calculate the bounds every time the user touches
the screen, that would be silly. */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
startTouchLocation = [touch locationInView:self];
NSLog(@"Touch Started: (%f,%f)", startTouchLocation.x, startTouchLocation.y);
// apply constraints
// highest x value between the lowest allotted x value and touch
currentTouchLocation.x = MAX(minX, startTouchLocation.x);
// lowest x value between the highest allotted x value and touch
currentTouchLocation.x = MIN(maxX, currentTouchLocation.x);
// highest y value between the lowest allotted y value and touch
currentTouchLocation.y = MAX(minY, startTouchLocation.y);
// lowest y value between the highest allotted y value and touch
currentTouchLocation.y = MIN(maxY, currentTouchLocation.y);
// NSLog(@"Touch Result: (%f,%f)", currentTouchLocation.x, currentTouchLocation.y);
// If you don't do this you won't see anything
[self setNeedsDisplay];
}
Today was a good day for learning.