How to move an image within a view - ios

Trying to move an image within a subview by passing certain values in the app.How to calculate the pixel points "CGPoints" of the view.What will be the best way to this.
setFrame:CGRectMake(_imgstride.frame.origin.x+_imgstride.frame.size.width,
_imgstride.frame.origin.y, _imgstride.frame.size.width,
_imgstride.frame.size.height)];
I tried with the above code, the imageView is moving but not working the way i want.

Try This.
For Swift :
Its Work For me.imageview move in view.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let myPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(myPanAction))
myPanGestureRecognizer.minimumNumberOfTouches = 1
myPanGestureRecognizer.maximumNumberOfTouches = 1
self.imageView.addGestureRecognizer(myPanGestureRecognizer)
}
func myPanAction(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
var recognizerFrame = recognizer.view?.frame
recognizerFrame?.origin.x += translation.x
recognizerFrame?.origin.y += translation.y
if (self.view.bounds.contains(recognizerFrame!)) {
recognizer.view?.frame = recognizerFrame!
}else{
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)!
}
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)!
}
}
recognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
For Objective C :
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.imageview addGestureRecognizer:pan];
}
#pragma mark - Gesture Recognizer
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
CGPoint translation = [gesture translationInView:self.view];
CGRect recognizerFrame = gesture.view.frame;
recognizerFrame.origin.x += translation.x;
recognizerFrame.origin.y += translation.y;
if (CGRectContainsRect(self.view.bounds, recognizerFrame)) {
gesture.view.frame = recognizerFrame;
}else {
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;
}
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;
}
}
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
}
#end

Related

How to drag a view inside other View in swift 3?

I want to drag a view inside other View in swift 3.I searched for many methods, but they are in objective-C.
Any Help in swift will be appreciated.
Thanks
You can simply achieve the same by using the following:
#IBAction func handlePan(_ recognizer: UIPanGestureRecognizer) {
let translation: CGPoint = recognizer.translation(in: view)
let recognizerFrame: CGRect? = recognizer.view?.frame
recognizerFrame?.origin.x += translation.x
recognizerFrame?.origin.y += translation.y
if view.bounds.contains(recognizerFrame) {
recognizer.view?.frame = recognizerFrame
}
else {
// Check vertically
if recognizerFrame?.origin.y < view.bounds.origin.y {
recognizerFrame?.origin.y = nil
}
else if recognizerFrame?.origin.y + recognizerFrame?.size.height > view.bounds.size.height {
recognizerFrame?.origin.y = view.bounds.size.height - recognizerFrame?.size.height
}
if recognizerFrame?.origin.x < view.bounds.origin.x {
recognizerFrame?.origin.x = nil
}
else if recognizerFrame?.origin.x + recognizerFrame?.size.width > view.bounds.size.width {
recognizerFrame?.origin.x = view.bounds.size.width - recognizerFrame?.size.width
}
recognizer.setTranslation(CGPoint(x: 0, y: 0), in: view)
}
oldView.addSubview(newView)
newView.leftAnchor.constrains(equalTo: oldView.leftAnchor).isActive = true
newView.rightAnchor.constrains(equalTo: oldView.rightAnchor).isActive = true
newView.topAnchor.constrains(equalTo: oldView.topAnchor).isActive = true
newView.bottomAnchor.constrains(equalTo: oldView.bottomAnchor).isActive = true

UIImageView disappears after rotate and move to edge

