UIView/Drawer using Pan/Swipe Gesture - ios

I am trying to make a UIView open & close on Swipe/Pan Gesture & i found some help from following link
Link , it's close to what i m trying to make.
I want UIView to be open by 100 pixels default & User can swipe/pan the UIView using gesture till 75% of the parent UIViewController & back to 100 pixels but it's flicking in this below code. I want UIView's X position to be 0 so it can be like a drawer opening from top.
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(move:)];
drawerView = [self.storyboard instantiateViewControllerWithIdentifier:#"drawerVC"];
[drawerView.view setFrame:CGRectMake(0, -self.view.frame.size.height + 100, self.view.frame.size.width, self.view.frame.size.height * 0.75)];
[drawerView.view setBackgroundColor:[UIColor redColor]];
[drawerView.view addGestureRecognizer:panGesture];
}
-(void)move:(UIPanGestureRecognizer*)recognizer {
recognizer.view.center = CGPointMake(self.view.frame.size.width/2,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200;
NSLog(#"magnitude: %f, slideMult: %f", magnitude, slideMult);
float slideFactor = 0.1 * slideMult; // Increase for more of a slide
CGPoint finalPoint = CGPointMake(0,
recognizer.view.center.y + (velocity.y * slideFactor));
finalPoint.x = 0;
finalPoint.y = MIN(MAX(finalPoint.y, 0), drawerView.view.frame.size.height*.75);
if (fabs(recognizer.view.frame.origin.y) >= fabs(yOffset))
{
return;
}
NSLog(#"ended %f",finalPoint.y);
if (finalPoint.y < recognizer.view.frame.size.height/2) {
// [self movePanelToOriginalPosition];
}
else{
[self movePanelToCenterPosition];
}
}
}
-(void)movePanelToCenterPosition {
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
drawerView.view.frame = CGRectMake(0, 0, drawerView.view.frame.size.width, drawerView.view.frame.size.height);
}
completion:^(BOOL finished) {
// Stuff to do
}];
}
Is there anything that can prevent user to pan UIView in Top(Up) direction if UIView is at default(100 pixels) & can only swipe down to desired CGPoint.

In your move: method you check if the move start or is in progress
else if (recognizer.state == UIGestureRecognizerStateBegin || recognizer.state == UIGestureRecognizerStateChanged)" and in the if view is at its limit. if so you can disabled/reenabled the gesture recognizer. This will cancel the pan...
- (void) move:(UIGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateChanged)
{
BOOL shouldEnablePan = NO; // TODO: do some logic here to figure out if you want to allow pan
if(!shouldEnablePan && [sender isKindOfClass:[UIPanGestureRecognizer class]])
{
sender.enabled = NO;
sender.enabled = YES;
}
} else ...
}

Related

iOS, can UIPanGesture be used to do UIImageView corner drag for resizing?

