iOS - UIPinchGestureRecognizer & UIPanGestureRecognizer - Reset - ios

In my application for zooming and panning, i'm using above said gesture recognizers. This is working fine.
I want to a button which will bring back the image to initial state. That means show the actual image or reset to initial state. Can some one tell me how to achieve this?
The code is as below:
-(void)handlePanGesture:(UIPanGestureRecognizer*)recognizer
{
CGPoint translation = [(UIPanGestureRecognizer*)recognizer translationInView:[self superview]];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y + translation.y);
[(UIPanGestureRecognizer*)recognizer setTranslation:CGPointMake(0, 0) inView:[self superview]];
}
-(void)handlePinchGesture:(UIPinchGestureRecognizer*)recognizer
{
static CGRect initialBounds;
if (recognizer.state == UIGestureRecognizerStateBegan)
{
initialBounds = self.bounds;
}
CGFloat factor = [(UIPinchGestureRecognizer *)recognizer scale];
CGAffineTransform zt = CGAffineTransformScale(CGAffineTransformIdentity, factor, factor);
self.bounds = CGRectApplyAffineTransform(initialBounds, zt);
}

Based on #borrden's comment.
Check if the current center of the ImageView and original center are same. If not reset the center of the ImageView. You can add a UIView.animation.. to make it look good.
Resize the imageView to original size by setting it to CGAffineTransformIdentity. This can also be added to the UIView.animation.. in the above.
Code. Make changes as per your need.
UIView.animateWithDuration(0.2, delay: 0.0, options: .CurveEaseIn, animations: {
//Move image back to center
self.mainImageView.center = self.originalCenter!
self.layoutIfNeeded()
//Resize image to original
self.mainImageView.transform = CGAffineTransformIdentity
}, completion: nil
)

Related

How can I move and rotate a view using CGAffineTransform?

I want to move a view using a pan gesture recognizer.
UIPanGestureRecognizer *gesture;
CGPoint touch = [gesture locationInView:view.superview];
view.frame = CGRectMake(touch.x, touch.y, view.frame.size.width, view.frame.size.height);
Also, I would like to rotate the view as it moves.
view.transform = CGAffineTransformMakeRotation(multiplier * M_2_PI);
I have two basic problems:
The movement didn't start from the point the user touched the view.
When I try to both move and rotate the view it stretches beyond logic.
Can someone give me a very basic code sample on how to fix those issues using CGAffineTransform rather than go read this and that?
You can find code example here https://github.com/K-Be/ViewMovingTest
Main idea is
Save starting point of center of the view.
Find translation and apply it for center of the view.
If you want to change frame, you should set identity transform before and restore transform after applying, because frame is a function of bounds, center, transform (https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instp/UIView/frame)
This is some code:
if (_panRecognizer.state == UIGestureRecognizerStateBegan)
{
_startCenter = _frameView.center;
}
else if (_panRecognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint transition = [_panRecognizer translationInView:self.view];
CGPoint newCenter = CGPointMake(_startCenter.x + transition.x, _startCenter.y + transition.y);
self.frameView.center = newCenter;
}
else
{
}
and
CGAffineTransform transform = self.frameView.transform;
self.frameView.transform = CGAffineTransformIdentity;
self.frameView.frame = CGRectInset(self.view.bounds, CGRectGetWidth(self.view.bounds) / 3.0, CGRectGetHeight(self.view.bounds) / 3.0);
self.frameView.transform = transform;

Resize UIImageView with finger IOS

