IOS: UIgesture for drag an drop - ios

In my app I should implement the drag and drop of a imageView; the problem is that my imageView is inside a scrollview; it's my code
- (void) viewDidLoad{
[super viewDidLoad];
UILongPressGestureRecognizer *downwardGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(dragGestureChanged:)];
[scrollViewAlfabeto addGestureRecognizer:downwardGesture];
for (UIGestureRecognizer *gestureRecognizer in myscrollView.gestureRecognizers)
{
[gestureRecognizer requireGestureRecognizerToFail:downwardGesture];
}
}
- (void) dragGestureChanged:(UIPanGestureRecognizer*)gesture
{
CGPoint point = [gesture locationInView:scrollViewAlfabeto];
if (gesture.state == UIGestureRecognizerStateBegan)
{
[imageViewToMove removeFromSuperview];
[self.view addSubview:imageViewToMove];
UIView *draggedView = [myscrollView hitTest:point withEvent:nil];
if ([draggedView isKindOfClass:[UIImageView class]])
{
imageViewToMove = (UIImageView*)draggedView;
}
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
imageToMove.center = point;
}
else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateFailed)
{
// Determine if dragged view is in an OK drop zone
// If so, then do the drop action, if not, return it to original location
NSLog(#"point.x final:%f", point.x);
NSLog(#"point.y final:%f", point.y);
if (CGRectContainsPoint(goal.frame, point)){
imageToMove.frame = CGRectMake(167, 159, 100, 100);
}
else{
[imageToMove removeFromSuperview];
[myscrollView addSubview:imageToMove];
[imageToMove setFrame:CGRectMake(12, 38, 100, 100)];
imageToMove = nil;
}
}
}
Then with my code I'm able to take an imageView from scrollView with longpress, drag it inside "self.view"; also if I drop this imageView over another imageView in self.view it puts on it. It work fine. But I have two problems:
1- when I drag this imageView and I do
[imageViewToMove removeFromSuperview];
[self.view addSubview:imageViewToMove];
the image view don't appear under my finger but in other position and I want that it remains under my finger
2- This code work only the first time when I launch my viewcontroller, because If I don't drop the imageview over other imageview inside self.view, itr return inside scrollview, but If I want drag it a second time, it don't work.
Can you help me?

I'm not advanced in objc, but...
Allow user ineraction [self.view userInteractionEnabled:YES];
Try [self.view setMultipleTouchEnabled:YES];
Taken from the documentation of UIView in relation to userInteractionEnabled:
Discussion
When set to NO, user events—such as touch and
keyboard—intended for the view are ignored and removed from the event
queue. When set to YES, events are delivered to the view normally. The
default value of this property is YES.
During an animation, user interactions are temporarily disabled for
all views involved in the animation, regardless of the value in this
property. You can disable this behavior by specifying the
UIViewAnimationOptionAllowUserInteraction option when configuring the
animation.
Hope it works

Related

UITapGestureRecognizer only detects parent view tap

I am using UITapGestureRecognizer for detecting which UIView was tapped on my screen but for some reason it only detects the parent view tap, for example below code logs only parent views tag. How do i detect subview taps which are present on main view. Please suggest.
Inside View did load :-
UITapGestureRecognizer *viewTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(actionForViewTapped:)];
[self.view addGestureRecognizer:viewTapRecognizer];
Method outside view did load.
-(void) actionForViewTapped:(UITapGestureRecognizer*)sender {
NSLog(#"view tapped");
UIView *view = sender.view;
NSLog(#"view tag is %lu", view.tag); //Always prints parent view tag.
if(view.tag == 10){
NSLog(#"tag1 tapped"); //Not called
}
if(view.tag == 20){
NSLog(#"tag 2 tapped"); //Not called
}
}
We have more options to find detecting on sub view by tap gesture
CHOICE 1:Directly tap to SubView
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapSubView)];
tapGesture.numberOfTapsRequired = 1;
[subView addGestureRecognizer:tapGesture];
CHOICE 2:Finding tap on SubView through Parent View
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapSubView)];
tapGesture.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapGesture];
-(void)tapSubView:(UITapGestureRecognizer *)sender
{
UIView* view = sender.view;
CGPoint loc = [sender locationInView:view];
UIView* subview = [view hitTest:loc withEvent:nil];
//OR
CGPoint point = [sender locationInView:sender.view];
UIView *viewTouched = [sender.view hitTest:point withEvent:nil];
if ([viewTouched isKindOfClass:[self.view class]])
{
NSLog(#"the subView is called");
}
else
{
NSLog(#"the subView is not called");
}
}
Printed Output is
the subView is called
CHOICE 3:Find Tap Detection using Delegate methods of Gesture
First You have to add the GestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if([touch.view isKindOfClass: [self.view class]] == YES)
{
return YES; // return YES (the default) to allow the gesture recognizer to examine the touch object
}
else {
return NO; //NO to prevent the gesture recognizer from seeing this touch object.
}
}
The gesture recognizer is only associated with one specific view, which means it will only recognize touches on the view it is added to. If you want to know which subview was touched, then you will need to do a couple of things:
Set userInteractionEnabled = false for each subview. This will make it so that every touch on a subview is passed up to the parent view, and the touch will be recognized by the gesture recognizer.
There isn't enough information on your view hierarchy or layout to know exactly how to proceed from here, but you can use one or some of these methods to determine which view was touched: UIView.hitTest(_:with:), UIView.point(inside:with:), CGRectContainsPoint() or UIGestureRecognizer.location(in:). For example, if the subviews do not overlap each other, you could use the following code snippet to test if the touch was in a particular view:
let location = tapGesture.locationInView(parentView)
if CGRectContainsPoint(subview1, location) {
// subview1 was touched
}

UIPanGestureRecognizer interception on SideMenu and UIPageViewController

I have leftMenu appears when swipe to right, and I want my tab changes when swipe to left whole page.
Their UIPanGestureRecognizer intercepts, I have tried disabling bouncing of UIScrollView of UIPageViewController, tried requireGestureRecognizerToFail:
But I could not make it both of them works. What I want is when it is swiped to to right side menu appears. When it is swiped to left if sidemenu is open it will close, if not, page will change.
My sample code is as follows;
for(UIScrollView *view in self.pageViewController.view.subviews)
{
if ([view isKindOfClass:[UIScrollView class]])
{
UIScrollView *scrollView = (UIScrollView *)view;
AppDelegate *appDel = [UIApplication sharedApplication].delegate;
UIPanGestureRecognizer* panGestureRecognizer = scrollView.panGestureRecognizer;
[panGestureRecognizer addTarget:self action:#selector(move:)];
[panGestureRecognizer requireGestureRecognizerToFail:appDel.slidingViewController.panGesture];
}
}
Thanks in advance
Something like the following should work (warning: untested!)
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panRecognized:)];
panRecognizer.delegate = self;
for (UIGestureRecognizer *recognizer in self.pageViewController.gestureRecognizers)
[recognizer requireGestureRecognizerToFail:panRecognizer];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)recogniser
{
if (self.leftMenuShowing)
return YES;
else
{
UIPanGestureRecognizer *panRecognizer = (id)recognizer;
CGPoint translation = [panRecognizer translationInView:self.view];
return (translation.x > 0);
}
}
- (void)panRecognized:(UIPanGestureRecognizer *panRecognizer)
{
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
{
// if pan is to left, move leftMenu offscreen
// if pan is to right, move leftMenu onscreen
}
else
{
// if leftMenu is mostly onscreen, animate completely onscreen
// if leftMenu is mostly offscreen, animate completely offscreen
}
}

