Implementing pinch effect on a image view in iphone - iphone

Thanks in advance.
I want to apply pinch, rotation and pan gesture effect on an image view.like this
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:#selector(scalePiece:)];
[imgView addGestureRecognizer:pinchGesture];
[pinchGesture release];
UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:#selector(rotatePiece:)];
[imgView addGestureRecognizer:rotationGesture];
[rotationGesture release];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(panPiece:)];
[imgView addGestureRecognizer:panGesture];
[panGesture release];
// Method implementation
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
UIView *piece = gestureRecognizer.view;
CGPoint locationInView = [gestureRecognizer locationInView:piece];
CGPoint locationInSuperview = [gestureRecognizer locationInView:piece.superview];
piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height);
piece.center = locationInSuperview;
}
}
- (void)rotatePiece:(UIRotationGestureRecognizer *)gestureRecognizer
{
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
[gestureRecognizer view].transform = CGAffineTransformRotate([[gestureRecognizer view] transform], [gestureRecognizer rotation]);
NSLog(#"Rotate : %f",[gestureRecognizer rotation]);
[gestureRecognizer setRotation:0];
}
}
- (void)panPiece:(UIPanGestureRecognizer *)gestureRecognizer
{
UIView *piece = [gestureRecognizer view];
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGPoint translation = [gestureRecognizer translationInView:[piece superview]];
[piece setCenter:CGPointMake([piece center].x + translation.x, [piece center].y + translation.y)];
[gestureRecognizer setTranslation:CGPointZero inView:[piece superview]];
}
}
- (void)scalePiece:(UIPinchGestureRecognizer *)gestureRecognizer
{
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
[gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer velocity], [gestureRecognizer velocity]);
[gestureRecognizer setScale:1];
}
}
In this rotation and swipe(pan) working fine but pinch is not good. I have seen one app i.e.,http://itunes.apple.com/us/app/image-mask-costume-hd/id443821357?mt=8 in this the touch events are very nice . I want to implement like this is it possible by using gesture or we need to follow touch events. Do any one have idea please help me to do it.

Try this code: http://dl.dropbox.com/u/9397784/Image%20multitouch.txt
Replace currentlyEditing with your view. Just copy the code into the implementation of the view or view controller you want to use it in. The code handles zoom, rotate and pan. To reset the view back to the default position, call yourView.transform = CGAffineTransformIdentity;.

The Apple Touches example suggests to use [gestureRecognizer scale] and not [gestureRecognizer velocity]:
- (IBAction)scalePiece:(UIPinchGestureRecognizer *)gestureRecognizer
{
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
[gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
[gestureRecognizer setScale:1];
}
}

Related

KVO mechanism throwing error?

