Panning a UIImage with a Gesture Recognizer on a placeholder UIView - ios

I need to crop an image to match a specific dimension. I have three layers in my view starting from the bottom:
I have the raw image in a UIImage. This image comes from the camera. (called cameraImage)
I have a UIView holding this image. When user clicks "crop", the UIView's bounds are used to crop the raw image inside it.
Above all of this I have a guide image which shows the user the dimensions they need to pan, rotate, and pinch their image to fit into.
I want to add the pan gesture to the top guide image and have it control the raw image at the bottom. So the guide image never moves but it is listening for the pan gesture.
I can't figure out how to reset the recognizer without it making my raw image jump back to zero. Maybe someone could help?
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:recognizer.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x+translation.x, recognizer.view.center.y+ translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view];
}
The above code works great in my gesture is attached to the bottom image. The problem is when the user goes outside the view's bounds, the image stops panning and is basically stuck. You can't touch it anymore so it sits there. So I thought if i attached the gesture to the top it would solve this problem.
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:recognizer.view];
cameraImage.center = CGPointMake(recognizer.view.center.x+translation.x, recognizer.view.center.y+ translation.y);
}
This almost works. I set the cameraImage's center and removed the third line which resets the recognizer. If I don't remove it, the cameraImage jumps back into the same position every time I try and pan. It almost works because when you click the image again it doesn't start from the pixel you touched. It moves the image back to the original position and then lets you pan.

First option:
when the recognizer enter the UIGestureRecognizerStateEndedstate
if(recofnizer.state == UIGestureRecognizerStateEnded )
{
...
}
You store the translation at that point in time in an instance varibale (#property) of your class.
And then you always add the saved translation to the new translation.
In code this would look like this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:recognizer.view];
CGPoint updatedTranslation = CGPointMake(translation.x+self.savedTranslation.x,translation.y+self.savedTranslation.y);
cameraImage.center = CGPointMake(recognizer.view.center.x+updatedTranslation.x, recognizer.view.center.y+ updatedTranslation.y);
if(recofnizer.state == UIGestureRecognizerStateEnded )
{
self.savedTranslation = updatedTranslation;
}
}
Dont forget to add #property (nonatomic, assign) CGPoint savedTranslation; to your interface.
Also make sure the savedTranslation variable is initialized in the init method of your class to self.savedTranslation = CGPointMake(0,0);
Second option:
You should think about doing all that what you want in an scrollview with the imageview as the viewForZooming of the scrollview. This allows very smooth interaction, like users are used to!
Above this scrollview you can then put your mask/guide, but make sure to disable the userInteraction of the mask/guide view to make your user touch the scrollview below!

Related

Adding gesture recognizer to UIImage, not UIImageView

I have a UIImageView whose 'View Mode' is set to 'Aspect Fit' in Interface Builder.I want to add gesture recognizer to only my image inside of the UIImageView.To clarify what i say:
I have a UIImageView whose bounds.size.width = 100; and bounds.size.height = 100; and I have a UIImage whose size is 100x50. So, when I add my UIImage to my UIImageView there exists some spaces of 25 from top and 25 from bottom. But I don't want to recognize user's tap when he/she taps on these spaces. I just want to know when user tapped on UIImage.
How can I do that?
As UIImage extends from NSObject you can not add UITapGestureRecognizer to it, you have to add gesture to the UIImageView itself. Then in any of the UIGestureRecognizer delegate methods:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
or
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
get touch's location in UIImageView. Then by using CGPoint location = [_gesture locationInView:yourImageView] get the touched point, now get the image's frame using this link. Now check if the point is in Image's frame or not by using:
bool CGRectContainsPoint ( CGRect rect, CGPoint point );
This might help you.
You have to add the gesture recognizer to the imageView. What you can do it use the gesture recognize delegate methods:
- gestureRecognizerShouldBegin:
//OR
- gestureRecognizer:shouldReceiveTouch:
You can then figure out where the user tapped in the imageView and compute if the touch landed inside the image in the imageView or outside the image.
You can do this using using the following:
CGPoint tapPoint = [gestureRecognizer locationInView:imageView];
// use tap point to compute if it lands inside the image.

UIPanGestureRecognizer not working as expected with multiple pans