I've searched for some time (days) for a solution, but none really do what I need. (iOS, Objective C, BTW).
I have a UIImageView that I resize with a UIPanGestureRecognizer. The typical pan works fine. It seems like I am so close.
But I want to resize the ImageView by dragging a corner of the image and only resizing dimensions relevant to the selected corner. It works great if I only do my "handleResize" UIPanGesture method. But if I pinch or rotate the image, the bounds or frame get messed up. I think I need some sort of CGAffineTransform but I have not been able to get it to work.
I need help to point me in the right direction. I've been working with CGAffineTransforms but I may be on the wrong track.
In my ViewController.h I have a float, touchRadius, set to 25:
float touchRadius = 25;
I have a UIPanGestureRecognizer in my ViewController.m:
- (IBAction)handleResize:(UIPanGestureRecognizer *)recognizer {
// where the user has touched down
CGPoint touch = [recognizer locationInView: self.view];
//get the translation amount in x,y
CGPoint translation = [recognizer translationInView:self.view];
if (recognizer.state == UIGestureRecognizerStateBegan ||
recognizer.state == UIGestureRecognizerStateChanged) {
CGRect frame = recognizer.view.frame;
CGRect topLeft = CGRectMake(frame.origin.x,
frame.origin.y,
touchRadius, touchRadius);
CGRect bottomLeft = CGRectMake(frame.origin.x,
frame.origin.y + frame.size.height - touchRadius,
touchRadius, touchRadius);
CGRect topRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
frame.origin.y,
touchRadius, touchRadius);
CGRect bottomRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
frame.origin.y + frame.size.height - touchRadius,
touchRadius, touchRadius);
Boolean useNewFrame = YES;
CGRect newFrame = frame;
if (CGRectContainsPoint(topLeft, touch)) {
newFrame.origin.x += translation.x;
newFrame.origin.y += translation.y;
newFrame.size.width -= translation.x;
newFrame.size.height -= translation.y;
recognizer.view.frame = newFrame;
} else if (CGRectContainsPoint(topRight, touch)) {
newFrame.origin.y += translation.y;
newFrame.size.width += translation.x;
newFrame.size.height -= translation.y;
recognizer.view.frame = newFrame;
} else if (CGRectContainsPoint(bottomLeft, touch)) {
newFrame.origin.x += translation.x;
newFrame.size.width -= translation.x;
newFrame.size.height += translation.y;
recognizer.view.frame = newFrame;
} else if (CGRectContainsPoint(bottomRight, touch)) {
newFrame.size.width += translation.x;
newFrame.size.height += translation.y;
recognizer.view.frame = newFrame;
} else {
useNewFrame = NO;
}
if (useNewFrame) {
// make sure it doesn't go too small to touch
if (newFrame.size.width < touchRadius)
newFrame.size.width = touchRadius;
if (newFrame.size.height < touchRadius)
newFrame.size.height = touchRadius;
recognizer.view.frame = newFrame;
// I THINK I NEED A TRANSFORM HERE
} else {
// use the fallback translate
[recognizer.view setTransform:CGAffineTransformTranslate(recognizer.view.transform, translation.x, translation.y)];
}
}
[recognizer setTranslation:CGPointZero inView:self.view];
}
- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan ||
recognizer.state == UIGestureRecognizerStateChanged)
{
// make sure it stays visible
float scale = recognizer.scale;
if (recognizer.view.frame.size.width * scale > touchRadius * 2 ||
recognizer.view.frame.size.height * scale > touchRadius * 2) {
[recognizer.view setTransform:CGAffineTransformScale(recognizer.view.transform, scale, scale)];
recognizer.scale = 1;
}
}
}
- (void)handleRotate:(UIRotationGestureRecognizer *)recognizer {
UIGestureRecognizerState state = [recognizer state];
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
{
CGFloat rotation = [recognizer rotation];
[recognizer.view setTransform:CGAffineTransformRotate(recognizer.view.transform, rotation)];
}
[recognizer setRotation:0];
}
Do I need a transform after I modify the view frame? Or should I pursue another path?
I'm trying to do this natively without using libs from others so I understand it. This simplistic example will be part of a larger project.
After some research I figured out how to modify the transform for a corner/side dragging pan, to resize an image. Here are the key elements to make it work:
Save the CGAffineTransform in each gesture (pan, pinch, rotate) if
the recognizer state = UIGestureRecognizerStateBegan
Apply new CGAAffineTransforms on the saved initial transform in each gesture (pan, pinch, rotate)
Save the corner/side detected in the UIGestureRecognizerStateBegan state
Clear the corner/side detected in the UIGestureRecognizerStateEnded state
For pan gesture, adjust the translation x/y values based on corner/side detected
Make sure touch radius is large enough to be useful (24 was too small, 48 works well)
The transform worked like this:
// pan the image
recognizer.view.transform = CGAffineTransformTranslate(initialTransform, tx, ty);
if (scaleIt) {
// the origin or size changed
recognizer.view.frame = newFrame;
}
The tx and ty values were the defaults returned from the recognizer if the pan was from the center of the image. But if the user touch was near a corner or side of the view frame, the tx/ty and the frame origin are adjusted to resize the view making it appear as if that corner or side were being dragged to resize the view.
For example:
CGRect newFrame = recognizer.view.frame;
if (currentDragType == DRAG_TOPLEFT) {
tx = -translation.x;
ty = -translation.y;
newFrame.origin.x += translation.x;
newFrame.origin.y += translation.y;
newFrame.size.width -= translation.x;
newFrame.size.height -= translation.y;
} else if (currentDragType == DRAG_TOPRIGHT) {
tx = translation.x;
ty = -translation.y;
newFrame.origin.y += translation.y;
newFrame.size.width += translation.x;
newFrame.size.height -= translation.y;
}
This makes the top left corner or top right corner move in or out according to how far the touch moved. Unlike a center pan, where the whole view moves along with the touch, the opposite corner (or side) remains fixed.
There were 2 problems I did not resolve:
if the image is rotated significantly, the corner/side detection (pan) does not work because I did not check for the touch in the rotated coordinate system (but the center pan still works fine)
once the image is rotated, pinch gestures work erratically and can resize an image to zero, making it invisible
I created a simple demo and uploaded it to GitHub: https://github.com/ByteSlinger/ImageGestureDemo
Yes, I know that link could go away someday, so here's the code:
ViewController.h
//
// ViewController.h
// ImageGestureDemo
//
// Created by ByteSlinger on 6/21/18.
// Copyright © 2018 ByteSlinger. All rights reserved.
//
#import <UIKit/UIKit.h>
NSString *APP_TITLE = #"Image Gesture Demo";
NSString *INTRO_ALERT = #"\nDrag, Pinch and Rotate the Image!"
"\n\nYou can also Drag, Pinch and Rotate the background image."
"\n\nDouble tap an image to reset it";
float touchRadius = 48; // max distance from corners to touch point
typedef NS_ENUM(NSInteger, DragType) {
DRAG_OFF,
DRAG_ON,
DRAG_CENTER,
DRAG_TOP,
DRAG_BOTTOM,
DRAG_LEFT,
DRAG_RIGHT,
DRAG_TOPLEFT,
DRAG_TOPRIGHT,
DRAG_BOTTOMLEFT,
DRAG_BOTTOMRIGHT
};
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>
//callback to process gesture events
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer;
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer;
- (IBAction)handleRotate:(UIRotationGestureRecognizer *)recognizer;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
#end
ViewController.m
//
// ViewController.m
// ImageGestureDemo
//
// This is a DEMO. It shows how to pan, pinch, rotate and drag/resize a UIImageView.
//
// There is a background image and a foreground image. Both images can be
// panned, pinched and rotated, but only the foreground image can be resized
// by dragging one of it's corners or it's sides.
//
// NOTE: Sure, much of this code could have been put into a subclass of UIView
// or UIImageView. But for simplicity and reference sake, all code and
// methods are in one place, this ViewController subclass. There is no
// error checking at all. App tested on an iPhone 6+ and an iPad gen3.
//
// Features:
// - allows an image to be resized with pan gesture by dragging corners and sides
// - background image can be modified (pan, pinch, rotate)
// - foreground image can be modified (pan, pinch, rotate, drag/resize)
// - all image manipulation done within gestures linked from storyboard
// - all finger touches on screen show with yellow circles
// - when dragging, the touch circles turn to red (so you know when gestures start)
// - double tap on foreground image resets it to original size and rotation
// - double tap on background resets it and also resets the foreground image
// - screen and image touch and size info displayed on screen
// - uses CGAffineTransform objects for image manipulation
// - uses UIGestureRecognizerStateBegan in gestures to save transforms (the secret sauce...)
//
// Known Issues:
// - when the image is rotated, determining if a touch is on a corner or side
// does not work for large rotations. Need to check touch points against
// non rotated view frame and adjust accordingly.
// - after rotations, pinch and resize can shrink image to invisibility despite
// code attempts to prevent it.
//
// Created by ByteSlinger on 6/21/18.
// Copyright © 2018 ByteSlinger. All rights reserved.
//
#import "ViewController.h"
#interface ViewController ()
#property (strong, nonatomic) IBOutlet UIImageView *backgroundImageView;
#property (strong, nonatomic) IBOutlet UIImageView *foregroundImageView;
#property (strong, nonatomic) IBOutlet UILabel *screenInfoLabel;
#property (strong, nonatomic) IBOutlet UILabel *touchInfoLabel;
#property (strong, nonatomic) IBOutlet UILabel *imageInfoLabel;
#property (strong, nonatomic) IBOutlet UILabel *backgroundInfoLabel;
#property (strong, nonatomic) IBOutlet UILabel *changeInfoLabel;
#property (strong, nonatomic) IBOutlet UITapGestureRecognizer *backgroundTapGesture;
#property (strong, nonatomic) IBOutlet UITapGestureRecognizer *foregroundTapGesture;
#end
#implementation ViewController
CGRect originalImageFrame;
CGRect originalBackgroundFrame;
CGAffineTransform originalImageTransform;
CGAffineTransform originalBackgroundTransform;
NSMutableArray* touchCircles = nil;
DragType currentDragType = DRAG_OFF;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// set this to whatever your desired touch radius is
touchRadius = 48;
// In Storyboard this must have set to 1, then this seems to work ok
// when setting the double tap here
_foregroundTapGesture.numberOfTapsRequired = 2;
_backgroundTapGesture.numberOfTapsRequired = 2;
[self centerImageView:_foregroundImageView];
originalImageFrame = _foregroundImageView.frame;
originalBackgroundFrame = _backgroundImageView.frame;
originalImageTransform = _foregroundImageView.transform;
originalBackgroundTransform = _backgroundImageView.transform;
_backgroundImageView.contentMode = UIViewContentModeCenter;
_foregroundImageView.contentMode = UIViewContentModeScaleToFill; // allow stretch
[_backgroundImageView setUserInteractionEnabled:YES];
[_backgroundImageView setMultipleTouchEnabled:YES];
[_foregroundImageView setUserInteractionEnabled:YES];
[_foregroundImageView setMultipleTouchEnabled:YES];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:[UIDevice currentDevice]];
[_touchInfoLabel setText:nil];
[_changeInfoLabel setText:nil];
[_imageInfoLabel setText:nil];
[_backgroundInfoLabel setText:nil];
touchCircles = [[NSMutableArray alloc] init];
}
- (void)viewDidAppear:(BOOL)animated {
[self alert:APP_TITLE :INTRO_ALERT];
}
- (void) orientationChanged:(NSNotification *)note
{
UIDevice * device = note.object;
switch(device.orientation)
{
case UIDeviceOrientationPortrait:
/* start special animation */
break;
case UIDeviceOrientationPortraitUpsideDown:
/* start special animation */
break;
default:
break;
};
[_screenInfoLabel setText:[NSString stringWithFormat:#"Screen: %.0f/%.0f",
self.view.frame.size.width,self.view.frame.size.height]];
}
//
// Update the info labels from the passed objects
//
- (void) updateInfo:(UIView *)imageView touch:(CGPoint)touch change:(CGPoint)change {
NSString *label;
UILabel *infoLabel;
if (imageView == _foregroundImageView) {
label = #"Image: %0.f/%0.f, %0.f/%0.f";
infoLabel = _imageInfoLabel;
} else {
label = #"Background: %0.f/%0.f, %0.f/%0.f";
infoLabel = _backgroundInfoLabel;
}
[infoLabel setText:[NSString stringWithFormat:label,
imageView.layer.frame.origin.x,
imageView.layer.frame.origin.y,
imageView.layer.frame.size.width,
imageView.layer.frame.size.height]];
[_touchInfoLabel setText:[NSString stringWithFormat:#"Touch: %0.f/%.0f",
touch.x,touch.y]];
[_changeInfoLabel setText:[NSString stringWithFormat:#"Change: %0.f/%.0f",
change.x,change.y]];
}
//
// Center the passed image frame within it's bounds
//
- (void)centerImageView:(UIImageView *)imageView {
CGSize boundsSize = self.view.bounds.size;
CGRect frameToCenter = imageView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
imageView.frame = frameToCenter;
}
//
// Remove all touch circles
//
- (void)removeTouchCircles {
[touchCircles makeObjectsPerformSelector: #selector(removeFromSuperview)];
[touchCircles removeAllObjects];
}
//
// Draw a circle around the passed point where the user has touched the screen
//
- (void)drawTouchCircle:(UIView *)view fromCenter:(CGPoint)point ofRadius:(float)radius {
CGRect frame = CGRectMake(point.x - view.frame.origin.x - radius,
point.y - view.frame.origin.y - radius,
radius * 2, radius * 2);
UIView *circle = [[UIView alloc] initWithFrame:frame];
circle.alpha = 0.5;
circle.layer.cornerRadius = radius;
circle.backgroundColor = currentDragType == DRAG_OFF ? [UIColor yellowColor] : [UIColor redColor];
[circle.layer setBorderWidth:1.0];
[circle.layer setBorderColor:[[UIColor blackColor]CGColor]];
[view addSubview:circle];
[touchCircles addObject:circle];
}
//
// Draw a touch circle for the passed user touch
//
- (void)handleTouchEvent:(UIView *) view
atPoint:(CGPoint) point
forState:(UIGestureRecognizerState) state
clear:(Boolean) clear {
//NSLog(#"handleTouchEvent");
if (clear) {
[self removeTouchCircles];
}
if (state == UIGestureRecognizerStateEnded) {
[self removeTouchCircles];
} else {
[self drawTouchCircle:self.view fromCenter:point ofRadius:touchRadius];
}
[_touchInfoLabel setText:[NSString stringWithFormat:#"Touch: %0.f/%.0f",
point.x,point.y]];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//NSLog(#"touchesBegan");
[self removeTouchCircles];
NSSet *allTouches = [event allTouches];
NSArray *allObjects = [allTouches allObjects];
for (int i = 0;i < [allObjects count];i++)
{
UITouch *touch = [allObjects objectAtIndex:i];
CGPoint location = [touch locationInView: self.view];
[self handleTouchEvent:touch.view atPoint:location forState:UIGestureRecognizerStateBegan clear:NO];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
//NSLog(#"touchesMoved");
[self removeTouchCircles];
NSSet *allTouches = [event allTouches];
NSArray *allObjects = [allTouches allObjects];
for (int i = 0;i < [allObjects count];i++)
{
UITouch *touch = [allObjects objectAtIndex:i];
CGPoint location = [touch locationInView: self.view];
[self handleTouchEvent:touch.view atPoint:location forState:UIGestureRecognizerStateChanged clear:NO];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
//NSLog(#"touchesEnded");
[self removeTouchCircles];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//NSLog(#"touchesCancelled");
[self removeTouchCircles];
}
//
// Double tap resets passed image. If background image, also reset foreground image.
//
- (IBAction)handleDoubleTap:(UITapGestureRecognizer *)recognizer {
CGPoint touch = [recognizer locationInView: self.view];
if (recognizer.state == UIGestureRecognizerStateBegan ||
recognizer.state == UIGestureRecognizerStateChanged) {
[self handleTouchEvent:recognizer.view atPoint:touch forState:recognizer.state clear:NO];
} else {
[self removeTouchCircles];
}
[self alert:#"Reset" :#"The Image has been Reset!"];
CGRect frame = originalImageFrame;
CGAffineTransform transform = originalImageTransform;
if (recognizer.view == _backgroundImageView) {
_foregroundImageView.transform = transform;
_foregroundImageView.frame = frame;
[self updateInfo:_foregroundImageView touch:touch change:CGPointZero];
frame = originalBackgroundFrame;
transform = originalBackgroundTransform;
}
recognizer.view.transform = transform;
recognizer.view.frame = frame;
[self updateInfo:recognizer.view touch:touch change:CGPointZero];
}
- (void) setDragType:(CGRect)frame withTouch:(CGPoint)touch {
// the corners and sides of the current view frame
CGRect topLeft = CGRectMake(frame.origin.x,frame.origin.y,
touchRadius, touchRadius);
CGRect bottomLeft = CGRectMake(frame.origin.x,
frame.origin.y + frame.size.height - touchRadius,
touchRadius, touchRadius);
CGRect topRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
frame.origin.y,
touchRadius, touchRadius);
CGRect bottomRight = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
frame.origin.y + frame.size.height - touchRadius,
touchRadius, touchRadius);
CGRect leftSide = CGRectMake(frame.origin.x,frame.origin.y,
touchRadius, frame.size.height);
CGRect rightSide = CGRectMake(frame.origin.x + frame.size.width - touchRadius,
frame.origin.y,
touchRadius, frame.size.height);
CGRect topSide = CGRectMake(frame.origin.x,frame.origin.y,
frame.size.width, touchRadius);
CGRect bottomSide = CGRectMake(frame.origin.x,
frame.origin.y + frame.size.height - touchRadius,
frame.size.width, touchRadius);
if (CGRectContainsPoint(topLeft, touch)) {
currentDragType = DRAG_TOPLEFT;
} else if (CGRectContainsPoint(topRight, touch)) {
currentDragType = DRAG_TOPRIGHT;
} else if (CGRectContainsPoint(bottomLeft, touch)) {
currentDragType = DRAG_BOTTOMLEFT;
} else if (CGRectContainsPoint(bottomRight, touch)) {
currentDragType = DRAG_BOTTOMRIGHT;
} else if (CGRectContainsPoint(topSide, touch)) {
currentDragType = DRAG_TOP;
} else if (CGRectContainsPoint(bottomSide, touch)) {
currentDragType = DRAG_BOTTOM;
} else if (CGRectContainsPoint(leftSide, touch)) {
currentDragType = DRAG_LEFT;
} else if (CGRectContainsPoint(rightSide, touch)) {
currentDragType = DRAG_RIGHT;
} else if (CGRectContainsPoint(frame, touch)) {
currentDragType = DRAG_CENTER;
} else {
currentDragType = DRAG_OFF; // touch point is not in the view frame
}
}
//
// Return the unrotated size of the view
//
- (CGSize) getActualSize:(UIView *)view {
CGSize result;
//CGSize originalSize = view.frame.size;
CGAffineTransform originalTransform = view.transform;
float rotation = atan2f(view.transform.b, view.transform.a);
// reverse rotation of current transform
CGAffineTransform unrotated = CGAffineTransformRotate(view.transform, -rotation);
view.transform = unrotated;
// get the size of the "unrotated" view
result = view.frame.size;
// reset back to what it was
view.transform = originalTransform;
//NSLog(#"Size current = %0.f/%0.f, rotation = %0.2f, unrotated = %0.f/%0.f",
// originalSize.width,originalSize.height,
// rotation,
// result.width,result.height);
return result;
}
//
// Resize or Pan an image on the ViewController View
//
- (IBAction)handleResize:(UIPanGestureRecognizer *)recognizer {
static CGRect initialFrame;
static CGAffineTransform initialTransform;
static Boolean scaleIt = YES;
// where the user has touched down
CGPoint touch = [recognizer locationInView: self.view];
//get the translation amount in x,y
CGPoint translation = [recognizer translationInView:recognizer.view];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
initialFrame = recognizer.view.frame;
initialTransform = recognizer.view.transform;
[self setDragType:recognizer.view.frame withTouch:touch];
scaleIt = YES;
}
if (recognizer.state == UIGestureRecognizerStateEnded) {
currentDragType = DRAG_OFF;
scaleIt = NO;
[self getActualSize:recognizer.view];
} else {
// our new view frame - start with the initial one
CGRect newFrame = initialFrame;
// adjust the translation point according to where the user touched the image
float tx = translation.x;
float ty = translation.y;
// resize by dragging a corner or a side
if (currentDragType == DRAG_TOPLEFT) {
tx = -translation.x;
ty = -translation.y;
newFrame.origin.x += translation.x;
newFrame.origin.y += translation.y;
newFrame.size.width -= translation.x;
newFrame.size.height -= translation.y;
} else if (currentDragType == DRAG_TOPRIGHT) {
ty = -translation.y;
newFrame.origin.y += translation.y;
newFrame.size.width += translation.x;
newFrame.size.height -= translation.y;
} else if (currentDragType == DRAG_BOTTOMLEFT) {
tx = -translation.x;
newFrame.origin.x += translation.x;
newFrame.size.width -= translation.x;
newFrame.size.height += translation.y;
} else if (currentDragType == DRAG_BOTTOMRIGHT) {
// origin does not change
newFrame.size.width += translation.x;
newFrame.size.height += translation.y;
} else if (currentDragType == DRAG_TOP) {
tx = 0;
newFrame.origin.y += translation.y;
newFrame.size.height -= translation.y;
} else if (currentDragType == DRAG_BOTTOM) {
tx = 0;
newFrame.size.height += translation.y;
} else if (currentDragType == DRAG_LEFT) {
tx = -translation.x;
ty = 0;
newFrame.origin.x += translation.x;
newFrame.size.width -= translation.x;
} else if (currentDragType == DRAG_BOTTOM) {
ty = 0;
newFrame.size.width += translation.x;
} else { //if (currentDragType == DRAG_CENTER) {
newFrame.origin.x += translation.x;
newFrame.origin.y += translation.y;
scaleIt = NO; // normal pan
}
// get the unrotated size of the view
CGSize actualSize = [self getActualSize:recognizer.view];
// make sure we can still touch the image
if (actualSize.width < touchRadius * 2) {
newFrame.size.width += touchRadius * 2;
tx = 0; // stop resizing
}
if (actualSize.height < touchRadius * 2) {
newFrame.size.height += touchRadius * 2;
ty = 0; // stop resizing
}
// pan the image
recognizer.view.transform = CGAffineTransformTranslate(initialTransform, tx, ty);
if (scaleIt) {
// the origin or size changed
recognizer.view.frame = newFrame;
}
}
[self updateInfo:recognizer.view touch:touch change:translation];
}
//
// Pan an image on the ViewController View
//
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
static CGAffineTransform initialTransform;
// where the user has touched down
CGPoint touch = [recognizer locationInView: self.view];
//get the translation amount in x,y
CGPoint translation = [recognizer translationInView:recognizer.view];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
initialTransform = recognizer.view.transform;
currentDragType = DRAG_ON;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
currentDragType = DRAG_OFF;
[self getActualSize:recognizer.view];
}
recognizer.view.transform = CGAffineTransformTranslate(initialTransform, translation.x, translation.y);
[self updateInfo:recognizer.view touch:touch change:translation];
}
//
// Pinch (resize) an image on the ViewController View
//
- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer {
static CGSize initialSize;
static CGAffineTransform initialTransform;
// where the user has touched down
CGPoint touch = [recognizer locationInView: self.view];
//get the translation amount in x,y
CGPoint change = CGPointMake(recognizer.view.transform.tx, recognizer.view.transform.ty);
if (recognizer.state == UIGestureRecognizerStateBegan)
{
initialSize = recognizer.view.frame.size;
initialTransform = recognizer.view.transform;
currentDragType = DRAG_ON;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
currentDragType = DRAG_OFF;
[self getActualSize:recognizer.view];
}
// make sure it stays visible
float scale = recognizer.scale;
float newWidth = initialSize.width * scale;
float newHeight = initialSize.height * scale;
// make sure we can still touch it
if (newWidth > touchRadius * 2 && newHeight > touchRadius * 2) {
// scale the image
recognizer.view.transform = CGAffineTransformScale(initialTransform, scale, scale);
}
[self updateInfo:recognizer.view touch:touch change:change];
}
//
// Rotate an image on the ViewController View
//
- (IBAction)handleRotate:(UIRotationGestureRecognizer *)recognizer {
static CGFloat initialRotation;
static CGAffineTransform initialTransform;
// where the user has touched down
CGPoint touch = [recognizer locationInView: self.view];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
initialTransform = recognizer.view.transform;
initialRotation = atan2f(recognizer.view.transform.b, recognizer.view.transform.a);
currentDragType = DRAG_ON;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
currentDragType = DRAG_OFF;
[self getActualSize:recognizer.view];
}
recognizer.view.transform = CGAffineTransformRotate(initialTransform, recognizer.rotation);
[self updateInfo:recognizer.view touch:touch change:CGPointMake(initialRotation, recognizer.rotation)];
}
//
// Prevent simultaneous gestures so my transforms don't get funky
// (may not be necessary ... )
//
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return NO;
}
//
// Spew a message to the user
//
- (void)alert:(NSString *) title :(NSString *)message {
UIAlertController *alert = [UIAlertController
alertControllerWithTitle: title
message: message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okButton = [UIAlertAction
actionWithTitle:#"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
}
#end

in Xcode 6, scale value from image diff with value in UIPinchGestureRecognizer

Now i develop an app that all user drag, rotate and scale image inside uiview. but when i want to save image view data like rotate value and scale value. i found scale value from [[img.layer valueForKeyPath:#"transform.scale"] floatValue] diff with scale that i found. the scale value is like below.
-(void)scale:(id)sender {
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
_lastScale = 1.0;
}
CGFloat scale = 1.0 - (_lastScale - [(UIPinchGestureRecognizer*)sender scale]);
//CGFloat scale = 1.0 + ([(UIPinchGestureRecognizer *)sender scale] - _lastScale);
CGAffineTransform currentTransform = self.transform;
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
[self setTransform:newTransform];
_lastScale = [(UIPinchGestureRecognizer*)sender scale];
CGFloat size = [[self.layer valueForKeyPath:#"transform.scale"] floatValue];
self.saveScale = _lastScale;
NSLog(#"Scale %f %f",_lastScale, size);
}
so the save scale value is diff with [[img.layer valueForKeyPath:#"transform.scale"].
PLEASE HELP!!!
Declare Variable -> CGFloat currentScale;
- (void) handlePinches:(UIPinchGestureRecognizer*)paramSender{
if (paramSender.state == UIGestureRecognizerStateEnded){
currentScale = paramSender.scale;
} else if (paramSender.state == UIGestureRecognizerStateBegan &&
self.currentScale != 0.0f){
paramSender.scale = currentScale;
}
if (paramSender.scale != NAN &&
paramSender.scale != 0.0){
paramSender.view.transform =
CGAffineTransformMakeScale(paramSender.scale,
paramSender.scale);
}
}
try this
You need to add Delegate
#interface MyClass : MySuperClass <UIGestureRecognizerDelegate>
- (void)viewDidLoad
{
[super viewDidLoad];
// set up the image view
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"someImage"]];
[imageView setBounds:CGRectMake(0.0, 0.0, 120.0, 120.0)];
[imageView setCenter:self.view.center];
[imageView setUserInteractionEnabled:YES]; // <--- This is very important
// create and configure the pinch gesture
UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchGestureDetected:)];
[pinchGestureRecognizer setDelegate:self];
[imageView addGestureRecognizer:pinchGestureRecognizer];
// create and configure the rotation gesture
UIRotationGestureRecognizer *rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotationGestureDetected:)];
[rotationGestureRecognizer setDelegate:self];
[imageView addGestureRecognizer:rotationGestureRecognizer];
[self.view addSubview:imageView]; // add the image view as a subview of the view controllers view
}
add this two methods
- (void)pinchGestureDetected:(UIPinchGestureRecognizer *)recognizer
{
UIGestureRecognizerState state = [recognizer state];
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
{
CGFloat scale = [recognizer scale];
[recognizer.view setTransform:CGAffineTransformScale(recognizer.view.transform, scale, scale)];
[recognizer setScale:1.0];
}
}
- (void)rotationGestureDetected:(UIRotationGestureRecognizer *)recognizer
{
UIGestureRecognizerState state = [recognizer state];
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
{
CGFloat rotation = [recognizer rotation];
[recognizer.view setTransform:CGAffineTransformRotate(recognizer.view.transform, rotation)];
[recognizer setRotation:0];
}
}
after add this delegate method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
you can handle also pangesture using this method.
add pan gesture in to image.
self.panResizeGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePanGestures:)];
imgPatSelect.userInteractionEnabled = YES;
[imgPatSelect addGestureRecognizer:self.panResizeGesture];
add this method to handle pan.
- (void) handlePanGestures:(UIPanGestureRecognizer*)recognizer{
CGPoint translation = [recognizer translationInView:self.view];
CGRect recognizerFrame = recognizer.view.frame;
recognizerFrame.origin.x += translation.x;
recognizerFrame.origin.y += translation.y;
// Check if UIImageView is completely inside its superView
if (CGRectContainsRect(self.view.bounds, recognizerFrame)) {
recognizer.view.frame = recognizerFrame;
}
// Else check if UIImageView is vertically and/or horizontally outside of its
// superView. If yes, then set UImageView's frame accordingly.
// This is required so that when user pans rapidly then it provides smooth translation.
else {
// Check vertically
if (recognizerFrame.origin.y < self.view.bounds.origin.y) {
recognizerFrame.origin.y = 0;
}
else if (recognizerFrame.origin.y + recognizerFrame.size.height > self.view.bounds.size.height) {
recognizerFrame.origin.y = self.view.bounds.size.height - recognizerFrame.size.height;
}
// Check horizantally
if (recognizerFrame.origin.x < self.view.bounds.origin.x) {
recognizerFrame.origin.x = 0;
}
else if (recognizerFrame.origin.x + recognizerFrame.size.width > self.view.bounds.size.width) {
recognizerFrame.origin.x = self.view.bounds.size.width - recognizerFrame.size.width;
}
}
// Reset translation so that on next pan recognition
// we get correct translation value
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}