I have a UIImageView (and a UITextView) and I am changing the hight and with of them both using a plus and minus button. However I wanted to do like you can do in most programs, where a box appears round my views, and the user can drag a corner of it to resize. Only one corner needs to be dragged. The opposite is fixed. How do you do this?
Another way - is by GestureRecognizer. In some task i resized image like this:
- (void)resizeImage:(UIPinchGestureRecognizer *)recognizer
{
if ([recognizer state] == UIGestureRecognizerStateBegan)
previousScale = [recognizer scale];
UIView *viewToResize = recognizer.view;
if ([recognizer state] == UIGestureRecognizerStateChanged)
{
CGFloat currentScale = [[viewToResize.layer valueForKeyPath:#"transform.scale"] floatValue];
CGFloat newScale = 1 - (previousScale - [recognizer scale]);
newScale = MIN(newScale, MAX_SCALE / currentScale);
newScale = MAX(newScale, MIN_SCALE / currentScale);
CGAffineTransform transform = CGAffineTransformScale([viewToResize transform], newScale, newScale);
viewToResize.transform = transform;
previousScale = [recognizer scale];
}
}
ios objective-c
I never done this before but i think you would have to override the touchesBegan and touchesMoved functions of the view. When you initiate the touchesBegan method make sure you are touching the correct view, (Imageview or UItextView) set a boolen in their stating. imTouchingOneOrTheOther. So when you now hit the touchesMoved function you can adjust the size of the frame accordingly. I would try to adjust the frame first with UIView block based animations and if that doesn't look ok then i would play around with coreAnimation. Let me know how it works out.

How to restrict a moveable view by Pan gesture

I have a UIImageView which is moveable via a pan gesture.
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.photoMask addGestureRecognizer:pan];
I would like to restrict the area this can be moved on screen. Rather than the user be able to drag the view right to the side of the screen, I want to restrict it by a margin of some sort. How can I do this?
Also, how is this then handled when rotated?
EDIT ---
#pragma mark - Gesture Recognizer
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
NSLog(#"Pan Gesture");
gesture.view.center = [gesture locationInView:self.view];
}
This is my current method to handle the pan. What I need to do is continue to move the imageview by the center point and also restrict its movement when close to the edge of the screen by 50 for example.
One possible solution to this is in your handlePan method, check the location of the point on the screen, and only commit the change if it is within the bounds you wish to restrict it to.
For ex.
-(void) handlePan:(UIGestureRecognizer*)panGes{
CGPoint point = [panGes locationInView:self.view];
//Only allow movement up to within 100 pixels of the right bound of the screen
if (point.x < [UIScreen mainScreen].bounds.size.width - 100) {
CGRect newframe = CGRectMake(point.x, point.y, theImageView.frame.size.width, theImageView.frame.size.height);
theImageView.frame = newframe;
}
}
I believe this would also correctly handle any screen rotation
EDIT
To move your image view by the center of its frame, the handlePan method could look something like this.
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
CGPoint point = [gesture locationInView:self.view];
//Only allow movement up to within 50 pixels of the bounds of the screen
//Ex. (IPhone 5)
CGRect boundsRect = CGRectMake(50, 50, 220, 448);
if (CGRectContainsPoint(boundsRect, point)) {
imgView.center = point;
}
}
Check whether the point is within your desired bounds, and if so, set the center of your image view frame to that point.
I'm not sure if I'm being over-simplistic here but I think you can accomplish this by using an if clause.
-(void)handlePan:(UIPanGestureRecognizer*)gesture {
UIImageView *viewToDrag = gesture.view; // this is the view you want to move
CGPoint translation = [gesture translationInView:viewToDrag.superview]; // get the movement delta
CGRect movedFrame = CGRectOffset(viewToDrag.frame, translation.x, translation.y); // this is the new (moved) frame
// Now this is the critical part because I don't know if your "margin"
// is a CGRect or maybe some int values, the important thing here is
// to compare if the "movedFrame" values are in the allowed movement area
// Assuming that your margin is a CGRect you could do the following:
if (CGRectContainsRect(yourPermissibleMargin, movedFrame)) {
CGPoint newCenter = CGPointMake(CGRectGetMidX(movedFrame), CGRectGetMidY(movedFrame));
viewToDrag.center = newCenter; // Move your view
}
// -OR-
// If you have your margins as int values you could do the following:
if ( (movedFrame.origin.x + movedFrame.size.width) < 50) {
CGPoint newCenter = CGPointMake(CGRectGetMidX(movedFrame), CGRectGetMidY(movedFrame));
viewToDrag.center = newCenter; // Move your view
}
}
You'll probably have to adapt this to meet your specific needs.
Hope this helps!
Here is the answer in Swift 4 -
Restrict the view's movement to superview
#objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer)
{
// Allows smooth movement of stickers.
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed
{
let point = gestureRecognizer.location(in: self.superview)
if let superview = self.superview
{
let restrictByPoint : CGFloat = 30.0
let superBounds = CGRect(x: superview.bounds.origin.x + restrictByPoint, y: superview.bounds.origin.y + restrictByPoint, width: superview.bounds.size.width - 2*restrictByPoint, height: superview.bounds.size.height - 2*restrictByPoint)
if (superBounds.contains(point))
{
let translation = gestureRecognizer.translation(in: self.superview)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.superview)
}
}
}
}
If you want more control over it, match restrictByPoint value to your movable view's frame.
- (void)dragAction:(UIPanGestureRecognizer *)gesture{
UILabel *label = (UILabel *)gesture.view;
CGPoint translation = [gesture translationInView:label];
if (CGRectContainsPoint(label.frame, [gesture locationInView:label] )) {
label.center = CGPointMake(label.center.x,
label.center.y);
[gesture setTranslation:CGPointZero inView:label];
}
else{
label.center = CGPointMake(label.center.x,
label.center.y + translation.y);
[gesture setTranslation:CGPointZero inView:label];
}
}

