Programmer Hugo Programmer Hugo - 3 months ago 21
Swift Question

Rotation gesture produces undesired rotation

I need to rotate an UIImageview on user's motion using just one finger motion. For that I use a rotation gesture and then override touchesMoved with this code:

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {

let touch = touches.first
if touch!.view === rotateImageView {
let position = touch!.locationInView(self.view)

let target = rotateImageView.center
let angle = atan2(target.y-position.y, target.x-position.x)
rotateImageView.transform = CGAffineTransformMakeRotation(angle)
}
}


This works as long as the user stop to touch the screen and make the motion on another part of the screen. On that scenario the UIImageview rotates on undesired position when the user starts to move the finger. I need to keep the original rotation position of the UIImageview to avoid this but I have no idea how to do it.

enter image description here

This gif shows the current behavior on the project prototype I have. As you can see when I start to rotate on the right side the flame icon suddenly appears near the touch point, that's what I want to prevent.

UPDATE Aug 23-2016

I used Matt and Dasem suggestion of using CGAffineTransformRotate but I still see the jump. I took Dasem code and adapted to my project like follows (Let me know if the adaption is wrong):

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

origin = (touches.first?.locationInView(self.view))!

//save the current transform
tempTransform = rotateImageView.transform

}

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {

let touchPoint = touches.first?.locationInView(self.view)

xOffSet = CGVector(dx:(origin.x)-rotateImageView.center.x, dy:(origin.x) - rotateImageView.center.y)
yOffSet = CGVector(dx:touchPoint!.x - rotateImageView.center.x, dy:touchPoint!.y - rotateImageView.center.y)

let angle = atan2(yOffSet.dy,yOffSet.dx) - atan2(xOffSet.dy,xOffSet.dx)
rotateImageView.transform = CGAffineTransformRotate(tempTransform, angle)

}


This change produces a similar effect when user touches a different region of the touchscreen. In this gif I've changed the image and remove the icons to make the effect more noticeable.

enter image description here

UPDATE WITH SOLUTION

Reapplied code with Jasem update and it worked , the following code solved the problem without using subclassing

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

origin = (touches.first?.locationInView(self.view))!
xOffSet = CGVector(dx:(origin.x)-rotateImageView.center.x, dy:(origin.y) - rotateImageView.center.y)
startingAngle = atan2(xOffSet.dy,xOffSet.dx)

//save the current transform
tempTransform = rotateImageView.transform
}

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {

let touchPoint = touches.first?.locationInView(self.view)
yOffSet = CGVector(dx:touchPoint!.x - rotateImageView.center.x, dy:touchPoint!.y - rotateImageView.center.y)
let angle = atan2(yOffSet.dy,yOffSet.dx)

let deltaAngle = angle - startingAngle!
rotateImageView.transform = CGAffineTransformRotate(tempTransform, deltaAngle)
}

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
startingAngle = nil
}

//reset in case drag is cancelled
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
rotateImageView.transform = tempTransform
startingAngle = nil
}

Answer

you may want to change self.superview to self.view (mine is subclass)

var xOffSet:CGVector = CGVectorMake(0, 0)
var yOffSet:CGVector = CGVectorMake(0, 0)
var origin:CGPoint = CGPointZero
var tempTransform=CGAffineTransform()
var startingAngle:CGFloat?


   override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

    origin = (touches.first?.locationInView(self.superview))!
    xOffSet = CGVector(dx:(origin.x)-self.center.x, dy:(origin.y) - self.center.y)
    startingAngle = atan2(xOffSet.dy,xOffSet.dx)

    //save the current transform
    tempTransform = self.transform
}

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {

    let touchPoint = touches.first?.locationInView(self.superview)
    yOffSet = CGVector(dx:touchPoint!.x - self.center.x, dy:touchPoint!.y - self.center.y)
    let angle = atan2(yOffSet.dy,yOffSet.dx)

    let deltaAngle = angle - startingAngle!
    self.transform = CGAffineTransformRotate(tempTransform, deltaAngle) 
}


override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {  
  startingAngle = nil
}

//reset in case drag is cancelled
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
        self.transform = tempTransform
        startingAngle = nil
    }