Limit UIPanGestureRecognizer movement in circle

I am using UIPanGesturerecogniser to move a UIButton on the screen.
I am adding the gesture to the button in this way:
panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(move:)];
[panRecognizer setMinimumNumberOfTouches:1];
[panRecognizer setMaximumNumberOfTouches:1];
[panRecognizer setDelegate:self];
[ButtonNote addGestureRecognizer:panRecognizer];
and the method is
- (void)move:(id)sender {
[[[(UITapGestureRecognizer*)sender view] layer] removeAllAnimations];
CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.ViewA];
if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
firstX = [[sender view] center].x;
firstY = [[sender view] center].y;
[self.view addSubview:[(UIPanGestureRecognizer*)sender view]];
if (self.Button1.frame.size.height > 200) {
}
else {
}
}
translatedPoint = CGPointMake(firstX+translatedPoint.x, firstY+translatedPoint.y);
[[sender view] setCenter:translatedPoint];
if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded||[(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateCancelled||[(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateFailed){
CGPoint fingerPoint2 = [(UIPanGestureRecognizer*)sender locationInView:self.BallBin.superview];
if (CGRectContainsPoint(self.BallBin.frame, fingerPoint2)) {
if (self.Button3.frame.size.height > 200) {
UIPanGestureRecognizer *gesture = (UIPanGestureRecognizer *)sender;
UIButton *button = (UIButton *)gesture.view;
[[(UIPanGestureRecognizer*)sender view] removeFromSuperview];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
NSString *myFilePath = [documentsDirectoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#", button.titleLabel.text]];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:myFilePath error:NULL];
[self performSelector:#selector(DeleteBusinessCard:) withObject:nil afterDelay:0.5];
}
else {
[self.ViewA addSubview:[(UIPanGestureRecognizer*)sender view]];
}
}
if (self.Button1.frame.size.height > 200) {
}
else {
[self.ViewB addSubview:self.BallBin];
}
}];
}
CGFloat finalX = translatedPoint.x + (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.ViewA].x);
CGFloat finalY = translatedPoint.y + (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.ViewA].y);
if(UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
if(finalX < 0) {
finalX = 0;
}
else if(finalX > 768) {
finalX = 768;
}
if(finalY < 0) {
finalY = 0;
}
else if(finalY > 1024) {
finalY = 1024;
}
}
else {
if(finalX < 0) {
finalX = 0;
}
else if(finalX > 1024) {
finalX = 768;
}
if(finalY < 0) {
finalY = 0;
}
else if(finalY > 768) {
finalY = 1024;
}
}
}
}
I want to limit the movement of the button in a certain circle area on screen, for e.g. of radius 80. Now the button is moving all over the screen. Anyway ViewA is a UIView covering the whole screen.
Use Pitagoras Formula to calculate the distance between the current location and the start point. In your case:
if (gesture.state == UIGestureRecognizerStateChanged)
{
if (sqrt(deltaX*deltaX + deltaY*deltaY) < 80)
move view to finger's position
else
stay where you are
}
We can have a logic to calculate the coordination to a particular center.
When the Pan Gesture is happening, the function should modify the center of the panning object,
with example in the following code, the pannig object named pin, which is a 50x50 pts image,
will be set to get around a Large Circle image with 194x194 pts, located in 63, 141.
And this the PanGesture handler code:
- (void)pinPanPiece:(UIPanGestureRecognizer *)gestureRecognizer {
CGPoint center = CGPointMake(63.0 + 194.0/2, 141.0+ 194.0/2);
UIView *piece = [gestureRecognizer view];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
if (piece == pin) {
CGPoint translation = [gestureRecognizer translationInView:[piece superview]];
float newX = piece.frame.origin.x + translation.x;
CGRect newFrame = piece.frame;
CGPoint pinCenter = CGPointMake(newFrame.origin.x + newFrame.size.width / 2.0, newFrame.origin.y + newFrame.size.height / 2.0);
newFrame.origin.x = newX;
CGPoint newPinCenter = CGPointMake(newFrame.origin.x + newFrame.size.width / 2.0, newFrame.origin.y + newFrame.size.height / 2.0);
float detectY = pinCenter.y + translation.y;
BOOL bUpCenter = detectY < center.y;
float deltaX = center.x - newPinCenter.x;
float deltaY = sqrtf((97.0 *97.0) - (deltaX * deltaX));
float newY;
if (bUpCenter) {
newY = center.y - deltaY;
}
else {
newY = center.y + deltaY;
}
newY -= newFrame.size.height / 2.0;
newFrame.origin.y = newY;
if ((newPinCenter.x >= 63.0) && (newPinCenter.x <= 63.0+194.0)) {
[piece setFrame:newFrame];
}
}
[gestureRecognizer setTranslation:CGPointZero inView:[piece superview]];
}
}