Long press gesture on UIImageView

I have a UIImageView image circle. I want to long press the picture zoom in and move it to another place, and when released it declined and remained this place. But when I release the finger, the picture moves to the starting place. Why? Here's the code:
UILongPressGestureRecognizer *recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(move:)];
recognizer.minimumPressDuration = .5;
recognizer.delegate = self;
[circle addGestureRecognizer:recognizer];
- (void)move:(UILongPressGestureRecognizer *)sender
{
UIView *image_ = sender.view;
CGPoint point = [sender locationInView:image_.superview];
if (sender.state == UIGestureRecognizerStateBegan) {
[UIView animateWithDuration:0.3 animations:^{
image_.transform = CGAffineTransformMakeScale(1.2f, 1.2f);
} completion:nil];
}
if (sender.state == UIGestureRecognizerStateChanged) {
image_.center = point;
}
if (sender.state == UIGestureRecognizerStateEnded) {
image_.center = point;
[UIView animateWithDuration:0.3 animations:^{
image_.transform = CGAffineTransformMakeScale(1.f, 1.f);
} completion:nil];
}
}
The reason that it is moving back to the start is because you are using a UILongPressGestureRecognizer. This type of recogniser will not give you a reliable update on tracking the user's input location. Once the gesture ends it is probably giving you the point where the gesture was first initiated.
I would also set the image's transform back to the identity transform rather than scale it back to 1.0f in both dimensions.
The solution would be to use a UIPanGestureRecognizer and properly track the gesture's location in the view. This will work far better, and on top of that it is the right way to do it.
The best way for you is to add Image view to UIView.
So its UIView will be container UIView and add UILongPressGesture to its container view.
Then it will work. UIImageview doesn't reply to UILongPressGesture and its container view will reply about it.
You will get the result what you want.
Hope your success.