Essentially, what I want to do is to move a view around to follow the user's pan. This works fine as long as the same pan object is being used. The problem comes when the user releases and than starts another pan.
According to the documentation, the value in translationInView is relative to the position at the start of the pan.
So my strategy for handling this was to add two properties to my view so I can tell whether the same pan object is being used and what the reference location is. The self object is the object being moved. It is a UIView subclass.
CGPoint originalPoint;
if (pan == self.panObject) {
//If the pan object is the same as the one in the property, use the saved value as the reference point.
originalPoint = CGPointMake(self.panStartLocation.x, self.panStartLocation.y);
} else {
//If the pan object is DIFFERENT, set the originalPoint from the existing center.
//self.center is in self.superview's coordinate system.
originalPoint = CGPointMake(self.center.x, self.center.y);
self.panStartLocation = CGPointMake(originalPoint.x, originalPoint.y);
self.panObject = pan;
}
CGPoint translation = [pan translationInView:self.superview];
self.center = CGPointMake(originalPoint.x+translation.x, originalPoint.y+translation.y);
This scheme doesn't work because each pan object apparently is the same object. I've spent a bit of time in the debugger verifying this, and that seems to be true. I thought the pan object would be different for each touch. So since this doesn't work, what is the alternative?
I solved it. Here is the corrected code:
CGPoint originalPoint;
if (pan.state == UIGestureRecognizerStateBegan) {
originalPoint = CGPointMake(self.center.x, self.center.y);
self.panStartLocation = CGPointMake(originalPoint.x, originalPoint.y);
} else {
originalPoint = CGPointMake(self.panStartLocation.x, self.panStartLocation.y);
}
CGPoint translation = [pan translationInView:self.superview];
self.center = CGPointMake(originalPoint.x+translation.x, originalPoint.y+translation.y);
EDIT: A better approach is to take advantage of the fact that the gesture recognizer allows you to set the translation:
[sender setTranslation:CGPointMake(0.0, 0.0) inView:self.pieceBeingMoved];
Do this when you move your item, and then the new translation next time will be relative to the position you just moved to.

reset an iOS gesture by button click

I have an application that allows a user to pan and zoom in on an image. I think that, without too much trouble, the user can get themselves into a state where they are zoomed in to one portion of the image, and would want to reset everything back to the 'ground state' (ie, bring all the translation and rescaling back to 0 and 1, respectively).
I'm doing translation by:
- (void)panGestureRecognized:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
And this works fine, I can translate the image.
If I press the button, I want to be able to change the translation back to 0,0. One way to do this would seem to be to store the gesturerecognizer and set that back to zero, as in:
mPanRecognizer.view.center = CGPointMake(mPanRecognizer.view.center.x,
mPanRecognizer.view.center.y);
[mPanRecognizer setTranslation:CGPointMake(0,0) inView:self.view];
Where mPanRecognizer is a member variable storing the recognizer. However, doing this produces the following log information, with no actual behavioral change:
Ignoring call to [UIPanGestureRecognizer setTranslation:inView:] since gesture recognizer is not active.
So how can I reset the gesture to be translating to 0,0 by pressing a button?
This can be done without using gesture recognizers. To set the scaling, use
[self.view setBound:CGRectMake(0,0,width,height)];
where width and height are the dimensions of your device.
To set the translation, use
[self.view setCenter:CGPointMake(width/2.0,height/2.0)];
To set your width and height easily, contain your view in a separate UIView. Have that UIView anchored properly and in the proper z-order, then you can modify the above lines to be
[imageView setBounds:CGRectMake(0,0,imageView.superview.bounds.size.width,imageView.superview.bounds.size.height)];
[imageView setCenter:CGPointMake(imageView.superview.center.x, imageView.superview.center.y)];
thereby avoiding the need for storying of any points or magic numbers specific to a particular device.

iOS Animate sliding an imageview from off-screen to on-screen with gesture