Why is my UIPanGestureRecognizer not being detected and firing the method?

I have a UITableView, and I want to attach a UIPanGestureRecognizer to each of the cells, which are subclassed UITableViewCells - ArticleCells. In the awakeFromNib method I add the pan gesture recognizer, but it never fires. Why?
- (void)awakeFromNib {
[super awakeFromNib];
self.cellBack = [[CellBack alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 80)];
[self.contentView addSubview:self.cellBack];
self.cellFront = [[CellFront alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 80)];
[self.contentView addSubview:self.cellFront];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pannedCell:)];
panGestureRecognizer.delegate = self;
}
Which should be firing this method. But I put a breakpoint on it and it never does get fired.
- (void)pannedCell:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
_firstTouchPoint = [recognizer translationInView:self];
NSLog(#"fired");
}
else if (recognizer.state == UIGestureRecognizerStateChanged) {
NSLog(#"fired");
CGPoint touchPoint = [recognizer translationInView:self];
// Holds the value of how far away from the first touch the finger has moved
CGFloat xPos;
// If the first touch point is left of the current point, it means the user is moving their finger right and the cell must move right
if (_firstTouchPoint.x < touchPoint.x) {
xPos = touchPoint.x - _firstTouchPoint.x;
if (xPos <= 0) {
xPos = 0;
}
}
else {
xPos = -(_firstTouchPoint.x - touchPoint.x);
if (xPos >= 0) {
xPos = 0;
}
}
if (xPos > 10 || xPos < -10) {
// Change our cellFront's origin to the xPos we defined
CGRect frame = self.cellFront.frame;
frame.origin = CGPointMake(xPos, 0);
self.cellFront.frame = frame;
}
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
[self springBack];
}
else if (recognizer.state == UIGestureRecognizerStateCancelled) {
[self springBack];
}
}
And in the .h file I added so it would be notified as implementing it. But it never calls it as I said.
Why?
Did you do this?
[self.contentView addGestureRecognizer:panGestureRecognizer];