How hide cell wile reordering TableVirew

Aim is to hide cell which is reordering when reordering animation starts and show cell when reordering animation stops.
PS
When you moving cell, it's alpha is changed, i think there is a way to set alpha to 0, but how? It is necessary for me to hide else shadow of moving cell, because i try show diferent picture instead moving cell.
[yourtableviewcell setHidden:YES];
If you want to fade it away, animate the alpha to 0 first.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
yourtableviewcell.alpha = 0;
[UIView commitAnimations];
There answer to shadow question
I used next code in my CustomCell class and result is in the picture. Cell is hidden and it's good.
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
NSLog(#"Drugging"); // Dragging started
self.hidden=YES;
} else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
NSLog(#"Drugging End"); // Dragging ended
self.hidden=NO;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *view in self.subviews) {
if ([NSStringFromClass ([view class]) rangeOfString:#"ReorderControl"].location != NSNotFound) { // UITableViewCellReorderControl
if (view.gestureRecognizers.count == 0) {
UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gesture.cancelsTouchesInView = NO;
gesture.minimumPressDuration = 0.150;
[view addGestureRecognizer:gesture];
}
}
}
}

Drag and drop within UIScrollView

I am trying to design a view which contains a list of items to be ordered and placed into an array. The items are dynamically generated and thus cold be an amount that goes off the bottom of the screen.
For this reason my first view is a UIScrollview which takes the whole devices screen
Nested under this I have a label, explaining what the list is for and how to interact with it and then a UITableView with drag & drop with the delegate methods from http://b2cloud.com.au/how-to-guides/reordering-a-uitableviewcell-from-any-touch-point
The problem I am facing is that while the script works great when there are 1 or two rows, when the contentsize of the UIscrollview is larger than the screen it seems to take priority over the drag & drop leading to unpredictable behaviour.
Is there any way to make clicks on the table take priority to only edit the cells and allow the user to scroll by interacting elsewhere on the view?
Thanks
UPDATE
Based on the comment below I managed to get:
- (void)viewDidLoad
{
[super viewDidLoad];
//
//
//
UIPanGestureRecognizer *tapGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(wasPanned:)];
[self.view addGestureRecognizer:tapGesture];
}
-(void)wasPanned:(UIPanGestureRecognizer *)gesture
{
CGPoint point = [gesture locationInView:scrollView];
UIView *isTable = [scrollView hitTest:point withEvent:nil];
if (gesture.state == UIGestureRecognizerStateBegan)
{
if([[[isTable class] description] isEqualToString:#"UITableViewCellReorderControl"])
{
NSLog(#"Dragged from within table");
[scrollView setScrollEnabled: NO];
}
else
{
[scrollView setScrollEnabled:YES];
}
}
else{
[scrollView setScrollEnabled:YES];
}
}
Now, when the scrollview is not long enough to begin scrolling it NSLogs the message fine
However when the scrollview is longer it only recognises the gesture if the scrollview hasn't began scrolling first
Update
I now have the Console recognising touches in the table 100% of the time and disable scrolling. Disabling the scrolling however also stops the drag & drop functionality. Does anyone know why?
Extra code:
tapGesture.delegate = self;
#pragma mark UIGestureRecognizerDelegate
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
So my final (but not perfect) solution was to do this:
.h
<UIGestureRecognizerDelegate>
.m (viewDidLoad)
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(wasTapped:)];
[self.view addGestureRecognizer:tapGesture];
tapGesture.delegate = self;
.m
-(void)wasTapped:(UIPanGestureRecognizer *)gesture
{
CGPoint point = [gesture locationInView:scrollView];
UIView *isTable = [scrollView hitTest:point withEvent:nil];
if (gesture.state == UIGestureRecognizerStateBegan)
{
if([[[isTable class] description] isEqualToString:#"UITableViewCellReorderControl"])
{
NSLog(#"Dragged from within table");
[scrollView setScrollEnabled: NO];
}
else
{
[scrollView setScrollEnabled:YES];
}
}
else{
[scrollView setScrollEnabled:YES];
}
}
A tap gesture works better than a Pan gesture because a pan gesture seems to recognise press&hold, drag, stop, drag as two different gestures. The feature now works as it should but if you so much as move your finger before the animation for a cell moving has started it will scroll. You also have to wait for the view (can I call it overscroll?) animation to completely stop and the scrollbars disappear before it will grab cells.
I'd appreciate it if anyone can improve on it :)

Resources