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.
Related
I tested it. When I moved the View, the tap event on the view doesn't respond, and it only responded when it stopped. I don't know why this happens. Below is my code
#interface ViewController ()
#property(nonatomic,strong)UIView *testView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.testView = [[UIView alloc]init];
self.testView.backgroundColor = [UIColor redColor];
self.testView.frame = CGRectMake(0, 100, 100, 100);
[self.view addSubview:self.testView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapTestView:)];
[self.testView addGestureRecognizer:tap];
}
- (void)tapTestView:(UITapGestureRecognizer *)tap {
NSLog(#"Event triggered");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[UIView animateWithDuration:6 animations:^{
CGRect frame = self.testView.frame;
frame.origin.x = 400;
self.testView.frame = frame;
}];
}
First, you want to make sure to use animatewithduration:delay:options:animations:completion: with the UIViewAnimationOptionAllowUserInteraction option. If you do not use this option, animated views will not receive touches.
[UIView animateWithDuration:26 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
CGRect frame = self.testView.frame;
frame.origin.x = ...;
self.testView.frame = frame;
} completion:nil];
Second, when views are animated, if you examine the frame of that view as it is moving, you will see that the frame property won’t reflect where the view really is at that moment of time, but, rather, where it will eventually be. Specifically, the frame property will be that value you set in that animations block, even if it hasn’t finished actually moving there, yet.
The implication of this behavior is that, even if you enable user interaction during animation, as shown above, gestures will be recognized based upon the view’s final destination rather than where it is now. E.g., if you start animation of frame from rect A to rect B, the gesture recognizer will recognize taps within rect B even if the view is not there yet! And conversely, it won’t recognize taps where the animated view happens to be, because the frame effectively thinks the view is already at the destination.
To get around that issue, one must refer to the view’s layer’s presentationLayer to get the location of the view mid-animation. One approach is to add a gesture recognizer to the main view and then, in there, see if the touch was inside the animated view’s presentation layer:
- (void)tapMainView:(UITapGestureRecognizer *)tap {
CGPoint point = [tap locationInView:self.testView.superview];
CGRect frame = self.testView.layer.presentationLayer.frame;
BOOL isInTestView = CGRectContainsPoint(frame, point);
if (isInTestView) {
[self testViewAction];
return;
}
// otherwise, we're in the super view
[self mainViewAction];
}
Alternatively, you can (a) define a UIView subclass for the child view and (b) override its hitTest implementation to factor in the presentation layer:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self.layer.presentationLayer hitTest:[self convertPoint:point toView:self.superview]]) {
return self;
} else {
return nil;
}
}
I have a UIView that flips over when long pressed. Works beautifully in the simulator, but in the real world the human finger has tiny movements while pressing. These tiny movements reset the gesture and instantly trigger the gesture ended state.
- (void)viewDidLoad {
...
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(didLongPress:)];
longPress.minimumPressDuration = 0.7;
[self.view addGestureRecognizer:longPress];
}
- (void)didLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
if ( gestureRecognizer.state == UIGestureRecognizerStateBegan )
{
[UIView transitionFromView:self.questionCardView toView:self.answerCardView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:nil];
}
else
{
[UIView transitionFromView:self.answerCardView toView:self.questionCardView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromRight
completion:^(BOOL finished){
[self.view addSubview:self.questionCardView];
[self.view sendSubviewToBack:self.questionCardView];
}];
}
}
You need to properly check the gesture's state in your gesture recognizer's handler.
Try:
- (void)didLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
if ( gestureRecognizer.state == UIGestureRecognizerStateBegan )
{
[UIView transitionFromView:self.questionCardView toView:self.answerCardView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:nil];
}
else if ( gestureRecognizer.state == UIGestureRecognizerStateEnded )
{
[UIView transitionFromView:self.answerCardView toView:self.questionCardView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromRight
completion:^(BOOL finished){
[self.view addSubview:self.questionCardView];
[self.view sendSubviewToBack:self.questionCardView];
}];
}
}
As you had it, the else block was being called on every little movement in addition to the gesture ending.
UILongPressGestureRecognizer has an allowableMovement property. This is what you are looking for. It lets the user move their finger by the pixel distance determined by the property without causing the gesture to end. The default value is 10 points. Set it to something larger than 10 in your initialisation.
I want to start the UIPanGestureRecognizer right after adding it to a screenshot. Because the screenshot is created through code, when an item has been highlighted, the user won't press on the screen again. So... How do I start the recognizer programmatically?
UIView *snapshot = [cell snapshotViewAfterScreenUpdates:NO];
//use the cell to map the snapshot frame to the window because this does a perfect job of accounting for table offset, etc. Other methods put the view a little to the side or way off
CGRect newFrame = snapshot.frame;
newFrame.origin = [cell convertPoint:newFrame.origin toView:self.view.window];
[snapshot setFrame:newFrame];
[HelperMethods shadowForView:cell color:[UIColor blackColor] offset:CGSizeMake(1, 1) opacity:.7 radius:snapshot.frame.size.width/4];
//[self.view addSubview:snapshot];
newFrame.origin.y -=10;
//move the frame a little to let user know it can be moved
[UIView animateWithDuration:.2 animations:^{
[snapshot setFrame:newFrame];
}];
//add a long press that kills the tap if recognized
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(userCellDragged:)];
[pan setMinimumNumberOfTouches:1];
[pan setMaximumNumberOfTouches:1];
[cell addGestureRecognizer:pan];
You can always call the method on its own.
For example, you've added the selector
- (void) userCellDragged:(UIPanGestureRecognizer)sender;
For your pan gesture recognizer.
You could call this from anywhere in the view by simply adding
[self userCellDragged:nil];
Remember to add a parameter to this method something like:
if (sender == nil) {
// Triggered programmatically
}
else {
// proceed as normal
}
I have a UIImageView that I am trying to move using a UIPanGestureRecognizer object. The UIImageView is positioned over top of a UITableView, and serves as a scrollbar. I want this UIImageView to be moved by the user up or down the UITableView, not sideways. To accomplish this, I have implemented UIGestureRecognizerDelegate, and I have the following methods:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (void)panGestureDetected:(UIPanGestureRecognizer *)recognizer {
NSLog(#"Do I get called?");
//_startLocation is a property of type CGPoint that I declare in the .h file
_startLocation = [recognizer locationInView:_imageView];
NSLog(#"The point is: %d", _startLocation);
CGRect frame = [_imageView frame];
frame.origin.y += frame.origin.y - _startLocation.y;
[_imageView setFrame: frame];
_imageView.center = _startLocation;
}
The method, panGestureDetected is called from viewDidLoad as follows:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureDetected:)];
panGesture.maximumNumberOfTouches = 1;
panGesture.minimumNumberOfTouches = 1;
panGesture.delegate = self;
[_table addGestureRecognizer:panGesture];
Unfortunately, my UIImageView moves all over the place frantically on the screen when I try to move it. I want to see a smooth scrolling UIImageView go up/down while the user drags it. Can anyone see what I am doing wrong?
In your handler method just keep this line and remove all other.
_imageView.center.y = [recognizer locationInView:[_imageView superView]].y;
You need to get location in superView, not imageView itself. And just change the y value of center.
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