I have written a custom class which offers the feature to move and rotates images.
I need to restrict the movement to the boundaries of parent view or Superview.
So, I wrote below code to restrict it.
This works fine before an image is rotated. If I try to rotate the image and then move the image to an edge, Image disappears leaving no log or traces.
Why does it disappear and how do I avoid it?
if(frame.origin.x < 1)
{
frame.origin.x = 1
}
if(frame.origin.y < 1)
{
frame.origin.y = 1
}
if(frame.maxX > superview!.frame.width)
{
frame.origin.x = superview!.frame.width - frame.width - 1
}
if(frame.maxY > superview!.frame.height)
{
frame.origin.y = superview!.frame.height - frame.height - 1
}
If I remove the above code, nothing disappears but image moves out of boundaries. So I feel something wrong in only above lines.
So help me to correctly implement this feature after rotation.
Full Movable Image Class code :
class movableImageView: UIImageView
{
var CenCooVar = CGPoint()
override init(image: UIImage!)
{
super.init(image: image)
self.userInteractionEnabled = true
let moveImage = UIPanGestureRecognizer(target: self, action: #selector(moveImageFnc(_:)))
let rotateImage = UIRotationGestureRecognizer(target: self, action: #selector(rotateImageFnc(_:)))
self.gestureRecognizers = [moveImage,rotateImage]
}
func moveImageFnc(moveImage: UIPanGestureRecognizer)
{
if moveImage.state == UIGestureRecognizerState.Began
{
CenCooVar = self.center
}
if moveImage.state == UIGestureRecognizerState.Changed
{
let moveCooVar = moveImage.translationInView(self.superview!)
self.center = CGPoint(x: CenCooVar.x + moveCooVar.x, y: CenCooVar.y + moveCooVar.y)
if(frame.origin.x < 1)
{
frame.origin.x = 1
}
if(frame.origin.y < 1)
{
frame.origin.y = 1
}
if(frame.maxX > superview!.frame.width)
{
frame.origin.x = superview!.frame.width - frame.width - 1
}
if(frame.maxY > superview!.frame.height)
{
frame.origin.y = superview!.frame.height - frame.height - 1
}
}
if moveImage.state == UIGestureRecognizerState.Ended
{
CenCooVar = self.center
}
}
func rotateImageFnc(rotateImage: UIRotationGestureRecognizer)
{
if rotateImage.state == UIGestureRecognizerState.Changed
{
self.transform = CGAffineTransformRotate(self.transform, rotateImage.rotation)
rotateImage.rotation = 0
}
}
}

Can't remove Gesture

I get two problem by using gestures. First I want remove a gesture called "panRecognizer" on double tap, but it doesn't work. Second Problem I use the panRecognizer to move the view but I can't move to the outer edges it stops 200 or 150 px before I reached the edge from the view.
Here is my code:
self.view.gestureRecognizers = self.pageViewController?.gestureRecognizers
let tap = UITapGestureRecognizer(target: self, action: #selector(miningCatalouge.doubleTapped(_:)))
tap.numberOfTapsRequired = 2
self.view.addGestureRecognizer(tap)
}
func doubleTapped(tap: UITapGestureRecognizer) {
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(miningCatalouge.handlePan(_:)))
let curScale = self.view!.layer.valueForKeyPath("transform.scale.x")!.floatValue
if (curScale == 1) {
pageViewController!.view.userInteractionEnabled = false
self.view.transform = CGAffineTransformMakeScale(3, 3)
panRecognizer.minimumNumberOfTouches = 1
panRecognizer.maximumNumberOfTouches = 1
self.view.addGestureRecognizer(panRecognizer)
} else {
self.view.transform = CGAffineTransformMakeScale(1, 1)
self.view.center = CGPointMake(512, 391)
pageViewController!.view.userInteractionEnabled = true
//DonĀ“t removed the gesture "panRecognizer"
self.view.removeGestureRecognizer(panRecognizer)
}
print("doubleTapped")
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: self.view)
if recognizer.state == UIGestureRecognizerState.Ended {
// 1
let velocity = recognizer.velocityInView(self.view)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
let slideMultiplier = magnitude / 200
// print("magnitude: \(magnitude), slideMultiplier: \(slideMultiplier)")
// 2
let slideFactor = 0.1 * slideMultiplier //Increase for more of a slide
// 3
var finalPoint = CGPoint(x:recognizer.view!.center.x + (velocity.x * slideFactor),
y:recognizer.view!.center.y + (velocity.y * slideFactor))
// 4
finalPoint.x = min(max(finalPoint.x, 0), self.view.bounds.size.width)
finalPoint.y = min(max(finalPoint.y, 0), self.view.bounds.size.height)
// 5
UIView.animateWithDuration(Double(slideFactor),
delay: 0,
// 6
options: UIViewAnimationOptions.CurveEaseOut,
animations: {recognizer.view!.center = finalPoint },
completion: nil)
}
}
for gesture in view.gestureRecognizers!
{
if let recognizer = gesture as? UITapGestureRecognizer {
view.removeGestureRecognizer(recognizer)
}
}
Hope this code could help
Here is the solution from J.Wang:
class miningCatalouge: UIViewController, UIPageViewControllerDelegate, UIGestureRecognizerDelegate, UIScrollViewDelegate {
// I created a new Variable
var panRecognizer: UIPanGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
self.view.gestureRecognizers = self.pageViewController?.gestureRecognizers
let tap = UITapGestureRecognizer(target: self, action: #selector(miningCatalouge.doubleTapped(_:)))
tap.numberOfTapsRequired = 2
self.view.addGestureRecognizer(tap)
//initialized action to panRecognizer
panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(miningCatalouge.handlePan(_:)))
}
func doubleTapped(tap: UITapGestureRecognizer) {
let curScale = self.view!.layer.valueForKeyPath("transform.scale.x")!.floatValue
if (curScale == 1) {
pageViewController!.view.userInteractionEnabled = false
self.view.transform = CGAffineTransformMakeScale(3, 3)
panRecognizer!.minimumNumberOfTouches = 1
panRecognizer!.maximumNumberOfTouches = 1
self.view.addGestureRecognizer(panRecognizer!)
} else {
self.view.transform = CGAffineTransformMakeScale(1, 1)
self.view.center = CGPointMake(512, 391)
pageViewController!.view.userInteractionEnabled = true
self.view.removeGestureRecognizer(panRecognizer!)
}
print("doubleTapped")
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: self.view)
if recognizer.state == UIGestureRecognizerState.Ended {
let velocity = recognizer.velocityInView(self.view)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
let slideMultiplier = magnitude / 200
// print("magnitude: \(magnitude), slideMultiplier: \(slideMultiplier)")
let slideFactor = 0.15 * slideMultiplier //Increase for more of a slide
let finalPoint = CGPoint(x:recognizer.view!.center.x + (velocity.x * slideFactor),
y:recognizer.view!.center.y + (velocity.y * slideFactor))
UIView.animateWithDuration(Double(slideFactor),
delay: 0,
// 6
options: UIViewAnimationOptions.CurveEaseOut,
animations: {recognizer.view!.center = finalPoint },
completion: nil)
}
}

