Prevent dragging UIButton outside of its UIView - objective-c

I've got two UIViews on my window: one to hold player scores, (a sidebar), and a main play area. They both fit on the UIWindow, and neither scroll. The user can drag UIButtons around on the main play area – but at present, they can drop them onto the sidebar. Once they do, they can't drag them again to bring them back, presumably because you're then tapping on the second view, which doesn't contain the button in question.
I'd like to prevent anything inside the main view being moved onto the sidebar view. I've managed this, but I need the drag to be released if the player's finger moves off that view. With the code below, the button keeps moving with the finger, but just won't go past the X coordinate of the view. How can I go about this? Dragging is enabled using this call:
[firstButton addTarget: self action: #selector(wasDragged: withEvent:) forControlEvents: UIControlEventTouchDragInside];
To this method:
- (void) wasDragged: (UIButton *) button withEvent: (UIEvent *) event
{
if (button == firstButton) {
UITouch *touch = [[event touchesForView:button] anyObject];
CGPoint previousLocation = [touch previousLocationInView:button];
CGPoint location = [touch locationInView:button];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
if ((button.center.x + delta_x) < 352)
{
button.center = CGPointMake(button.center.x + delta_x, button.center.y + delta_y);
} else {
button.center = CGPointMake(345, button.center.y + delta_y);
}
}
}

implement
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
touch delegate method, then check the location of the UITouch, if the location is outside of the bounds you want to allow (the first view), then don't move it any further. You could also kill the touch at the point the user drags outside the view using a BOOL iVar
//In .h file
BOOL touchedOutside;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
touchedOutside = NO;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!touchedOutside) {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:firstView];
if (location.x < UPPER_XLIMIT && location.x > LOWER_XLIMIT) {
if (location.y < UPPER_YLIMIT && location.x > LOWER_YLIMIT) {
//Moved within acceptable bounds
button.centre = location;
}
} else {
//This will end the touch sequence
touchedOutside = YES;
//This is optional really, but you can implement
//touchesCancelled: to handle the end of the touch
//sequence, and execute the code immediately rather than
//waiting for the user to remove the finger from the screen
[self touchesCancelled:touches withEvent:event];
}
}

Related

Drag and drop. Nudging other images while moving