UIPanGestureRecognizer to maximize and minimize a UIView

To minimize and maximize a UIView I am using a UIPangestureRecognizer. The code is listed below:
-(void) pannningMyView:(UIPanGestureRecognizer*) panGesture{
CGPoint newPoint=[panGesture translationInView:self.view];
CGPoint oldPoint=self.myPanningView.frame.origin;
CGFloat dx=newPoint.x;
CGFloat dy=newPoint.y;
if(dx>0){
CGRect oldRect=self.myPanningView.frame;
oldRect.size.width+=dx;
oldRect.size.height+=dy;
self.myPanningView.frame=oldRect;
}
}
But, the transition is very fast, such that I move few pixels and it covers the entire screen. I am not able to figure out what correction is required to my code.
The problem is that your translations are cumulative because the translationInView is from the beginning of the continuous gesture, but you're you're adding the translation to the current frame, not the original frame. This is solved by checking the gesture state, and if you're at the start of the gesture then save the original frame, and then use that as the basis for future translations as the gesture proceeds.
-(void) panningMyView:(UIPanGestureRecognizer*) panGesture
{
static CGRect originalFrame; // or you could make this a non-static class ivar
if (panGesture.state == UIGestureRecognizerStateBegan)
{
originalFrame = self.myPanningView.frame;
}
else if (panGesture.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [panGesture translationInView:self.view];
if (translation.x > 0) {
CGRect newFrame = originalFrame;
newFrame.size.width += translation.x;
newFrame.size.height += translation.y;
self.myPanningView.frame = newFrame;
}
}
}
Note, I got rid of oldPoint because you didn't seem to be using it. I also renamed newPoint to translation because it's not a point on the screen but a measure of how much your finger has moved (or translated) on the screen. I also renamed oldRect to newFrame, because I think that more accurately captures what it is.
Essentially, I've tried to preserve the logic of your routine, but simply clarify your logic and variable names. I would have thought that you might want an additional else if cause, checking for ended or canceled gestures, using an animation to complete or reverse the gesture as appropriate, but I didn't tackle that as you didn't reference this in your original question.
Regardless, I hope you get the idea of what we're doing here. We're saving the original frame and applying the translation to that rather than applying it to the current frame.
Update:
In a follow up question, you asked how you might clarify the animation. You might do something like:
-(void) panningMyView:(UIPanGestureRecognizer*) panGesture
{
static CGRect originalFrame; // or you could make this a non-static class ivar
CGPoint translation = [panGesture translationInView:self.view];
if (panGesture.state == UIGestureRecognizerStateBegan)
{
originalFrame = self.myPanningView.frame;
}
else if (panGesture.state == UIGestureRecognizerStateChanged)
{
if (translation.x > 0) {
CGRect newFrame = originalFrame;
newFrame.size.width += translation.x;
newFrame.size.height += translation.y;
self.myPanningView.frame = newFrame;
}
}
else if (panGesture.state == UIGestureRecognizerStateEnded ||
panGesture.state == UIGestureRecognizerStateCancelled ||
panGesture.state == UIGestureRecognizerStateFailed)
{
CGRect finalFrame = originalFrame;
// if we've gone more than half way, move it all the way,
// otherwise return it to the original frame
if (translation.x > (self.view.frame.size.width / 2.0))
{
finalFrame.size.width += self.view.frame.size.width;
finalFrame.size.height += self.view.frame.size.height;
}
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.myPanningView.frame = finalFrame;
}
completion:nil];
}
}