Pan, zoom, rotate UIView around anchorPoint using UIGestureRecognizer (iOS)

I'm trying to rotate, pan and zoom an UIView using UIGestureRecognizers. The recognisers are added to the superview, while the rotation,zoom etc is applied to a subview (_baseImage) this is to enable me to overlay other things on top of the subview in future while still receiving the gesture events.
The idea is that the UIView should scale/rotate around an "anchor" point on the subview underneath the two touch points as this seems the most natural. What I'm having problems with is the position of the subview after setting the anchorPoint, and also that the scale and rotation doesn't seem to use the set anchorPoint. My lack of understanding of overlapping co-ordinate systems/CGAffine transforms may be getting me into trouble. Code is pulled from various examples.
Here's my code as it stands at the moment:
-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view
{
CGPoint oldOrigin = view.frame.origin;
view.layer.anchorPoint = anchorPoint;
CGPoint newOrigin = view.frame.origin;
CGPoint transition;
transition.x = newOrigin.x - oldOrigin.x;
transition.y = newOrigin.y - oldOrigin.y;
view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}
- (void) updateTransformWithOffset: (CGPoint) translation
{
// Create a blended transform representing translation,
// rotation, and scaling
_baseImage.transform = CGAffineTransformMakeTranslation(translation.x + tx, translation.y + ty);
_baseImage.transform = CGAffineTransformRotate(_baseImage.transform, theta);
_baseImage.transform = CGAffineTransformScale(_baseImage.transform, scale, scale);
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)uigr {
if (uigr.state == UIGestureRecognizerStateBegan) {
UIView *piece = self.view;
CGPoint locationInView = [uigr locationInView:_baseImage];
myFrame = _baseImage.frame;
CGPoint newAnchor = CGPointMake( (locationInView.x / piece.bounds.size.width), (locationInView.y / piece.bounds.size.height ));
[self setAnchorPoint:newAnchor forView:_baseImage];
}
}
- (void) handlePinch: (UIPinchGestureRecognizer *) uigr
{
[self adjustAnchorPointForGestureRecognizer:uigr];
if (uigr.state == UIGestureRecognizerStateBegan) {
initScale = scale;
}
scale = initScale*uigr.scale;
[self updateTransformWithOffset:CGPointZero];
}
I've found a solution to my specific problem with the anchorPoint. I was trying to apply transforms to a UIView added in Interface Builder which caused problems with the anchor point and setting new centre point, removing then re-adding the subview seemed to fix it. If anyone has similar problems here is the final code I used on my view controller, it does scale,rotate and pan on a UIView using the touch position as centre for rotate and scale:
#import "ViewController.h"
#interface ViewController (){
CGFloat tx; // x translation
CGFloat ty; // y translation
CGFloat scale; // zoom scale
CGFloat theta; // rotation angle
CGFloat initScale ;
CGFloat initTheta ;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(handleRotation:)];
[rotationGesture setDelegate:self];
[self.view addGestureRecognizer:rotationGesture];
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinch:)];
[pinchGesture setDelegate:self];
[self.view addGestureRecognizer:pinchGesture];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[panGesture setDelegate:self];
[panGesture setMinimumNumberOfTouches:1];
[panGesture setMaximumNumberOfTouches:1];
[self.view addGestureRecognizer:panGesture];
_baseImage.transform = CGAffineTransformIdentity;
tx = 0.0f; ty = 0.0f; scale = 1.0f; theta = 0.0f;
scale = 1.0;
//removing and adding back to the view seems to fix problems with anchor point I was having, I suspect because of IB layout/scaling and constraints etc
UIView *mySuperView =_baseImage.superview;
[_baseImage removeFromSuperview];
[mySuperView addSubview:_baseImage];
}
-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)myview
{
CGPoint oldOrigin = myview.frame.origin;
myview.layer.anchorPoint = anchorPoint;
CGPoint newOrigin = myview.frame.origin;
CGPoint transition;
transition.x = (newOrigin.x - oldOrigin.x);
transition.y = (newOrigin.y - oldOrigin.y);
CGPoint myNewCenter = CGPointMake (myview.center.x - transition.x, myview.center.y - transition.y);
myview.center = myNewCenter;
}
- (void) updateTransformWithOffset: (CGPoint) translation
{
// Create a blended transform representing translation,
// rotation, and scaling
_baseImage.transform = CGAffineTransformMakeTranslation(translation.x + tx, translation.y + ty);
_baseImage.transform = CGAffineTransformRotate(_baseImage.transform, theta);
_baseImage.transform = CGAffineTransformScale(_baseImage.transform, scale, scale);
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)uigr {
if (uigr.state == UIGestureRecognizerStateBegan) {
tx =_baseImage.transform.tx;
ty =_baseImage.transform.ty;
CGPoint locationInView = [uigr locationInView:_baseImage];
CGPoint newAnchor = CGPointMake( (locationInView.x / _baseImage.bounds.size.width), (locationInView.y / _baseImage.bounds.size.height ));
[self setAnchorPoint:newAnchor forView:_baseImage];
}
}
- (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 (![gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && ![otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return YES;
}
return NO;
}
- (void) handleRotation: (UIRotationGestureRecognizer *) uigr
{
if (uigr.state == UIGestureRecognizerStateBegan) {
initTheta = theta;
}
theta = initTheta+uigr.rotation;
[self adjustAnchorPointForGestureRecognizer:uigr];
[self updateTransformWithOffset:CGPointZero];
}
- (void) handlePinch: (UIPinchGestureRecognizer *) uigr
{
if (uigr.state == UIGestureRecognizerStateBegan) {
initScale = scale;
}
scale = initScale*uigr.scale;
[self adjustAnchorPointForGestureRecognizer:uigr];
[self updateTransformWithOffset:CGPointZero];
}
- (void) handlePan: (UIPanGestureRecognizer *) uigr
{
CGPoint translation = [uigr translationInView:_baseImage.superview];
[self adjustAnchorPointForGestureRecognizer:uigr];
[self updateTransformWithOffset:translation];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
The OP's original solution translated to Swift 2.0. I left out the workaround since it doesn't seem to be an issue anymore.
class ViewController: UIViewController {
var tx:CGFloat = 0.0 // x translation
var ty:CGFloat = 0.0 // y translation
var scale:CGFloat = 1.0 // zoom scale
var theta:CGFloat = 0.0 // rotation angle
var initScale:CGFloat = 1.0
var initTheta:CGFloat = 0.0
var transformedView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
transformedView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
transformedView.backgroundColor = UIColor.blueColor()
view.addSubview(transformedView)
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(OHPlastyViewController.handleRotation(_:)))
rotationGesture.delegate = self
view.addGestureRecognizer(rotationGesture)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(OHPlastyViewController.handlePinch(_:)))
pinchGesture.delegate = self
view.addGestureRecognizer(pinchGesture)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(OHPlastyViewController.handlePan(_:)))
panGesture.delegate = self
panGesture.minimumNumberOfTouches = 1
panGesture.maximumNumberOfTouches = 1
view.addGestureRecognizer(panGesture)
}
func setAnchorPoint(anchorPoint: CGPoint, forView myView: UIView) {
let oldOrigin: CGPoint = myView.frame.origin
myView.layer.anchorPoint = anchorPoint
let newOrigin = myView.frame.origin
let transition = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)
let myNewCenter = CGPoint(x: myView.center.x - transition.x, y: myView.center.y - transition.y)
myView.center = myNewCenter
}
func updateTransformWithOffset(translation: CGPoint) {
transformedView.transform = CGAffineTransformMakeTranslation(translation.x + tx, translation.y + ty)
transformedView.transform = CGAffineTransformRotate(transformedView.transform, theta)
transformedView.transform = CGAffineTransformScale(transformedView.transform, scale, scale)
}
func adjustAnchorPointForGestureRecognizer(recognizer: UIGestureRecognizer) {
if (recognizer.state == .Began) {
tx = transformedView.transform.tx
ty = transformedView.transform.ty
let locationInView = recognizer.locationInView(transformedView)
let newAnchor = CGPoint(x: (locationInView.x / transformedView.bounds.size.width), y: (locationInView.y / transformedView.bounds.size.height))
setAnchorPoint(newAnchor, forView: transformedView)
}
}
func handleRotation(recognizer: UIRotationGestureRecognizer) {
if recognizer.state == .began {
initTheta = theta
}
theta = initTheta + recognizer.rotation
adjustAnchorPointForGestureRecognizer(recognizer)
updateTransformWithOffset(CGPointZero)
}
func handlePinch(recognizer: UIPinchGestureRecognizer) {
if recognizer.state == .began {
initScale = scale
}
scale = initScale * recognizer.scale
adjustAnchorPointForGestureRecognizer(recognizer)
updateTransformWithOffset(CGPointZero)
}
func handlePan(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(transformedView.superview)
adjustAnchorPointForGestureRecognizer(recognizer)
updateTransformWithOffset(translation)
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.view != otherGestureRecognizer.view {
return false
}
if !gestureRecognizer.isKindOfClass(UITapGestureRecognizer.self) && !otherGestureRecognizer.isKindOfClass(UITapGestureRecognizer.self) {
return true
}
return false
}
}