I'm looking to animate bubbles with text on them to slide on and off the screen. The ideal implementation for this animation is iOS's horizonatal scroll with paging enabled. I definitely want the "bounce" when I reach the end of the speech bubbles and I definetely want the bubbles to track the finger until a certain point before they will slide off the screen. I believe this is not the same as a swipe (which is just a flick in one direction).
However, the problem with the horizontal scroll is that it is optimized for a static number of images. I will be having a dynamic number of images and as far as I can tell, you cannot dynamically append images to horizontal scroller. The idea is the app dynamically adds content to the scroller as you continue to progress through it.
The scroller was easy enough to get going but I'm going to have to tear it down now. How can I get started with the gesture (I'm not sure if the standard gesture recognizers will work for me at this point) as well as the animation? I've never worked with that portion of iOS code before.
I'm not sure if I follow your question entirely, but if you want to animate the movement of something based upon a gesture, you can use a UIPanGestureRecognizer and change the center of whatever subview you want. For example, in viewDidLoad you would:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(movePiece:)];
[whateverViewYouWantToAnimate addGestureRecognizer:panGesture];
You can then have your gesture recognizer move it where ever you want:
- (void)movePiece:(UIPanGestureRecognizer *)gestureRecognizer
{
static CGPoint originalCenter;
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
originalCenter = [gestureRecognizer view].center;
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [gestureRecognizer translationInView:self.view];
gestureRecognizer.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y);
// if you wanted to animate both left/right and up/down, it would be:
// gestureRecognizer.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y);
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
// replace this offscreen CGPoint with something that makes sense for your app
CGPoint offscreen = CGPointMake(480, gestureRecognizer.view.center.y);
[UIView animateWithDuration:0.5
animations:^{
gestureRecognizer.view.center = offscreen;
}
completion:^(BOOL finished){
// when you're done, you might want to do whatever cleanup
// is appropriate for your app (e.g. do you want to remove it?)
[gestureRecognizer.view removeFromSuperview];
}];
}
}

Panning UIView after RotationGesture Causes view to collapse

I'm going to try to describe with words something that might only be describable with video.
I have created a simple iOS app with a storyboard containing a single image view. I have added two gesture recognizers: a UIPanGestureRecognizer and a UIRotationGestureRecognizer along with their corresponding IBActions.
When I first start the application in the simulator, the image view pans correctly. The image view also rotates correctly. After a rotation, however, any subsequent pan fails. When I try to pan after a rotation, regardless of the direction of the pan, the image rapidly scales to zero and disappears, i.e., it collapses or implodes to a point that disappears.
The gesture recognizers are created using the following code. myImageView is set up as an IBOutlet UIImageView.
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(processPan:)];
[myImageView addGestureRecognizer:panRec];
UIRotationGestureRecognizer *rotRec = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(processRotation:)];
[myImageView addGestureRecognizer:rotRec];
I've written the associated actions as best I know how. They are basically slight modifications of the methods I found in the iOS documentation. These are shown below.
-(IBAction)processPan:(UIPanGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [sender translationInView:self.view];
CGRect newFrame = myImageView.frame;
newFrame.origin.x += translation.x; 
newFrame.origin.y += translation.y;
myImageView.frame = newFrame;
[sender setTranslation:CGPointMake(0, 0) inView:self.view];
}
}
-(IBAction)processRotation:(UIRotationGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateChanged)
{
myImageView.transform = CGAffineTransformRotate(myImageView.transform, sender.rotation);
[sender setRotation:0];
}
}
So what am I missing? I am new at this, so hopefully my ignorance will be tolerated.
I am running Xcode version 4.2.1 on OS X version 10.7.3 on a MacBook if that helps. Thank you so much for taking the time to read my question. Stack Overflow is an unbelievable resource!
-Dave
Well, I don't know if I've come up with a solution or if I've come up with a kludge. Basically, the pan code wasn't working for me. Any time the view was rotated or scaled, the panning code would seriously distort or collapse the view being translated. I stared at transform matrices and frame coordinate systems until I just about went blind.
The translation code I listed in my first post was basically copied from Listing 3-2, "Handling pinch, pan, and double-tap gestures" from the Gesture Recognizers section out of Apple's Event Handling Guide for iOS, so I figured it would do the trick for me. Well, I ended up writing my own code for it using the UIImageView center and not messing with the frame at all. Here is what worked for me.
CGPoint translation = [sender translationInView:self.superview];
CGPoint newCenter = CGPointMake(self.myImageView.center.x + translation.x, self.myImageView.center.y + translation.y);
[self.myImageView setCenter:newCenter];
[sender setTranslation:CGPointMake(0, 0) inView:self.superview];
I used the superview as a reference for the translation in case it was rotated. It seems to work now.
This effort probably reveals something about how my understanding of frames isn't correct. If someone can tell me how to correct my understanding, I'd appreciate it.
-Dave

Resources