How to dismiss programmatically the clipsToBounds property when a GestureRecognizer begin? - ios

I have a UIScrollView which is able to contains many view. To allow a good scrolling (without the content going outside the view while scrolling), on my Main.sotryboard , I've clicked on my UIScrollView and then in the attribute inspector I have allowed the Clip Subviews property:
My problem: all the views which are in my UIScrollViews are draggable (because they all have a UIPanGestureRecognizer.
So, when I try to drag them OUTSIDE my UIScrollView, they just disappear.
In fact they're just going behind every other view
To give you an exemple, I have others components which allow the drop of a view form the precedent UIScrollView. So when I begin the drag'n'drop from it, it disappear, and then reappear in the second component on which I have dropped the view.
What I have tried: I have a special UIPanGestureRecognizer for te drag'n'drop of a view coming from this UIScrollView. So, I actually have this (which, obviously, doesn't work, otherwise I would not be here):
//Here recognizer is the `UIPanGestureRecognizer`
//selectpostit is the name of the view I want to drag
if(recognizer.state == UIGestureRecognizerStateBegan){
selectpostit.clipsToBounds = NO;
}
Any Ideas on how I can improve that?
Thanks in advance.

You could try to reset scrollView.clipsToBounds to NO every time gesture starts, but that would lead to side effect when other content outside scroll view would become visible when dragging is in the progress.
I would recommend to take snapshot of the the draggable view when panning starts, place it on the scrollview's parent, and move it. Such approach should solve your problem.
Here is the code:
- (void)onPanGesture:(UIPanGestureRecognizer*)panRecognizer
{
if(panRecognizer.state == UIGestureRecognizerStateBegan)
{
//when gesture recognizer starts, making snapshot of the draggableView and hiding it
//will move shapshot that's placed on the parent of the scroll view
//that helps to prevent cutting by the scroll view bounds
self.draggableViewSnapshot = [self.draggableView snapshotViewAfterScreenUpdates: NO];
self.draggableView.hidden = YES;
[self.scrollView.superview addSubview: self.draggableViewSnapshot];
}
//your code that updates position of the draggable view
//updating snapshot center, by converting coordinates from draggable view
CGPoint snapshotCenter = [self.draggableView.superview convertPoint:self.draggableView.center toView: self.scrollView.superview];
self.draggableViewSnapshot.center = snapshotCenter;
if(panRecognizer.state == UIGestureRecognizerStateEnded ||
panRecognizer.state == UIGestureRecognizerStateCancelled ||
panRecognizer.state == UIGestureRecognizerStateFailed)
{
//when gesture is over, cleaning up the snapshot
//and showing draggable view back
[self.draggableViewSnapshot removeFromSuperview];
self.draggableViewSnapshot = nil;
self.draggableView.hidden = NO;
}
}

I recommend you look at this article by ray wenderlich Moving Table View Cells with a Long Press Gesture
It explains how to create snapshots

Related

Cannot pan gesture smaller UIView outside of its superview

I have a UIView that contains many UIView subviews. When the user tries to pan gesture one of the subviews, I want them to be able to drag it anywhere else on the screen.
However, I can only drag these smaller views within the bigger UIView they are contained in. When the user first tries to pan gesture the smaller view, is there any way to programmatically create a second UIView on top and then drag around the second UIView instead? The second UIView would be completely new from the smaller view that was first touched.
This is the handler function I have so far, just for reference. I don't know where I would programmatically create the second UIView, though. Any help would be greatly appreciated:
#objc func handlePanGesture(sender: UIPanGestureRecognizer) {
// get translation
var translation = sender.translation(in: view)
sender.setTranslation(CGPoint.zero, in: view)
// drag around current UIView
var newView = sender.view as! UIView
newView.center = CGPoint(x: newView.center.x+translation.x, y: newView.center.y+translation.y)
newView.isMultipleTouchEnabled = true
newView.isUserInteractionEnabled = true
if sender.state == UIGestureRecognizer.State.began {
// add something you want to happen when the Label Panning has started
}
if sender.state == UIGestureRecognizer.State.ended {
// add something you want to happen when the Label Panning has ended
}
if sender.state == UIGestureRecognizer.State.changed {
// add something you want to happen when the Label Panning has been change ( during the moving/panning )
} else {
// or something when it's not moving
}
}
Is there any way to be able to drag the smaller views anywhere while maintaining the same layout I have right now
Because of the way touch works, it is impossible by default for a view to be touched when it reaches the boundaries of its superview. But you can overcome that limitation by overriding hitTest in the superview.
A very common pattern that enables dragging of views outside of their superview, is to create a snapshot or clone of the original view as soon as the gesture begins, you can of course hide the original view so to the user it feels like the same UI element is being dragged. It's a pattern used by Apple in their drag and drop sample code here
https://developer.apple.com/documentation/uikit/drag_and_drop/adopting_drag_and_drop_in_a_custom_view
Have a look at line 36 in ViewController+Drag.swift for the relevant code.

UISwipeGestureRecognizer interferes with slider

I have a view in an iOS application (Obj-C) which has an image view in the centre, and immediately below that a slider.
The image view shows album artwork, and the slider can be used to adjust the now-playing track position.
There is also a pair of left and right Swipe Gesture Recognizers. These are used to skip to the next or previous tracks.
The problem is that the swipe gesture recognizers seem to over-ride the users moving the slider thumb.
In my gesture recognizer code I check that the point touched was inside the image view, but it still stops the slider from being moved. (The thumb moves, but jumps back to it's original position when you remove your finger).
This is the code I use to reject the gesture if it's not inside the image view.
- (IBAction)swipeLeftGestureAction:(UISwipeGestureRecognizer *)sender {
// Get the location of the gesture.
CGPoint tapPoint = [sender locationInView:_artworkImageView];
// Make sure tap was INSIDE the artwork image frame.
if( (tapPoint.x <0)
|| (tapPoint.y < 0 )
|| (tapPoint.x > _artworkImageView.frame.size.width)
|| (tapPoint.y>_artworkImageView.frame.size.height))
{
NSLog(#"Outside!");
return;
}
NSLog(#"Swipe LEFT");
[_mediaController skipNext];
}
So my question is, how do I limit the gesture to work ONLY when swiped across the image view?
Try to put the code that restricts the gesture's area in gestureRecognizer:shouldReceiveTouch: and return NO in case you don't want the gesture to receive this touch. It should prevent the gesture from over taking the slider interaction.
If you're only interested in swipes that are inside the image view, then you should add the swipe gesture recognizer to the image view instead of adding it to your entire view. Then you won't need any special logic.
There is a dedicated method for checking if a point is inside a view
BOOL isPointInsideView = [_artworkImageViewpointInside:tapPoint withEvent:nil];
But I think what is happening is that if you will look at the tapPoint is that it actually outside of your imageView
And if your slider is really close to the imageView so the slider captures the movement, what you should do is check on the slider if it is intended for the slider and propagate on to the imageView if needed
Or inherit the UISlider and reduce its response rect

Xcode iOS UIView with tap gesture allowing input on subviews

So I have a homepage view that people see when they first boot up the app, it has several different options. Yesterday I added a tutorial overlay, so the first time they open the app I have a view that is 0.5 alpha black and is fullscreen over the other view, this view has white text and arrows pointing to functions on the page. I attached a tap gesture recognizer to this view and its action is to call the next tutorial screen ( 3 in total). Afterwords they all are hidden and the app acts as normal. This all works, except even though my view is full screen, with User Interaction Enabled checked and a gesture tap recognizer, the gestures / touches on the sub view still work. So if I just tap some white space on the app the tutorial and tap gesture work as expected, cycle me through the pages etc and works. However if I accidentally press a sub view button or do a gesture from the subview it will do the action (take you to a new page or w/e)
So basically as far as I can tell my view despite being on top of the other and having a tap gesture recognizer attached to the view the sub views are still getting pressed, any ideas?
Check the tap event view before doing any operation..
- (IBAction)yourTapEvent:(UITapGestureRecognizer *)gestureRecognizer
{
UIView *piece = [gestureRecognizer view];
if(piece == (view on which you have added gesture))
{
//Do your necessary operation
}
}
try this it would help you to disable gesture on its subviews
- (void)tapAction:(UITapGestureRecognizer *) gestureRecognizer{
UIView *v = [gestureRecognizer view];// Guesture added view
NSArray *temp = [self.view subviews];
for (int i = 0; i<temp.count; i++) {
if([temp objectAtIndex:i]== v){
// Do your swipe tap action here
}
}
}

iOS: UIPanGesture and frame/bounds issue

I have created a relatively simple app which allows the user to drag an imageview from a 'tray' UIView at the bottom of the screen, and when the view is dropped, it adds itself to a subview of a 'target' UIView.
My problem is, when I drop the imageview into the target view, the frame is totally wrong, and the imageview jumps (seemingly randomly) to another position in the target view.
I've torn my hair out over this, and am having a massive brainfart. Is there a way to just get the coordinates of the dragged view in the superview, and then translate that into coordinates of the target view?
As you can see, i'm taking the icon out of the 'tray' view, and into the main view when the dragging begins. When it ends, I simply add the icon into the target view.
if (gesture.state == UIGestureRecognizerStateBegan){
if (icon.superview == viewTray) {
[self closeTray:nil];
[self.view addSubview:icon];
}
}
if (gesture.state == UIGestureRecognizerStateEnded) {
[viewTarget addSubview:icon];
}
An image of the basics of the xib:
use the gesture recognizer to figure out where the finger touches in the viewTarget's frame by:
CGPoint p = [gesture locationInView:viewTarget];
then set the icon's center to be that point, and you are done:
icon.center = p;
[icon removeFromSuperView];
[viewTarget addSubview:icon];
The problem is that the frame is in the bounds of its superView. So when you drop it into a new superView its not the same. You need to convert it.
This code will convert its from from its superView coordinate system to its targetView coordinate system. You should probably do this right after [viewTarget addSubview:icon];
[viewTarget convertRect:icon.frame fromView:icon.superView];

Drag a UIView from one UIScrollView to another

I have two UIScrollViews on my screen and I need to be able to drag a UIView from one scrollview to the other.
At the moment, I have a UILongGestureRecognizer on the UIView that I want to move, such that when the user starts dragging it, I make the view follow the touch:
- (void)dragChild:(UILongPressGestureRecognizer *)longPress {
[[longPress view] setCenter:[longPress locationInView:[[longPress view] superview]]];
}
But when I get the boundary of the starting UIScrollView, the view disappears because it's locked into that scrollview's bounds.
Is there a way to "pop" it out of the scrollview when I start dragging such that I can carry it over to the other scrollview?
Also, how do I test for the "drop" location? I want to know if it's been dropped over a certain other view.
Or am I going about this all the wrong way?
Thanks guys
If you will need to drag it from a scroll view to another do the following (pseudo code)
when you start dragging do the following
//scrollView1 is the scroll view that currently contains the view
//Your view is the view you want to move
[scrollView1.superView addSubView:yourView];
now when dropping the view, you will need to know if it is inside the other scrollview
//scrollView2 is the second scroll view that you want to move it too
//Your view is the view you want to move
CGPoint point = yourView.center;
CGRect rect = scrollView2.frame;
if(CGRectContainsPoint(rect, point))
{
//Yes Point is inside add it to the new scroll view
[scrollView2 addSubView:yourView];
}
else
{
//Point is outside, return it to the original view
[scrollView1 addSubView:yourView];
}
Here is an untested idea:
When the drag begins, move the dragging-view out of the scroll view (as a subview) and into the mutual superview of both scroll views.
When the drag ends, move the dragging-view out of the superview and into the new scroll view.
You'll probably have to be careful with coordinate systems, using things like [UIView convertPoint:toView:] to convert between the views' different perspectives when moving things around.

Resources