Which label did the user last touch and how to remove it from the view

I want to know how to know which label the user last touched and how to remove that specific label from the view. I know how to change the properties for all labels, but I don't know how to find out which one was last selected.
This is how I add a label to the view.
myNewLabel.text =textField.text;
numberOfLabels++;
myNewLabel.tag=numberOfLabels;
[self.view addSubview:myNewLabel];
[shirtBackgroundView addSubview:myNewLabel];
[myNewLabel addGestureRecognizer:panGestureRecognizer];
[myNewLabel addGestureRecognizer:rotateGestureRecognizer];
[myNewLabel addGestureRecognizer:PinchGestureRecognizer];
myNewLabel.userInteractionEnabled=YES;
myNewLabel.backgroundColor=[UIColor clearColor];
[arrayForLabels addObject:myNewLabel];
Here is how I change the color of all labels.
for(int i=0;i<numberOfLabels;i++)
{
UILabel *tempLabel = [arrayForLabels objectAtIndex:i];
tempLabel.textColor=[UIColor redColor];
}
-(void)labelMoved:(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];
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200;
NSLog(#"magnitude: %f, slideMult: %f", magnitude, slideMult);
float slideFactor = 0.1 * slideMult; // Increase for more of a slide
CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
recognizer.view.center.y + (velocity.y * slideFactor));
finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);
[UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
recognizer.view.center = finalPoint;
} completion:nil];
}
}
Thanks for any suggestions...
For detect UIlabels to capture taps:
label.userInteractionEnabled = YES;
UIGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapRecieved:)];
[label addGestureRecognizer:tap];
[tap release];
//repeat for each additional label
when you touched
-(void) tapRecieved:(UITapGestureRecognizer *)tap{
currentLabel = (UILabel *) tap.view;
[currentLabel removeFromSuperview];
}
You can declare a property in .h class as UILabel *lastSelectedLabel; and in your recognizer
-(void)labelMoved:(UIPanGestureRecognizer *)recognizer{
if(lastSelectedLabel!=nil){
[lastSelectedLabel removeFromSuperview];
lastSelectedLabel=recognizer.view;
}else
lastSelectedLabel=recognizer.view;
}
I think above should work..
Thanks.

Resources