adding inertia to a UIPanGestureRecognizer

I am trying to move a sub view across the screen which works, but i also want to add inertia or momentum to the object.
My UIPanGestureRecognizer code that i already have is below.
Thanks in advance.
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self addGestureRecognizer:panGesture];
(void)handlePan:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:self.superview];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.superview];
if (recognizer.state == UIGestureRecognizerStateEnded) {
[self.delegate card:self.tag movedTo:self.frame.origin];
}
}
Again thanks.
Have a look at RotationWheelAndDecelerationBehaviour. there is an example for how to do the deceleration for both linear panning and rotational movement. Trick is to see what is the velocity when user ends the touch and continue in that direction with a small deceleration.
Well, I'm not a pro but, checking multiple answers, I managed to make my own code with which I am happy.
Please tell me how to improve it and if there are any bad practices I used.
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translatedPoint = [recognizer translationInView:self.postViewContainer];
CGPoint velocity = [recognizer velocityInView:recognizer.view];
float bottomMargin = self.view.frame.size.height - containerViewHeight;
float topMargin = self.view.frame.size.height - scrollViewHeight;
if ([recognizer state] == UIGestureRecognizerStateChanged) {
newYOrigin = self.postViewContainer.frame.origin.y + translatedPoint.y;
if (newYOrigin <= bottomMargin && newYOrigin >= topMargin) {
self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, self.postViewContainer.center.y + translatedPoint.y);
}
[recognizer setTranslation:CGPointMake(0, 0) inView:self.postViewContainer];
}
if ([recognizer state] == UIGestureRecognizerStateEnded) {
__block float newYAnimatedOrigin = self.postViewContainer.frame.origin.y + (velocity.y / 2.5);
if (newYAnimatedOrigin <= bottomMargin && newYAnimatedOrigin >= topMargin) {
[UIView animateWithDuration:1.2 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^ {
self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, self.postViewContainer.center.y + (velocity.y / 2.5));
}
completion:^(BOOL finished) {
[self.postViewContainer setFrame:CGRectMake(0, newYAnimatedOrigin, self.view.frame.size.width, self.view.frame.size.height - newYAnimatedOrigin)];
}
];
}
else {
[UIView animateWithDuration:0.6 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^ {
if (newYAnimatedOrigin > bottomMargin) {
self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, bottomMargin + self.postViewContainer.frame.size.height / 2);
}
if (newYAnimatedOrigin < topMargin) {
self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, topMargin + self.postViewContainer.frame.size.height / 2);
}
}
completion:^(BOOL finished) {
if (newYAnimatedOrigin > bottomMargin)
[self.postViewContainer setFrame:CGRectMake(0, bottomMargin, self.view.frame.size.width, scrollViewHeight)];
if (newYAnimatedOrigin < topMargin)
[self.postViewContainer setFrame:CGRectMake(0, topMargin, self.view.frame.size.width, scrollViewHeight)];
}
];
}
}
}
I have used two different animation, one is the default inertia one and the other if for when the user flings the containerView with high velocity.
It works well under iOS 7.
I took the inspiration from the accepted answer's implementation. Here is a Swift 5.1 version.
Logic:
You need to calculate the angle changes with the velocity at which the pan gesture ended and keep rotating the wheel in an endless timer until the velocity wears down because of deceleration rate.
Keep decreasing the current velocity in every iteration of the timer
with some factor (say, 0.9).
Keep a lower limit on the velocity to
invalidate the timer and complete the deceleration process.
Main function used to calculate deceleration:
// deceleration behaviour constants (change these for different deceleration rates)
private let timerDuration = 0.025
private let decelerationSmoothness = 0.9
private let velocityToAngleConversion = 0.0025
private func animateWithInertia(velocity: Double) {
_ = Timer.scheduledTimer(withTimeInterval: self.timerDuration, repeats: true) { [weak self] timer in
guard let this = self else {
return
}
let concernedVelocity = this.currentVelocity == 0.0 ? velocity : this.currentVelocity
let newVelocity = concernedVelocity * this.decelerationSmoothness
this.currentVelocity = newVelocity
var angleTraversed = newVelocity * this.velocityToAngleConversion * this.maximumRotationAngleInCircle
if !this.isClockwiseRotation {
angleTraversed *= -1
}
// exit condition
if newVelocity < 0.1 {
timer.invalidate()
this.currentVelocity = 0.0
} else {
this.traverseAngularDistance(angle: angleTraversed)
}
}
}
Full working code with helper functions, extensions and usage of aforementioned function in the handlePanGesture function.
// deceleration behaviour constants (change these for different deceleration rates)
private let timerDuration = 0.025
private let decelerationSmoothness = 0.9
private let velocityToAngleConversion = 0.0025
private let maximumRotationAngleInCircle = 360.0
private var currentRotationDegrees: Double = 0.0 {
didSet {
if self.currentRotationDegrees > self.maximumRotationAngleInCircle {
self.currentRotationDegrees = 0
}
if self.currentRotationDegrees < -self.maximumRotationAngleInCircle {
self.currentRotationDegrees = 0
}
}
}
private var previousLocation = CGPoint.zero
private var currentLocation = CGPoint.zero
private var velocity: Double {
let xFactor = self.currentLocation.x - self.previousLocation.x
let yFactor = self.currentLocation.y - self.previousLocation.y
return Double(sqrt((xFactor * xFactor) + (yFactor * yFactor)))
}
private var currentVelocity = 0.0
private var isClockwiseRotation = false
#objc private func handlePanGesture(panGesture: UIPanGestureRecognizer) {
let location = panGesture.location(in: self)
if let rotation = panGesture.rotation {
self.isClockwiseRotation = rotation > 0
let angle = Double(rotation).degrees
self.currentRotationDegrees += angle
self.rotate(angle: angle)
}
switch panGesture.state {
case .began, .changed:
self.previousLocation = location
case .ended:
self.currentLocation = location
self.animateWithInertia(velocity: self.velocity)
default:
print("Fatal State")
}
}
private func animateWithInertia(velocity: Double) {
_ = Timer.scheduledTimer(withTimeInterval: self.timerDuration, repeats: true) { [weak self] timer in
guard let this = self else {
return
}
let concernedVelocity = this.currentVelocity == 0.0 ? velocity : this.currentVelocity
let newVelocity = concernedVelocity * this.decelerationSmoothness
this.currentVelocity = newVelocity
var angleTraversed = newVelocity * this.velocityToAngleConversion * this.maximumRotationAngleInCircle
if !this.isClockwiseRotation {
angleTraversed *= -1
}
if newVelocity < 0.1 {
timer.invalidate()
this.currentVelocity = 0.0
this.selectAtIndexPath(indexPath: this.nearestIndexPath, shouldTransformToIdentity: true)
} else {
this.traverseAngularDistance(angle: angleTraversed)
}
}
}
private func traverseAngularDistance(angle: Double) {
// keep the angle in -360.0 to 360.0 range
let times = Double(Int(angle / self.maximumRotationAngleInCircle))
var newAngle = angle - times * self.maximumRotationAngleInCircle
if newAngle < -self.maximumRotationAngleInCircle {
newAngle += self.maximumRotationAngleInCircle
}
self.currentRotationDegrees += newAngle
self.rotate(angle: newAngle)
}
Extensions being used in above code:
extension UIView {
func rotate(angle: Double) {
self.transform = self.transform.rotated(by: CGFloat(angle.radians))
}
}
extension Double {
var radians: Double {
return (self * Double.pi)/180
}
var degrees: Double {
return (self * 180)/Double.pi
}
}

Resources