I'm trying to implement a drag & drop feature on a UIButton, and it works fine, but I can't figure out a way to determine when the user has let go of the button and finished dragging. The below code works fine for dragging, but I need to be notified when the user has finished dragging and let go of the button.
- (void)viewDidLoad
{
[gesturesBrowserButton addTarget:self action:#selector(wasDragged:withEvent:)
forControlEvents:UIControlEventTouchDragInside];
[gesturesBrowserButton addTarget:self action:#selector(finishedDragging:withEvent:)
forControlEvents:UIControlEventTouchDragExit];
}
- (void)wasDragged:(UIButton *)button withEvent:(UIEvent *)event
{
// get the touch
UITouch *touch = [[event touchesForView:button] anyObject];
// get delta
CGPoint previousLocation = [touch previousLocationInView:button];
CGPoint location = [touch locationInView:button];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
// move button
button.center = CGPointMake(button.center.x + delta_x,
button.center.y + delta_y);
NSLog(#"was dragged");
}
- (void)finishedDragging:(UIButton *)button withEvent:(UIEvent *)event
{
//doesn't get called
NSLog(#"finished dragging");
}
I think that replacing
UIControlEventTouchDragExit
with
UIControlEventTouchDragExit|UIControlEventTouchUpInside
would be a good start to solve your problem.
The event I believe you want here is UIControlEventTouchUpInside. That's the "touch has gone away while inside this control." It's possible you won't get that one at this point, though. You certainly won't get UIControlEventTouchDragExit, since that means the drag has left the control.
If UIControlEventTouchUpInside doesn't work for you, I like to capture UIControlEventAllEvents and just log them as they come in. Then you can see exactly what is fired when.
Related
In my iPhone App I want to implemented functionality as shown below
user can pick a shape(an image) from one view and put in other view
How can I achieve this?
Easiest way is to use a UIPanGestureRecognizer. That will give you messages when the view is moved and when it is dropped. When it is moved, update its center. When it is dropped, check if its position is within the bounds of the view you want to drop in. (You might need to convert coordinates to the target view's coordinate system.) If it is, do the appropriate action.
Try below link drag and drop image around the screen
also try this
You can refer to the previous post which will definitely help you to achieve this functionality...
Basic Drag and Drop in iOS
Building drag and drop interface on iphone
iPhone drag/drop
For all of above link, You have to keep in mind that You need to first get TouchesBegin event for any Control and then you have to get the TouchesMoved event for same control.
In TouchesMoved event, you just have to get the center point (CGPoint) of the Control. And when you release the control will be set at that CGPoint. If this creates problem then you can take that CGPoint in variable and set that Point in TouchesEnded event.
For your case, i think you must have to maintain the Hierarchy of the Views...Else while dragging you view may not be visible...
FOR MORE CODING PART :
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%f,%f", self.center.x, self.center.y);
CGPoint newLoc = CGPointZero;
newLoc = [self.mainView convertPoint:[[touches anyObject] locationInView:self.superview] toView:self.superview];
float newX = newLoc.x + self.superview.frame.origin.x + (self.frame.size.width /2) + [[touches anyObject] locationInView:self].x ;
float newY = newLoc.y - (((UIScrollView *)self.superview).contentOffset.y *2) ;
NSLog(#"content offset %f", ((UIScrollView *)self.superview).contentOffset.y);
self.scrollParent.scrollEnabled = NO;
NSLog(#"%f,%f", self.center.x, self.center.y);
newLoc = CGPointMake(newX, newY);
[self.superview touchesCancelled:touches withEvent:event];
[self removeFromSuperview];
NSLog(#"%f,%f", self.center.x, self.center.y);
self.center = CGPointMake(newLoc.x, newLoc.y);
[self.mainView addSubview:self];
NSLog(#"%f,%f", self.center.x, self.center.y);
[self.mainView bringSubviewToFront:self];
isInScrollview = NO;
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[UIView beginAnimations:#"stalk" context:nil];
[UIView setAnimationDuration:.001];
[UIView setAnimationBeginsFromCurrentState:YES];
UITouch *touch = [touches anyObject];
self.center = [touch locationInView: self.superview];
[UIView commitAnimations];
if ((self.center.x + (self.frame.size.width / 2)) > 150 && hasExitedDrawer && !self.scrollParent.dragging ) {
self.scrollParent.scrollEnabled = NO;
[self.delegate moveItemsDownFromIndex: ((self.center.y + (self.scrollParent.contentOffset.y)) / 44) + 1 ];
//NSLog(#"%i", ((self.center.y + (self.scrollParent.contentOffset.y *2)) / 44) + 1);
}
if (self.center.x + (self.frame.size.width / 2) < 150) {
hasExitedDrawer = YES;
}
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ((self.center.x + (self.frame.size.width / 2)) > 150 && hasExitedDrawer && !self.scrollParent.dragging ) {
CGPoint newLoc = CGPointZero;
newLoc = [self.scrollParent convertPoint:[[touches anyObject] locationInView:self.superview] toView:self.superview];
float newY = newLoc.y + (self.scrollParent.contentOffset.y *2);
[self.scrollParent insertSubview:self atIndex:((self.center.y + (self.scrollParent.contentOffset.y)) / 44) ];
self.frame = CGRectMake(0, newY, self.frame.size.width, self.frame.size.height);
isInScrollview = YES;
hasExitedDrawer = NO;
}
}
This code may cantains some irrelevant but gives you more idea...
You can use below open source libraries:
Library 1 OBDragDrop
Library 2 AJWDraggableDroppable
Library 3 iOS-DragAndDrop
Library 4 ios-drag-and-drop
Adding drag and drop component to iOS app the Question
i thing you are going to Build a feature like Add to cart in Most of the Shopping Apps like Amazon, Flip kart and etc..
Three step solution
1) u'll have to implement/listen for touch events
2) implement dragging and touch events
3) and then manage movetosuperview method.
I'm quite new to iPhone development and I'm building my first app :)
In one of my view controllers I built a customSlider that should behave as the native "slide to unlock" slider.
My doubt right now is how to implement the "drag outside" behaviour. As said, I would like to have it exactly as the native slider, that means that when the finger is dragging the slider if it moves out the slider, the slider should be going to zero.
My doubt is not on the animation part (I'm already using animation block successfully), but on control events part. Which control event should I use?
I'm using:
[customSlider addTarget:self action:#selector(sliderMoved:) forControlEvents:UIControlEventValueChanged];
to handle the sliding part (finger sliding the cursor), and
[customSlider addTarget:self action:#selector(sliderAction:) forControlEvents:UIControlEventTouchUpInside];
to handle the release part, but the problem is that if I release the finger outside, the sliderAction function it's not called.
EDIT:
I've tried to implement the solution #Bruno Domingues gave me, but I'm realizing that the issue is that by default UISlider keep getting updated even if the finger is dragged outside of it (try to open for example the Brightness section in System Preferences and you'll see that the slider will keep on updating even if you drag outside of it). So my question could be redefined: How to avoid this default behaviour and have the slider updating only when the finger is moving on it?
Simply interrupt the touches methods in your custom subclass and only forward the touches you want acted on to the superclass, like so:
in .h:
#interface CustomSlider : UISlider
#end
in .m:
#import "CustomSlider.h"
#implementation CustomSlider
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesEnded:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesCancelled:touches withEvent:event];
}
#end
Please note that this implementation will start updating the control if your finger moves back to the control. To eliminate this, simply set a flag if a touch is received outside of the view then check that flag in subsequent touches methods.
You need to set a [slider addTarget:self action:#selector(eventDragOutside:) forControlEvents:UIControlEventTouchDragOutside];
And add a function
- (IBAction)eventDragOutside:(UISlider *)sender {
sender.value = 0.0f;
}
My app has 2 buttons, near.
I'm trying to touch this two buttons, at the same time, using only one finger.
There is a way to check if the touch is over this two buttons at same time?
You can (but really shouldnt) touch 2 buttons with one finger like Abizern said, but you can also call 2 methods for one button touch. For example:
-(void)viewDidLoad {
self.myButton = /*initialize the button*/;
[self.myButton addTarget:self action:#selector(callTwoMethods) forControlEvents:UIControlEventTouchUpInside];
}
-(void)callTwoMethods {
[self methodOne];
[self methodTwo];
}
However, this is not always the correct behavior for what you're trying to do, so starting with iOS 3, we can use a bit of jiggering with the UITouch and event mechanism to figure a lot of it out:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([touches count] == 2) {
//we are aware of two touches, so get them both
UITouch *firstTouch = [[allTouches allObjects] objectAtIndex:0];
UITouch *secondTouch = [[allTouches allObjects] objectAtIndex:1];
CGPoint firstPoint = [firstTouch locationInView:self.view];
CGPoint secondPoint = [secondTouch locationInView:self.view];
if ([self.firstButton pointInside:firstPoint withEvent:event] && [self.secondButton secondPoint withEvent:event] || /*the opposite test for firstButton getting the first and secondButton getting the second touch*/) {
//Do stuff
}
}
}
A touch is a point. Although the recommendation is to make controls of a reasonable size, when you touch the screen, you are getting a point, not a region. That point is going to be in one or other of the controls.
You could try to intercept the touch and turn it into a larger rectangle and see if that covers both the buttons at the same time.
EDIT
Have a look at this SO question to see one way of doing intercepting touches.
I have a view control and inside I plan to place some controls like buttons textbox etc... I can drag my view along the x axis like:
1)
2)
with the following code:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if( [touch view] == ViewMain)
{
CGPoint location = [touch locationInView:self.view];
displaceX = location.x - ViewMain.center.x;
displaceY = ViewMain.center.y;
startPosX = location.x - displaceX;
}
CurrentTime = [[NSDate date] timeIntervalSince1970];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
if( [touch view] == ViewMain)
{
CGPoint location = [touch locationInView:self.view];
location.x =location.x - displaceX;
location.y = displaceY;
ViewMain.center = location;
}
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
double time = [[NSDate date] timeIntervalSince1970]-CurrentTime;
UITouch *touch = [[event allTouches] anyObject];
if( [touch view] == ViewMain)
{
CGPoint location = [touch locationInView:self.view];
location.x =location.x - displaceX;
location.y = displaceY;
ViewMain.center = location;
double speed = (ViewMain.center.x-startPosX)/(time*2);
NSLog(#"speed: %f", speed);
}
}
not that I have to add the global variables:
float displaceX = 0;
float displaceY = 0;
float startPosX = 0;
float startPosY = 0;
double CurrentTime;
the reason why I created those variables is so that when I start dragging the view the view moves from the point where I touch it instead of from the middle.
Anyways if I touch a button or image the view will not drag even though the images have transparency on the background. I want to be able to still be able to drag the view regardless if there is an image on top of the view. I where thinking that maybe I need to place a large transparent view on top of everything but I need to have buttons, images etc. I want to be able to drag a view just like you can with:
note that I was able to drag the view regardless of wither I first touched an app/image or text. How could I do that?
I think your problem is that if you touch a UIButton or a UIImageView with interaction enabled, it doesn't pass the touch along.
For the images, uncheck the User Interaction Enabledproperty in IB.
For the buttons that are causing touchesBegan:withEvent:, etc. to not get called, then look at the following link: Is there a way to pass touches through on the iPhone?.
You may want to consider a different approach to this problem. Rather than trying to manually manage the content scrolling yourself you would probably be better off using a UIScrollView with the pagingEnabled property set to YES. This is the method Apple recommends (and it's probably the method used by Springboard.app in your last screenshot). If you are a member of the iOS developer program check out the WWDC 2010 session on UIScrollView for an example of this. I think they may have also posted sample code on developer.apple.com.
Could someone please tell me how to make an image appear when the user taps the screen and make it appear at the position of the tap.
Thanks in advance,
Tate
UIView is a subclass of UIResponder, which has the following methods that might help: -touchesBegan:withEvent:, -touchesEnded:withEvent:, -touchesCancelled:withEvent: and -touchesMoved:withEvent:.
The first parameter of each of those is an NSSet of UITouch objects. UITouch has a -locationInView: instance method which should yield the position of the tap in your view.
You could create an initial star and just move it every time view is touched.
I'm not sure what you're end result will look like.
Note:
This code will give you 1 star that moves with a tap
Here is my code:-
(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
switch ([allTouches count]) {
case 1:
{
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
CGPoint point = [touch locationInView:myView];
myStar.center = point;
break;
}
default:
break;
}
}
It seems implied from the question that you want the user to be able to tap anywhere on the screen and have an image drawn where they tap? As opposed to tapping in a designated place and having the image appear there?
If so, you're probably going to have to go with a custom view. In that case, you'd do something like the following:
Create a subclass of UIView.
Override the touchesBegan method. Call [[touches anyObject] locationInView:self] (where touches is the first argument to the method, an NSSet of UITouch objects) to get the location of the touch, and record it.
Override the touchesEnded method. Determine the location touches ended at using the same method as in step 2.
If the second location is near the first, you'll want to place your image at that location. Record that location and call [self setNeedsDisplay] to cause the custom view to be redrawn.
Override the drawRect method. Here, if the location has been set in step 4, you can use the UIImage method drawAtPoint to draw your image at the selected location.
For further details, this link might be worth a look. Hope that helps!
EDIT: I've notice you've asked essentially the same question before. If you're not happy with the answers given there, it's generally considered better to "bump" the old one, perhaps by editing it to ask for further clarification, rather than create a new question.
EDIT: As requested, some very brief sample code follows. This is probably not the best code around, and I haven't tested it, so it may be a little iffy. Just for clarification, the THRESHOLD allows the user to move their finger a little while tapping (up to 3px), because it's very difficult to tap without moving your finger a little.
MyView.h
#define THRESHOLD 3*3
#interface MyView : UIView
{
CGPoint touchPoint;
CGPoint drawPoint;
UIImage theImage;
}
#end
MyView.m
#implementation MyView
- (id) initWithFrame:(CGRect) newFrame
{
if (self = [super initWithFrame:newFrame])
{
touchPoint = CGPointZero;
drawPoint = CGPointMake(-1, -1);
theImage = [[UIImage imageNamed:#"myImage.png"] retain];
}
return self;
}
- (void) dealloc
{
[theImage release];
[super dealloc];
}
- (void) drawRect:(CGRect) rect
{
if (drawPoint.x > -1 && drawPoint.y > -1)
[theImage drawAtPoint:drawPoint];
}
- (void) touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
touchPoint = [[touches anyObject] locationInView:self];
}
- (void) touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
CGPoint point = [[touches anyObject] locationInView:self];
CGFloat dx = point.x - touchPoint.x, dy = point.y - touchPoint.y;
if (dx + dy < THRESHOLD)
{
drawPoint = point;
[self setNeedsDisplay];
}
}
#end