I have been trying to implement dragable UIButton in iOS by overriding touchesMoved method.
The button shows up , however i am not able to drag it.What am i missing here?
this is what i reffered
This is my .h file.
#interface ButtonAnimationViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *firstButton;
And the .m file.
#implementation ButtonAnimationViewController
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint pointMoved = [touch locationInView:self.view];
self.firstButton.frame = CGRectMake(pointMoved.x, pointMoved.y, 73, 44);
}
Here you have a fully working button dragging example using UIPanGestureRecognizer which, in my opinion, is easier. I tested it before posting the code. Let me know if you have any more questions:
#interface TSViewController ()
#property (nonatomic, strong) UIButton *firstButton;
#end
#implementation TSViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// this code is just to create and configure the button
self.firstButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.firstButton setTitle:#"Button" forState:UIControlStateNormal];
self.firstButton.frame = CGRectMake(50, 50, 300, 40);
[self.view addSubview:self.firstButton];
// Create the Pan Gesture Recognizer and add it to our button
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragging:)];
[self.firstButton addGestureRecognizer:panGesture];
}
// this method will be called whenever the user wants to drag the button
-(void)dragging:(UIPanGestureRecognizer*)panGesture {
// if is not our button, return
if (panGesture.view != self.firstButton) {
return;
}
// if the gesture was 'recognized'...
if (panGesture.state == UIGestureRecognizerStateBegan || panGesture.state == UIGestureRecognizerStateChanged) {
// get the change (delta)
CGPoint delta = [panGesture translationInView:self.view];
CGPoint center = self.firstButton.center;
center.x += delta.x;
center.y += delta.y;
// and move the button
self.firstButton.center = center;
[panGesture setTranslation:CGPointZero inView:self.view];
}
}
#end
Hope it helps!
Related
I want to add multiple UITapGestureRecognizer on UIScrollView but it recognise only one gesture.
I want to add first gesture for touch begin and second one for touch end event.
Following is my code:-
self.tapStartGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
self.tapStartGesture.numberOfTapsRequired = 1;
self.tapStartGesture.numberOfTouchesRequired = 1;
[self.tapStartGesture setState:UIGestureRecognizerStateBegan];
[self.scrollView addGestureRecognizer:self.tapStartGesture];
self.tapEndGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
self.tapEndGesture.numberOfTapsRequired = 1;
self.tapEndGesture.numberOfTouchesRequired = 1;
[self.scrollView addGestureRecognizer:self.tapEndGesture];
- (void)tapGesture:(UITapGestureRecognizer *)sender {
if(sender==self.tapStartGesture) {
NSLog(#"tapStartGesture");
} else if(sender==self.tapEndGesture) {
NSLog(#"tapEndGesture");
}
}
A tap gesture only has one state - "ended". You can't detect when a tap starts using a tap gesture. As you've seen, attempting to use two tap gestures doesn't accomplish what you want.
You need to implement the UIResponder methods touchesBegan and touchesEnded.
You may also want to see UITapGestureRecognizer - make it work on touch down, not touch up?
.
Issue solved by implement custom gesture.
File:-MyGesture.h
#import <UIKit/UIKit.h>
#interface MyGesture : UIGestureRecognizer
#end
File:-MyGesture.m
#import "MyGesture.h"
#implementation MyGesture
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (self.state == UIGestureRecognizerStatePossible) {;
self.state = UIGestureRecognizerStateBegan;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
self.state = UIGestureRecognizerStateEnded;
}
#end
How to Use:-
MyGesture *gesture = [[MyGesture alloc] initWithTarget:self action:#selector(myGesture:)];
[self.scrollView addGestureRecognizer:gesture];
- (void)myGesture:(MyGesture *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
NSLog(#"tapStartGesture");
} else if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"tapEndGesture");
}
}
I have a custom view that will pinch zoom, but I'd like to also slide it around in any direction. The zoom works well, and the moving works well, but so far it's just one or the other. The touchesMoved method never gets called.
The scrollView has a subView which the user will touch and move. It will function like a little map. On the subView are several custom labels. I have a tap Gesture recognizer on the labels, which works fine now, and will still need to work when the zoom and moving is corrected.
I also tried [scrollView setScrollEnabled:NO]; and other things, but that didn't allow both to work. How can touches Moved be called? What will I need to do to allow both to work?
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setUserInteractionEnabled:YES];
scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
[scrollView setMinimumZoomScale:.7];
[scrollView setMaximumZoomScale:1.5];
[scrollView setAutoresizesSubviews:YES];
[scrollView setUserInteractionEnabled:YES];
scrollView.delegate = self;
[self.view addSubview:scrollView];
CGRect mapFrame = CGRectMake(0, 0, 300, 300);//temp numbers
subView = [[UIView alloc]initWithFrame:mapFrame];
subView.userInteractionEnabled=YES;
subView.autoresizesSubviews=YES;
[scrollView addSubview:subView];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touchesMoved");
touch = [touches anyObject];
CGPoint location = [touch locationInView:subView];
CGPoint prevLoc = [touch previousLocationInView:subView];
CGPoint origin = subView.frame.origin;
origin.x += (location.x - prevLoc.x);
origin.y += (location.y - prevLoc.y);
CGRect newFrame = subView.frame;
newFrame.origin = origin;
subView.frame = newFrame;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return subView;
}
//label sample
newCity =[[MyCustomClass alloc] initWithFrame:CGRectMake(x, y, 95, 40)];
newCity.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(touchUp:)];
[newCity addGestureRecognizer:tapGesture];
[subView addSubview:newCity];
I'm also getting this in the log and don't know why. "Ignoring call to [UIPanGestureRecognizer setTranslation:inView:] since gesture recognizer is not active.
" I removed the pan gesture recognizer, after that didn't work. I'm using Xcode 4.5.2
My header is
#interface CitiesViewController : UIViewController <UIScrollViewDelegate, UIGestureRecognizerDelegate>
{
UIView *subView;
}
#property(nonatomic, strong) UIView *subView;
#property(nonatomic, strong) UIScrollView *scrollView;
To solve this I removed the TouchesMoved entirely and used a panGestureRecognizer instead. Hopefully it helps someone.
I added this to my subView:
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[pan setDelegate:self];
[subView addGestureRecognizer:pan];
Add this method:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:subView];
subView.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
}
I never did figure out why that message Ignoring call to [UIPanGestureRecognizer setTranslation:inView:] is still occurring, but this seems to work.
I have a RootViewController with a UIScrollViewController (boardScrollView).
This boardScrollView has a UIImageView as subview (boardImage) to create the board. I can zoom in and out and scroll on the boardImage within the boardScrollView. Works great!
Now I want to drag & drop other UIImageViews into the boardScrollImage within the boardScrollView and also OUT of the boardScrollView.
For these other UIImageViews (Tiles) I have created a subclass of the UIImageView class (TileViewClass).
I have the drag&drop working to drop the Tile INTO the boardScrollView/boardImage and also drag&drop INSIDE the boardScrollView/boardImage but I can not get the drag&drop to OUTSIDE the boardScrollView working..
I think this is because I can not access the views in the rootviewcontroller from the subclass.
Maybe it is even better to place the Tile back to the topview (window) in touchesBegan, and so the determination of the drop-position is always done from the same view.
But I don't know how this can be done...
I have tried [[UIApplication sharedApplication].keyWindow bringSubviewToFront:self.dragObject]; in the touchesBegan method, but this does not do the trick....
Maybe I am missing a removeFromSuperView somewhere?
Anyone any idea how I can get the drag&drop working?
RootViewController.h:
#interface RootViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, strong) IBOutlet UIScrollView *boardScrollView;
#property (nonatomic, strong) UIImageView *dragObject;
#property (nonatomic, assign) CGPoint touchOffset;
#property (nonatomic, assign) CGPoint homePosition;
#property (nonatomic, strong) UIImageView *boardImage;
#end
RootViewController.m:
#implementation RootViewController
#synthesize boardScrollView;
#synthesize dragObject;
#synthesize touchOffset;
#synthesize homePosition;
#synthesize boardImage;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
UIImage *image = [UIImage imageNamed:#"greyblue_numbered_15x15_900x900.png"];
self.boardImage = [[UIImageView alloc] initWithImage:image];
self.boardImage.frame = (CGRect){.origin=CGPointMake(0.0f, 0.0f), .size=image.size};
[self.boardScrollView addSubview:self.boardImage];
self.boardImage.userInteractionEnabled = YES;
self.boardScrollView.contentSize = image.size;
self.boardScrollView.canCancelContentTouches = NO;
self.boardScrollView.userInteractionEnabled = YES;
self.boardScrollView.clipsToBounds = YES;
}
TileImageView.h:
#import <UIKit/UIKit.h>
#interface TileImageView : UIImageView
#property (nonatomic, strong) UIImageView *dragObject;
#property (nonatomic, assign) CGPoint touchOffset;
#end
TileImageView.m:
#import "TileImageView.h"
#implementation TileImageView
#synthesize dragObject;
#synthesize touchOffset;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.exclusiveTouch = YES;
self.userInteractionEnabled = YES;
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count] == 1) {
// one finger
CGPoint touchPoint = [[touches anyObject] locationInView:self.superview];
for (UIImageView *iView in self.superview.subviews) {
if ([iView isMemberOfClass:[TileImageView class]]) {
if (touchPoint.x > iView.frame.origin.x &&
touchPoint.x < iView.frame.origin.x + iView.frame.size.width &&
touchPoint.y > iView.frame.origin.y &&
touchPoint.y < iView.frame.origin.y + iView.frame.size.height)
{
self.dragObject = iView;
self.touchOffset = CGPointMake(touchPoint.x - iView.frame.origin.x,
touchPoint.y - iView.frame.origin.y);
[self.superview bringSubviewToFront:self];
}
}
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint touchPoint = [[touches anyObject] locationInView:self.superview];
CGRect newDragObjectFrame = CGRectMake(touchPoint.x - touchOffset.x,
touchPoint.y - touchOffset.y,
self.dragObject.frame.size.width,
self.dragObject.frame.size.height);
self.dragObject.frame = newDragObjectFrame;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UIView *iView in self.superview.subviews) {
if ([iView isMemberOfClass:[UIScrollView class]])
{
CGPoint touchPointScreen = [[touches anyObject] locationInView:[UIApplication sharedApplication].keyWindow];
if (touchPointScreen.x > iView.frame.origin.x &&
touchPointScreen.x < iView.frame.origin.x + iView.frame.size.width &&
touchPointScreen.y > iView.frame.origin.y &&
touchPointScreen.y < iView.frame.origin.y + iView.frame.size.height)
{
for (UIView *iView2 in iView.subviews) {
[iView2 addSubview:self.dragObject];
CGPoint touchPointImage = [[touches anyObject] locationInView:iView2];
self.dragObject.frame = CGRectMake(touchPointImage.x - touchOffset.x,
touchPointImage.y - touchOffset.y,
self.dragObject.frame.size.width,
self.dragObject.frame.size.height);
}
}
self.dragObject = nil;
}
}
Basically you'r catching all touch events on the tileimageview and moving its origin CGPoint around.
If a touch event ended, your cycling though all subviews of your tileviews superview.
If any1 of this is a UIScrollview, you try to locate if the touchpoint matches its frame.
So basically you just checking the frame of your uiscrollview. Therefore your touchpoint cannot be outside it.
Solution: you have to check the view in which you want to drop the tileimageview! you can assume that its a sibling of your uiscrollview(what i assume).
Other way, which i would recommend: Create a View - dropgroundview, in which you catch the touches(=> implement touchesend)
there you can drop your views around and check for subclasses of e.g. dropviews.
Solution: I have delegated the touch methodes to the RootViewController. There, I have access to all subviews and most important: the highest view.
Here, I say:
[self.view bringSubviewToFront:self.tmpDragObject];
And then it works!
I have added 20 subviews to scrollview line by line as rows
yPos=0;
for (int i=0; i<24; i++) {
UIView *timeView=[[UIView alloc]initWithFrame:CGRectMake(71, yPos, 909, 60)];
timeView.userInteractionEnabled=TRUE;
timeView.exclusiveTouch=YES;
timeView.tag=i;
NSLog(#"sub vieww tag=:%d",timeView.tag);
timeView.backgroundColor=[UIColor whiteColor];
UILabel *lbltime=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 70, 60)];
lbltime.text=#"VIEW HERE";
lbltime.textColor=[UIColor grayColor];
// [timeView addSubview:lbltime];
[scrlView addSubview:timeView];
yPos=yPos+61;
}
Now when ever I tap on a subview I am not getting the tapped subview properties.
like coordinates. It is giving parent view coordinates
I enabled subview UserInteractionEnabled to Yes.
Can any one tell me how to get tapped subview coordinate and tag value?
DO NOT subclass from UIScrollView, that's exactly why there are gesture recognizers. Also, DO NOT add a separate gesture recognizer to each view.
Add one gesture recognizer to your scroll view, and when it's clicked use the x,y values of the touch to calculate which view was clicked.
You'll need to do a small calculation: (y value of the click + UIScrollView y offset) / 60.
60 is the height of each view. This should return the index of the clicked view.
EDIT:
Code example:
- (void)viewTapped:(UIGestureRecognizer*)recognizer
{
CGPoint coords = [recognizer locationInView:recognizer.view];
int clickedViewIndex = (self.offset.y + coords.y) / 60;
// now clickedViewIndex contains the index of the clicked view
}
UIView *v = recognizer.view;
int tagNum = [v tag];
Using the tagNum you can do your further operatins.
Or v is your object of tapped view.
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
tap.numberOfTapsRequired = 1;
[timeview addGestureRecognizer:tap];
Add this in for loop only.
Make a class extending UIScrollView :
For example :
.h file :
#protocol CustomScrollViewDelegate <NSObject>
#optional
// optional becuase we always don't want to interact with ScrollView
- (void) customScrollViewTouchesEnded :(NSSet *)touches withEvent:(UIEvent *)event;
- (void) customScrollViewDidScroll;
#end
#interface CustomScrollView : UIScrollView
#property (weak, nonatomic) id<CustomScrollViewDelegate> touchDelegate;
// delegate was giving error becuase name is already there in UIScrollView
#end
Code for .m file :
#import "CustomScrollView.h"
#implementation CustomScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesEnded");
if (!self.dragging) {
//NSLog(#"touchesEnded in custom scroll view");
if (_touchDelegate != nil) {
if ([_touchDelegate respondsToSelector:#selector(customScrollViewTouchesEnded:withEvent:)]) {
[_touchDelegate customScrollViewTouchesEnded:touches withEvent:event];
}
}
}
else {
// it wouldn't be called becuase at the time of dragging touches responding stops.
[super touchesEnded:touches withEvent:event];
}
}
#end
Implementing this use subview of scroll view, it will work
for (UILabel *label in [customScrollView subviews]) { // change name of table here
if ([label isKindOfClass:[UILabel class]]) {
if (label.tag == savedtag) { // use your tag
// write the code as desired
}
}
}
I have made an animation that moves across the screen, my animation loops continuously. How can I stop the animation when you tap the animated image, Then let the animation continue when you lift the touch?
I know how to use TouchesMoved to move a specified button like this:
CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
UIControl *control = sender;
control.center = point;
But getting it to work with my animation. I would like the animation to continue after I touch it.
SelectedCellViewController.h
// SelectedCellViewController.h
#import <Accounts/Accounts.h>
#import <QuartzCore/QuartzCore.h>
#interface SelectedCellViewController : UIViewController {
IBOutlet UIImageView *imageView;
UIImageView *rocket;
}
#end
viewControllertoShow.m
#import "SelectedCellViewController.h"
#interface SelectedCellViewController ()
#end
#implementation SelectedCellViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
}
return self;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:#selector(imageSpawn:) withObject:nil afterDelay:3];
}
- (void) imageSpawn:(id) sender
{
UIImage* image = [UIImage imageNamed:#"ae"];
rocket = [[UIImageView alloc] initWithImage:image];
rocket.frame = CGRectMake(-25, 200, 25, 40);
[UIView animateWithDuration:5
delay:0.2f
options:UIViewAnimationCurveEaseInOut | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
animations:^(){rocket.frame=CGRectMake(345, 200, 25, 40);}
completion:^(BOOL fin) {
}];
[self.view addSubview:rocket];
UITapGestureRecognizer *tapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(ballTapped:)];
tapped.numberOfTapsRequired = 1;
[rocket addGestureRecognizer:tapped];
[rocket setUserInteractionEnabled:YES];
}
-(void)ballTapped:(UIGestureRecognizer *)gesture
{
CGPoint location = [gesture locationInView:gesture.view];
//then write code to remove the animation
[self.view.layer removeAllAnimations];
NSLog(#"Tag = %d", gesture.view.tag);
rocket.frame = CGRectMake(location.x,location.y,25,40);
}
- (void)dismissView {
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (void)viewDidUnload {
}
#end
As you had already stated in your question ,you can get the touched point using
CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
Then check check whether this point lies inside the coordinates of the animated UIImageview and then stop animation.
But if you are using scrollview, you won't be able to use this because scrollview will not return any UIView touch events.
As your image view is animating, a better choice will be adding a UITapGestureRecogniser to the image view when you add it s subview, like this
- (void) imageSpawn:(id) sender
{
UIImage* image = [UIImage imageNamed:#"ae"];
UIImageView *rocket = [[UIImageView alloc] initWithImage:image];
rocket.frame = CGRectMake(-25, 200, 25, 40);
[UIView animateWithDuration:5
delay:0.2f
options:UIViewAnimationCurveEaseInOut | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
animations:^(){rocket.frame=CGRectMake(345, 200, 25, 40);}
completion:^(BOOL fin) {
}];
[myScrollView addSubview:rocket];
UITapGestureRecognizer *tapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(ballTapped:)];
tapped.numberOfTapsRequired = 1;
[self.rocket addGestureRecognizer:tapped];
[rocket setUserInteractionEnabled:YES];
}
And in the target function write code to stop the animation:
-(void)ballTapped:(UIGestureRecognizer *)gesture
{
//here also you can get the tapped point if you need
CGPoint location = [gesture locationInView:gesture.view];
//then write code to remove the animation
[self.view.layer removeAllAnimations];
}
EDIT:
If you are trying to stop the image view at the touched point, you can add this to ballTapped event:
rocket.frame = CGRectMake(location.x,location.y,25,40);
But for this you have to declare UIImageView *rocket outside this particular method, i.e. declare to in your header file.
Another way is to add this to the parent view of the animation -
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
self.animating = NO;
return [super hitTest:point withEvent:event];
}
use an (atomic) property, then check the property in the animation to see if it should be stopped. We use this to stop a photo gallery which is running so that the user can manually move the photos. You could also check in here if the point is in a specific area if necessary. This method runs before any touch methods are called.
try like this may be it helps you, in the middle of the animation if you touch on the imageview then animation removes from the view .
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint lastLocation = [touch locationInView: self.view];
if([[touch view] isKindOfClass:[UIImageView class]]){
[youImageView.layer removeAllAnimations];
youImageView.center=lastLocation;//assign your image view center when the animation removes from the view.
}
}
and add #import <QuartzCore/QuartzCore.h> framework.