I m implementing one photo edit app in which i have one camera captured image and one is output image which is i shown beside of captured image. Whenever i change my captured image scaling or rotation then i want notify this changes to outputImage. For this senario i written following code,
-(void)imageCapturedByCameraAPI:(UIImage *)theCapturedImage{
[[arOverlayVC view] removeFromSuperview];
capturedImageView.image = theCapturedImage;
[self.view addSubview:imageEditorView];
rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotatePiece:)];
[capturedImageView addGestureRecognizer:rotationGesture];
**//KVO added for rotation**
[capturedImageView addObserver:self forKeyPath:#"transform.rotate" options:NSKeyValueObservingOptionNew context:NULL];
pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(scalePiece:)];
[pinchGesture setDelegate:self];
[capturedImageView addGestureRecognizer:pinchGesture];
**//KVO added for scaling**
[capturedImageView addObserver:self forKeyPath:#"transform.scale" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)rotatePiece:(UIRotationGestureRecognizer *)gestureRecognizer {
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
[gestureRecognizer view].transform = CGAffineTransformRotate([[gestureRecognizer view] transform], [gestureRecognizer rotation]);
[self editedImage];
[gestureRecognizer setRotation:0];
}
}
- (void)scalePiece:(UIPinchGestureRecognizer *)gestureRecognizer {
if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
lastScale = [gestureRecognizer scale];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:#"transform.scale"] floatValue];
const CGFloat kMaxScale = 2.0;
const CGFloat kMinScale = 1.0;
CGFloat newScale = 1 - (lastScale - [gestureRecognizer scale]);
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
[gestureRecognizer view].transform = transform;
lastScale = [gestureRecognizer scale];
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// if the gesture recognizers are on different views, don't allow simultaneous recognition
if (gestureRecognizer.view != otherGestureRecognizer.view)
return NO;
// if either of the gesture recognizers is the long press, don't allow simultaneous recognition
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
return NO;
return YES;
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
UIView *piece = gestureRecognizer.view;
CGPoint locationInView = [gestureRecognizer locationInView:piece];
CGPoint locationInSuperview = [gestureRecognizer locationInView:piece.superview];
piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height);
piece.center = locationInSuperview;
}
}
Now my observation method is,
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if(object == capturedImageView && ([keyPath isEqualToString:#"transform.rotate"] || [keyPath isEqualToString:#"transform.scale"])) {
NSLog(#"KVO detected");
}
}
I found following error,
An instance 0x189f40 of class NSConcreteValue was deallocated while
key value observers were still registered with it. Observation info
was leaked, and may even become mistakenly attached to some other
object. Set a breakpoint on NSKVODeallocateBreak to stop here in the
debugger. Here's the current observation info:
( Context: 0x0, Property: 0x189ff0>
An instance 0x188cb0 of class NSConcreteValue was deallocated while
key value observers were still registered with it. Observation info
was leaked, and may even become mistakenly attached to some other
object. Set a breakpoint on NSKVODeallocateBreak to stop here in the
debugger. Here's the current observation info:
( Context: 0x0, Property: 0x188d80>
* Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Cannot update for
observer for the key path
"transform.scale" from , most likely because the
value for the key "transform" has changed without an appropriate KVO
notification being sent. Check the KVO-compliance of the UIImageView
class.'
as the error message said
An instance of class was deallocated while key value observers were still registered with it
you need to remove the observation before capturedImageView is deallocated using removeObserver:forKeyPath:context:
It is very important to make sure you have balance the count of register/deregister in order to not break KVO.
I you want to know more about KVO, this link is helpful: http://www.mikeash.com/pyblog/key-value-observing-done-right.html
and part2: http://www.mikeash.com/pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html
arc only handles memory for NSObject&Co, for everything else (e.g. KVO or network connections or file writes) you need to manually clean up
overwrite -(void)dealloc for that
- (void)dealloc {
[capturedImageView removeObserver:self forKeyPath:#"transform.scale"];
}

Rotation gesture for UIImageView

I have implemented a rotation gesture for UIImageView.
Here is the Code that I implemented:
- (IBAction)rotaehandler:(UIRotationGestureRecognizer *)sender
{
sender.view.transform = CGAffineTransformRotate(sender.view.transform, sender.rotation);
}
I connected UIRotationGestureRecognizer to UIImageView.
It works but, How can i rotate image form its center.
CGFloat initialRotation = atan2f(sender.view.transform.b-somefloat, sender.view.transform.a-somefloat);
CGFloat newRotation = initialRotation + sender.rotation;
sender.view.transform = CGAffineTransformMakeRotation(newRotation);
try above code
Try this :
- (IBAction)rotaehandler:(UIRotationGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan || [sender state] ==
UIGestureRecognizerStateChanged)
{
[sender view].transform = CGAffineTransformRotate([[sender view] transform],
[sender rotation]);
NSLog(#"Rotate : %f",[gestureRecognizer rotation]);
[sender setRotation:0];
}
}

How to limit pinch out to the default view size

I am working on the pdf based application where I am trying to implement UIPinchGestureRecognizer.I want to limit the pinch off functionality when user reaches to the default view size with 640,960.
In my current implementation user is able to pinch in/out infinite.
- (void)pinchZoom:(UIPinchGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
if (!zoomActive) {
zoomActive = YES;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panMove:)];
[panGesture setMaximumNumberOfTouches:2];
[panGesture setDelegate:self];
[self addGestureRecognizer:panGesture];
[panGesture release];
}
[gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
[delegate leavesView:self zoomingCurrentView:[gestureRecognizer scale]];
[gestureRecognizer setScale:1];
}
}
// This method will handle the PAN / MOVE gesture
- (void)panMove:(UIPanGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGPoint translation = [gestureRecognizer translationInView:[[gestureRecognizer view] superview]];
[[gestureRecognizer view] setCenter:CGPointMake([[gestureRecognizer view] center].x + translation.x, [[gestureRecognizer view] center].y + translation.y)];
[gestureRecognizer setTranslation:CGPointZero inView:[[gestureRecognizer view] superview]];
}
}
this is default view size/scale I am talking about:
and this is what I don't want or I want to limit in pinch out:
Any suggestion?
What about handling yourself the lower limit in your handler function? Something like this:
- (void)pinchZoom:(UIPinchGestureRecognizer *)gestureRecognizer {
....
if ( [gestureRecognizer scale] > MIN_SCALE )
[gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
...
Inside of your gestureRecognizer, you are going to have to test and understand the current scale / size of you PDF view, and NOT zoom smaller when you are taking up the full screen.

UIPanGestureRecognizer gets stuck before moving after image is idle for a bit

So I'm making a simple iPhone app that lets you move / scale images that have been imported in a view. I'm using UIGestureRecognizers with #paulsolt 's code below to accomplish this. It works great. The only problem I'm having is when I go to move an object after not having touched the screen or performed any other actions for a while, there is a slight hiccup before it starts moving smoothly.
Does anyone know how to fix this problem?
- (void)addGestureRecognizersToView:(UIView *)theView {
theView.userInteractionEnabled = YES; // Enable user interaction
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[panGesture setMaximumNumberOfTouches:2];
[panGesture setDelegate:self];
[theView addGestureRecognizer:panGesture];
[panGesture release];
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinchGesture:)];
[theView addGestureRecognizer:pinchGesture];
[pinchGesture release];
}
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer {
if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = [gestureRecognizer scale];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:#"transform.scale"] floatValue];
// Constants to adjust the max/min values of zoom
const CGFloat kMaxScale = 1.5;
const CGFloat kMinScale = 1.0;
const CGFloat kSpeed = 0.75;
CGFloat newScale = 1 - (lastScale - [gestureRecognizer scale]) * (kSpeed);
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
[gestureRecognizer view].transform = transform;
lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
}
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer {
UIView *myView = [gestureRecognizer view];
CGPoint translate = [gestureRecognizer translationInView:[myView superview]];
if ([gestureRecognizer state] == UIGestureRecognizerStateChanged || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
[myView setCenter:CGPointMake(myView.center.x + translate.x, myView.center.y + translate.y)];
[gestureRecognizer setTranslation:CGPointZero inView:[myView superview]];
}
}
Maybe because you do this [panGesture setMaximumNumberOfTouches:2];, it has to ensure that the current action is not pinching before moving your view?
This is happening when the imagepicker selects a photo taken by the iPhone camera. For other images it seems to work fine. Not sure how to fix this.

UIImageView Gestures (Zoom, Rotate) Question

I would like to make 2 operations to an UIImageView zoom, rotate, I have 2 problems:
A. I make an operation for zoom for ex. and when I try to make rotation the UIImageView is set to initial size, I would like to know how to keep the zoomed UIImageView and make the rotation from the zoomed image.
B. I would like to combine the zoom operation with rotation and I don't know ho to implement this:
- (void)viewDidLoad
{
foo = [[UIImageView alloc]initWithFrame:CGRectMake(100.0, 100.0, 600, 800.0)];
foo.userInteractionEnabled = YES;
foo.multipleTouchEnabled = YES;
foo.image = [UIImage imageNamed:#"earth.jpg"];
foo.contentMode = UIViewContentModeScaleAspectFit;
foo.clipsToBounds = YES;
[self.view addSubview:foo];
}
//---pinch gesture---
UIPinchGestureRecognizer *pinchGesture =
[[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinchGesture:)];
[foo addGestureRecognizer:pinchGesture];
[pinchGesture release];
//---rotate gesture---
UIRotationGestureRecognizer *rotateGesture =
[[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(handleRotateGesture:)];
[foo addGestureRecognizer:rotateGesture];
[rotateGesture release];
//---handle pinch gesture---
-(IBAction) handlePinchGesture:(UIGestureRecognizer *) sender {
NSLog(#"Pinch");
CGFloat factor = [(UIPinchGestureRecognizer *) sender scale];
if (factor > 1) {
//---zooming in---
sender.view.transform = CGAffineTransformMakeScale(
lastScaleFactor + (factor-1),
lastScaleFactor + (factor-1));
}
else {
//---zooming out---
sender.view.transform = CGAffineTransformMakeScale(lastScaleFactor * factor, lastScaleFactor * factor);
}
if (sender.state == UIGestureRecognizerStateEnded) {
if (factor > 1) {
lastScaleFactor += (factor-1);
} else {
lastScaleFactor *= factor;
}
}
}
//---handle rotate gesture---
-(IBAction) handleRotateGesture:(UIGestureRecognizer *) sender {
CGFloat rotation = [(UIRotationGestureRecognizer *) sender rotation];
CGAffineTransform transform = CGAffineTransformMakeRotation(rotation + netRotation);
sender.view.transform = transform;
if (sender.state == UIGestureRecognizerStateEnded) {
netRotation += rotation;
}
}
Thanks
Hope this can be helpful to you, that's how I usually implement gesture recognizers:
UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotatePiece:)];
[piece addGestureRecognizer:rotationGesture];
[rotationGesture release];
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(scalePiece:)];
[pinchGesture setDelegate:self];
[piece addGestureRecognizer:pinchGesture];
[pinchGesture release];
Rotate method: Reset the gesture recognizer's rotation to 0 after applying so the next callback is a delta from the current rotation
- (void)rotatePiece:(UIRotationGestureRecognizer *)gestureRecognizer {
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
[gestureRecognizer view].transform = CGAffineTransformRotate([[gestureRecognizer view] transform], [gestureRecognizer rotation]);
[gestureRecognizer setRotation:0];
}
}
Scale Method, at the end reset the gesture recognizer's scale to 1 after applying so the next callback is a delta from the current scale
- (void)scalePiece:(UIPinchGestureRecognizer *)gestureRecognizer {
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
[gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
[gestureRecognizer setScale:1];
}
}
Than ensure that the pinch, pan and rotate gesture recognizers on a particular view can all recognize simultaneously prevent other gesture recognizers from recognizing simultaneously
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// if the gesture recognizers are on different views, don't allow simultaneous recognition
if (gestureRecognizer.view != otherGestureRecognizer.view)
return NO;
// if either of the gesture recognizers is the long press, don't allow simultaneous recognition
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
return NO;
return YES;
}
Scale and rotation transforms are applied relative to the layer's anchor point this method moves a gesture recognizer's view's anchor point between the user's fingers
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
UIView *piece = gestureRecognizer.view;
CGPoint locationInView = [gestureRecognizer locationInView:piece];
CGPoint locationInSuperview = [gestureRecognizer locationInView:piece.superview];
piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height);
piece.center = locationInSuperview;
}
}
Just implement gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: in your delegate.
I have a UIPinchGestureRecognizer, a UIPanGestureRecognizer and a UIRotationGestureRecognizer set up and I want them all to work at the same time. I also have a UITapGestureRecognizer which I do not want to be recognized simultaneously. All I did was this:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (![gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && ![otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return YES;
}
return NO;
}
I found something that may interest you on the stanford university website:
http://www.stanford.edu/class/cs193p/cgi-bin/drupal/downloads-2010-winter
on this site you will need to scroll down until you see the number 14: "Title: Lecture #14 - MultiTouch"
Download the: "14_MultiTouchDemo.zip"
In this example you can scale and rotate every image at the same time.
hope i helped :)
When you use CGAffineTransformMakeScale, you are resetting the transformation of Identity every time you use it and you lose the previous transformation information.
Try using CGAffineTransformScale(view.transform,scale, scale) for the pinch zooming. You will need to retain the original frame size to keep the zooming under control though.
see: How can I use pinch zoom(UIPinchGestureRecognizer) to change width of a UITextView?
For rotation similarly:
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
view.transform = CGAffineTransformRotate([view transform], [gestureRecognizer rotation]);
[gestureRecognizer setRotation:0];
}
I know this is a pretty old thread, I came across this imageview subclass, which works nice for zoom, rotate and pan. It uses gesture recognizer on an imageview. I am using this for one of my app.
ZoomRotatePanImageView