I've implemented drag and drop for multiple images which works fine but I'm facing one issue. When I'm dragging one image, other images will be nudged along when my finger moves over them whilst Im still dragging the original image. I'd love to be able to have only one image moveable at once.
Heres some of my code.
-(void)touchesBegan: (NSSet *) touches withEvent:(UIEvent *)event{
UITouch* touch = [touches anyObject];
for (UIImageView *noseImage in noseArray) {
if ([touch.view isEqual:noseArray]) {
firstTouchPoint = [touch locationInView:[self view]];
xd = firstTouchPoint.x - [[touch view]center].x;
yd = firstTouchPoint.y - [[touch view]center].y;
}
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint oldPoint = [touch previousLocationInView:touch.view];
CGPoint newPoint = [touch locationInView:touch.view];
CGPoint diff = CGPointMake(newPoint.x - oldPoint.x, newPoint.y - oldPoint.y);
for (UIImageView *noseImageView in noseArray) {
if (CGRectContainsPoint(noseImageView.frame, newPoint)) {
CGPoint cntr = [noseImageView center];
[noseImageView setCenter:CGPointMake(cntr.x + diff.x, cntr.y + diff.y)];
}
}
}
Typically you would determine which image is being dragged in the touchesBegan: and remember that. Then in the touchesMoved:, move the remembered image the given amount.
But a Gesture Recogniser works a lot easier than these low level methods, so I suggest you use that instead.
You wrote:
for (UIImageView *noseImage in noseArray) {
if ([touch.view isEqual:noseArray]) {
Are you sure the second line shouldn't be the following?
if ([touch.view isEqual: noseImage]) {

i need to auto resize the other views while dragging a particular view on a Parent View

I'm sub viewing four or five Views on a Parent View. And i want resize the other Views while editing a particular view using touch methods. If i touch and drag a particular view to the right , the view which is in the right side have resize automatically. This is the requirement for all the sides while dragging a View (Right,Left,Top,Bottom). Any suggestion.
thanks.
I don't know this is exactly what you are asking for, but maybe it helps.
You can use touchesMoved method to find out where you are dragging your view (which direction):
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
CGPoint prevLocation = [touch previousLocationInView:self.view];
if (location.x - prevLocation.x > 0)
{
NSLog(#"resize right view");
}
else if (location.x - prevLocation.x < 0)
{
NSLog(#"resize left view");
}
if (location.y - prevLocation.y > 0)
{
NSLog(#"resize bottom view");
}
else if (location.y - prevLocation.y < 0)
{
NSLog(#"resize top view");
}
}
Probably you have to add some bool variable which tell you do you drag any view and when you do change the frame.

How to move a UIImageView with Touch

I'm trying to create moving functionality to my imageView (maskPreview in the code below), so that users can move a picture, which is contained in maskPreview, around the screen. Here's my code for touch begin and touch moved:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count]==1) {
UITouch *touch= [touches anyObject];
originalOrigin = [touch locationInView:maskPreview];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count]==1) {
UITouch *touch = [touches anyObject];
CGPoint lastTouch = [touch previousLocationInView:self.view];
CGFloat movedDistanceX = originalOrigin.x-lastTouch.x;
CGFloat movedDistanceY = originalOrigin.y-lastTouch.y;
[maskPreview setFrame:CGRectMake(maskPreview.frame.origin.x+movedDistanceX, maskPreview.frame.origin.y + movedDistanceY, maskPreview.frame.size.width, maskPreview.frame.size.height)];
}
}
but I'm getting some weird responses from the app. I haven't put restrictions on how far the imageview can move, i.e. to prevent it from going out of the screen, but even if it's a small move, my imageview goes wild and disappears.
Thanks alot in advance for all the help
Implementing touchesBegan and so on is way overkill in this modern world. You're just confusing the heck out of yourself, and your code will quickly become impossible to understand or maintain. Use a UIPanGestureRecognizer; that's what it's for. Making a view draggable with a UIPanGestureRecognizer is trivial. Here's the action handler for a UIPanGestureRecognizer that makes the view draggable:
- (void) dragging: (UIPanGestureRecognizer*) p {
UIView* vv = p.view;
if (p.state == UIGestureRecognizerStateBegan ||
p.state == UIGestureRecognizerStateChanged) {
CGPoint delta = [p translationInView: vv.superview];
CGPoint c = vv.center;
c.x += delta.x; c.y += delta.y;
vv.center = c;
[p setTranslation: CGPointZero inView: vv.superview];
}
}
There are two problems with your code. First, this line is wrong:
CGPoint lastTouch = [touch previousLocationInView:self.view];
It should be this:
CGPoint lastTouch = [touch previousLocationInView:maskPreview];
Really, you shouldn't even be using previousLocationInView:. You should just be using locationInView: like this:
CGPoint lastTouch = [touch locationInView:maskPreview];
Second, you are getting the signs of movedDistanceX and movedDistanceY wrong. Change them to this:
CGFloat movedDistanceX = lastTouch.x - originalOrigin.x;
CGFloat movedDistanceY = lastTouch.y - originalOrigin.y;
Also, the documentation for touchesBegan:withEvent: says this:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, if only as stub (empy) implementations.
So make sure you're also overriding touchesEnded:withEvent: and touchesCancelled:withEvent:.
Anyway, you can do this quite a bit more simply. One way is to make touchesBegan:withEvent: empty and do all the work in touchesMoved:withEvent: by using both previousLocationInView: and locationInView:, and updating maskPreview.center instead of maskPreview.frame. You won't even need the originalOrigin instance variable:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count]==1) {
UITouch *touch = [touches anyObject];
CGPoint p0 = [touch previousLocationInView:maskPreview];
CGPoint p1 = [touch locationInView:maskPreview];
CGPoint center = maskPreview.center;
center.x += p1.x - p0.x;
center.y += p1.y - p0.y;
maskPreview.center = center;
}
}
Another way to do this is by using a UIPanGestureRecognizer. I leave that as an exercise for the reader.

Bug when moving a UIView horizontally

I've created a puzzle engine for iPad (iOS SDK 4.2)
I have a PuzzleViewController that controls a UIView (rootView) that holds a smaller UIView (puzzleView) and twelve puzzle pieces (PuzzlePieceView class extends UIImageView).
The ideia is very simple. The puzzlePieces are around the puzzleView and are picked and dragged to the puzzleView. When the pieces are picked they double they're size and are centered to place where the finger is touching.
When they're dropped in the right place they stay put (they're removed from rootView and added as subviews to the puzzleView) if they're drop in the wrong place they return to the original position and size.
Currently I'm overriding touches Began/Moved/Ended/Cancelled in the PuzzlePieceView class so that each PuzzlePiece controls its own movements.
here's what they do
#pragma mark UIResponder overriding
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesBegan");
if (!isDeployed) {
if (self.initialSize.width == 0 || self.initialSize.height ==0) { //if there is still no initial size
self.initialSize = self.frame.size;
}
NSLog(#"self.initialLocation.x %f %f", self.initialLocation.x, self.initialLocation.y);
if (self.initialLocation.x == 0. || self.initialLocation.y == 0.) { //if there is still no initial location
self.initialLocation = self.center; //record the initial location
}
UITouch *touch = [[event allTouches] anyObject];
self.center = [touch locationInView:self.superview]; //set the center of the view as the point where the finger touched it
[self.superview bringSubviewToFront:self]; //the piece brings itself as frontMostView so that it is always visible and never behind any other piece while moving
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesMoved");
if (self.frame.size.width == self.initialSize.width) {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.image.size.width, self.image.size.height);
}
if (!isDeployed) {
UITouch *touch = [[event allTouches] anyObject];
self.center = [touch locationInView:self.superview]; //set the center of the view as the point where the finger moved to
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesEnded");
if (!isDeployed) {
UITouch *touch = [[event allTouches] anyObject];
NSLog(#"point %# x%f y%f", self.puzzleView, [touch locationInView:self.puzzleView].x, [touch locationInView:self.puzzleView].y);
if (CGRectContainsPoint(self.dropZone,[touch locationInView:self.puzzleView])) {
[self removeFromSuperview];
[self.puzzleViewController addPiece:self];
self.center = self.finalCenter;
self.isDeployed = YES;
}
else {
[self restoreToInitialSize];
[self restoreToInitialPosition];
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"PuzzlePiece touchesCancelled");
if (!isDeployed) {
[self restoreToInitialSize];
[self restoreToInitialPosition];
}
}
#pragma mark -
Seemed pretty easy and it was.
I've got only one bug left.
When I try to make an horizontal move on any puzzlePiece the touch gets cancelled.
This only happens when the move is started in a fast way. If i touch the piece and wait for a moment (about a second) the pieces move perfectly.
The pieces also move perfectly in the vertical direction.
I've tried not changing the puzzlePiece size when the view is first touched bu the bug remains...
I found the solution to this.
One of my team-mates registered a UISwipeGestureRecognizer in the RootView instead of the specific subview were the swipeGesture should be recognized.

Point not in Rect but CGRectContainsPoint says yes

If I have a UIImageView and want to know if a user has tapped the image. In touchesBegan, I do the following but always end up in the first conditional. The window is in portrait mode and the image is at the bottom. I can tap in the upper right of the window and still go into the first condition, which seems very incorrect.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
if(CGRectContainsPoint(myimage.frame, location) == 0){
//always end up here
}
else
{ //user didn't tap inside image}
and the values are:
location: x=303,y=102
frame: origin=(x=210,y=394) size=(width=90, height=15)
Any suggestions?
First, you get the touch with:
UITouch *touch = [[event allTouches] anyObject];
Next, you want to be checking for the locationInView relative to your image view.
CGPoint location = [touch locationInView:self]; // or possibly myimage instead of self.
Next, CGRectContainsPoint returns a boolean, so comparing it to 0 is very odd. It should be:
if ( CGRectContainsPoint( myimage.frame, location ) ) {
// inside
} else {
// outside
}
But if self is not myimage then the myimage view may be getting the touch instead of you - its not clear from your question what object self is it is is not a subclass of the UIImageView in question.
Your logic is simply inverted. The CGRectContainsPoint() method returns bool, i.e. true for "yes". True is not equal to 0.