UIGestureRecognizer to Drag UIView

I'm trying to build a GestureRecognizer to drag a UIView from right to left. If the user drag the View to the half of the screen, the View will be automatically dragged into the left corner to discard it, but if the user do not drag up to half the screen it will return to the corner right of the screen.
Little lost here, any tutorials or something?
Thanks
EDIT : Heres some code, its a UIImageView, inside a UIScrollView, i need to drag the entire scroll or just the image inside it :
_myScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.window.bounds.size.width, self.window.bounds.size.height)]; [_myScroll setContentSize:CGSizeMake(self.window.bounds.size.width , 1300)];
_tutorial = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.window.bounds.size.width, 1300)]; [_tutorial setImage:[UIImage imageNamed:#"Default"]];
_tutorial.contentMode = UIViewContentModeScaleAspectFit; [_myScroll addSubview:_tutorial];
_tutorial.userInteractionEnabled = YES;
UIPanGestureRecognizer * recognizer = [[UIPanGestureRecognizer alloc]initWithTarget:_tutorial action:#selector(handlePan:)];
[_tutorial addGestureRecognizer:recognizer];
[self.window addSubview:_myScroll];
And the method i try to drag the UIImageView
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_myScroll];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:_myScroll];
}
Have a look over here. This is a UIGestureRecognizer tutorial which will give you the basics to handle pinch and pan gestures. Once you've learnt the basics, it's really easy to accomplish what you want. All you need is to check the position of the view once the pan is completed to send it to the correct position. Have a look at this other tutorial on UIScrollViews, it may help you find your way through the second part.
Both tutorials come from raywenderlich.com, probably the most useful iOS tutorials site I know.
Here is some code on your handlePan: method you can work on :
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_myScroll];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
if (recognizer.state == UIGestureRecognizerStateEnded) {
// Check here for the position of the view when the user stops touching the screen
// Set "CGFloat finalX" and "CGFloat finalY", depending on the last position of the touch
// Use this to animate the position of your view to where you want
[UIView animateWithDuration: aDuration
delay: 0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
CGPoint finalPoint = CGPointMake(finalX, finalY);
recognizer.view.center = finalPoint; }
completion:nil];
}
[recognizer setTranslation:CGPointMake(0, 0) inView:_myScroll];
}
I won't give you the perfect answer here, you'll have to work on the code to find a way to make it work as you want it to.
Here is one last tip. You can do some kind of "smooth" animation by using a slideFactor. It will help your animation being more "realistic". Work on this code, that you add right at the beginning of the "if":
CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200;
float slideFactor = 0.1 * slideMult; // Increase for more slide
Then in UIView animateWithDuration:, use slideFactor as the duration.

Resources