Resizing a view in two directions? - ios

In Swift I have a question. I know how to resize views in general using gestures but what I'm trying to do is have the user scale the rectangular view in two different directions. If I'm trying to increase the width of the rectangle I pinch left and right. If I'm trying to increase the length pinch up and down. You get the picture. Here is my code so far. I'm not sure how to convert my pseudo code into real code.
func axisFromPoints(p1: CGPoint, _ p2: CGPoint) -> String {
let x_1 = p1.x
let x_2 = p2.x
let y_1 = p1.y
let y_2 = p2.y
let absolutePoint = CGPoint(x: x_2 - x_1, y: y_2 - y_1)
let radians = atan2(Double(absolutePoint.x), Double(absolutePoint.y))
let absRad = fabs(radians)
if absRad > M_PI_4 && absRad < 3*M_PI_4 {
return "x"
} else {
return "y"
}
}
func handlePinch(_ sender : UIPinchGestureRecognizer) {
//var p1 = location(ofTouch 0,in:sender)
//var p2 = location of touch 1
//call function
// if function == 'x'
//view.transformed.scaledBy(x:sender.scale,y:1)
//if function == 'y'
//view.transformed.scaledBy(x:1,y:sender.scale)
if let view = sender.view {
view.transform = view.transform.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
}
}

Here's How I did it.
func axisFromPoints(p1: CGPoint, _ p2: CGPoint) -> String {
let x_1 = p1.x
let x_2 = p2.x
let y_1 = p1.y
let y_2 = p2.y
let absolutePoint = CGPoint(x: x_2 - x_1, y: y_2 - y_1)
let radians = atan2(Double(absolutePoint.x), Double(absolutePoint.y))
let absRad = fabs(radians)
if absRad > M_PI_4 && absRad < 3*M_PI_4 {
return "x"
} else {
return "y"
}
}
func handlePinch(_ sender : UIPinchGestureRecognizer) {
if sender.state == .changed{
let p1: CGPoint = sender.location(ofTouch: 0, in: view)
let p2: CGPoint = sender.location(ofTouch: 1, in: view)
var x_scale = sender.scale;
var y_scale = sender.scale;
if axisFromPoints(p1,p2) == "x" {
y_scale = 1;
x_scale = sender.scale
}
if axisFromPoints(p1, p2) == "y" {
x_scale = 1;
y_scale = sender.scale
}
view.transform = view.transform.scaledBy(x: x_scale, y: y_scale)
sender.scale = 1
}}

Related

Determining if custom iOS views overlap

I've defined a CircleView class:
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
// Get the Graphics Context
if let context = UIGraphicsGetCurrentContext() {
// Set the circle outerline-width
context.setLineWidth(5.0);
// Set the circle outerline-colour
UIColor.blue.set()
// Create Circle
let center = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
let radius = (frame.size.width - 10)/2
context.addArc(center: center, radius: radius, startAngle: 0.0, endAngle: .pi * 2.0, clockwise: true)
context.setFillColor(UIColor.blue.cgColor)
// Draw
context.strokePath()
context.fillPath()
}
}
}
And created an array of them with a randomly set number:
var numberOfCircles: Int!
var circles: [CircleView] = []
numberOfCircles = Int.random(in: 1..<10)
let circleWidth = CGFloat(50)
let circleHeight = circleWidth
var i = 0
while i < numberOfCircles {
let circleView = CircleView(frame: CGRect(x: 0.0, y: 0.0, width: circleWidth, height: circleHeight))
circles.append(circleView)
i += 1
}
After creating the circles, I call a function, drawCircles, that will draw them on the screen:
func drawCircles(){
for c in circles {
c.frame.origin = c.frame.randomPoint
while !UIScreen.main.bounds.contains(c.frame.origin) {
c.frame.origin = CGPoint()
c.frame.origin = c.frame.randomPoint
let prev = circles.before(c)
if prev?.frame.intersects(c.frame) == true {
c.frame.origin = c.frame.randomPoint
}
}
}
for c in circles {
self.view.addSubview(c)
}
}
The while loop in the drawCircles method makes sure that no circles are placed outside of the bounds of the screen, and works as expected.
What I'm struggling with is to make sure that the circles don't overlap each other, like so:
I'm using the following methods to determine either the next
I'm using this methods to determine what the previous / next element in the array of circles:
extension BidirectionalCollection where Iterator.Element: Equatable {
typealias Element = Self.Iterator.Element
func after(_ item: Element, loop: Bool = false) -> Element? {
if let itemIndex = self.firstIndex(of: item) {
let lastItem: Bool = (index(after:itemIndex) == endIndex)
if loop && lastItem {
return self.first
} else if lastItem {
return nil
} else {
return self[index(after:itemIndex)]
}
}
return nil
}
func before(_ item: Element, loop: Bool = false) -> Element? {
if let itemIndex = self.firstIndex(of: item) {
let firstItem: Bool = (itemIndex == startIndex)
if loop && firstItem {
return self.last
} else if firstItem {
return nil
} else {
return self[index(before:itemIndex)]
}
}
return nil
}
}
This if statement, however; doesn't seem to be doing what I'm wanting; which is to make sure that if a circle intersects with another one, to change it's origin to be something new:
if prev?.frame.intersects(c.frame) == true {
c.frame.origin = c.frame.randomPoint
}
If anyone has any ideas where the logic may be, or of other ideas on how to make sure that the circles don't overlap with each other, that would be helpful!
EDIT: I did try the suggestion that Eugene gave in his answer like so, but still get the same result:
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt(xDist * xDist + yDist * yDist))
}
if prev != nil {
if distance((prev?.frame.origin)!, c.frame.origin) <= 40 {
print("2")
c.frame.origin = CGPoint()
c.frame.origin = c.frame.randomPoint
}
}
But still the same result
EDIT 2
Modified my for loop based on Eugene's edited answer / clarifications; still having issues with overlapping circles:
for c in circles {
c.frame.origin = c.frame.randomPoint
let prev = circles.before(c)
let viewMidX = self.circlesView.bounds.midX
let viewMidY = self.circlesView.bounds.midY
let xPosition = self.circlesView.frame.midX - viewMidX + CGFloat(arc4random_uniform(UInt32(viewMidX*2)))
let yPosition = self.circlesView.frame.midY - viewMidY + CGFloat(arc4random_uniform(UInt32(viewMidY*2)))
if let prev = prev {
if distance(prev.center, c.center) <= 50 {
c.center = CGPoint(x: xPosition, y: yPosition)
}
}
}
That’s purely geometric challenge. Just ensure that distance between the circle centers greater than or equal to sum of their radiuses.
Edit 1
Use UIView.center instead of UIView.frame.origin. UIView.frame.origin gives you the top left corner of UIView.
if let prev = prev {
if distance(prev.center, c.center) <= 50 {
print("2")
c.center = ...
}
}
Edit 2
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(hypot(xDist, yDist))
}
let prev = circles.before(c)
if let prevCircleCenter = prev?.center {
let distance = distance(prevCenter, c.center)
if distance <= 50 {
let viewMidX = c.bounds.midX
let viewMidY = c.bounds.midY
var newCenter = c.center
var centersVector = CGVector(dx: newCenter.x - prevCircleCenter.x, dy: newCenter.y - prevCircleCenter.y)
centersVector.dx *= 51 / distance
centersVector.dy *= 51 / distance
newCenter.x = prevCircleCenter.x + centersVector.dx
newCenter.y = prevCircleCenter.y + centersVector.dy
c.center = newCenter
}
}

iOS Swift: Rotate and Scale UIView without resizing

I have a UIView which i want to scale and rotate via pan and pinch gesture. But issue is when i scale view and after then when i rotate it's resizing back to initial value before scaling.
extension UIView {
func addPinchGesture() {
var pinchGesture = UIPinchGestureRecognizer()
pinchGesture = UIPinchGestureRecognizer(target: self,
action: #selector(handlePinchGesture(_:)))
self.addGestureRecognizer(pinchGesture)
}
#objc func handlePinchGesture(_ sender: UIPinchGestureRecognizer) {
self.transform = self.transform.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
}
}
// ROTATION
extension UIView {
func addRotationGesture() {
var rotationGesture = RotationGestureRecognizer()
rotationGesture = RotationGestureRecognizer(target: self,
action: #selector(handleRotationGesture(_:)))
self.addGestureRecognizer(rotationGesture)
}
#objc func handleRotationGesture(_ sender: RotationGestureRecognizer) {
var originalRotation = CGFloat()
switch sender.state {
case .began:
sender.rotation = sender.lastRotation
originalRotation = sender.rotation
case .changed:
let newRotation = sender.rotation + originalRotation
self.transform = CGAffineTransform(rotationAngle: newRotation) // Rotation is fine but it is resizing view
// self.transform = self.transform.rotated(by: newRotation / CGFloat(180 * Double.pi)) // NOT WORKING i.e. irregular rotation
case .ended:
sender.lastRotation = sender.rotation
default:
break
}
}
}
Before Scaling
After Scaling
After Rotation
I want it to be rotate without affecting view size. How can i achieve that?
You are resetting the scale transform of view when applying rotation transform. Create a property to hold original scale of the view.
var currentScale: CGFloat = 0
And when pinch is done, store the currentScale value to current scale. Then when rotating also use this scale, before applying the rotation.
let scaleTransform = CGAffineTransform(scaleX: currentScale, y: currentScale)
let concatenatedTransform = scaleTransform.rotated(by: newRotation)
self.transform = concatenatedTransform
You are using extension to add gesture recognizers, for that reason you cannot store currentScale. You can also get the scale values of view from current transform values. Here is how your code would look like,
extension UIView {
var currentScale: CGPoint {
let a = transform.a
let b = transform.b
let c = transform.c
let d = transform.d
let sx = sqrt(a * a + b * b)
let sy = sqrt(c * c + d * d)
return CGPoint(x: sx, y: sy)
}
func addPinchGesture() {
var pinchGesture = UIPinchGestureRecognizer()
pinchGesture = UIPinchGestureRecognizer(target: self,
action: #selector(handlePinchGesture(_:)))
self.addGestureRecognizer(pinchGesture)
}
#objc func handlePinchGesture(_ sender: UIPinchGestureRecognizer) {
self.transform = self.transform.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
}
}
// ROTATION
extension UIView {
func addRotationGesture() {
var rotationGesture = RotationGestureRecognizer()
rotationGesture = RotationGestureRecognizer(target: self,
action: #selector(handleRotationGesture(_:)))
self.addGestureRecognizer(rotationGesture)
}
#objc func handleRotationGesture(_ sender: RotationGestureRecognizer) {
var originalRotation = CGFloat()
switch sender.state {
case .began:
sender.rotation = sender.lastRotation
originalRotation = sender.rotation
case .changed:
let scale = CGAffineTransform(scaleX: currentScale.x, y: currentScale.y)
let newRotation = sender.rotation + originalRotation
self.transform = scale.rotated(by: newRotation)
case .ended:
sender.lastRotation = sender.rotation
default:
break
}
}
}
I used this answer as a reference for extracting the scale value.
I was facing the same issue Once I pinch from a finger, then after I rotate from a button it automatically scales down from the current but I set logic below.
#objc func rotateViewPanGesture(_ recognizer: UIPanGestureRecognizer) {
touchLocation = recognizer.location(in: superview)
let center = CGRectGetCenter(frame)
switch recognizer.state {
case .began:
deltaAngle = atan2(touchLocation!.y - center.y, touchLocation!.x - center.x) - CGAffineTrasformGetAngle(transform)
initialBounds = bounds
initialDistance = CGpointGetDistance(center, point2: touchLocation!)
case .changed:
let ang = atan2(touchLocation!.y - center.y, touchLocation!.x - center.x)
let angleDiff = deltaAngle! - ang
let a = transform.a
let b = transform.b
let c = transform.c
let d = transform.d
let sx = sqrt(a * a + b * b)
let sy = sqrt(c * c + d * d)
let currentScale = CGPoint(x: sx, y: sy)
let scale = CGAffineTransform(scaleX: currentScale.x, y: currentScale.y)
self.transform = scale.rotated(by: -angleDiff)
layoutIfNeeded()
case .ended:
print("end gesture status")
default:break
}
}

Make UIPinchGestureRecognizer work outside imageview like stickers in Instagram story

I am working on an image editing application. When sticker is added to the view, pinchGesture can be used to scale it. Problem is when the imageView(sticker) is very small, the gesture is very difficult to use. I need to be able to pinch outside of the sticker and still scale the imageView.
I then added pinchGesture to self.view and detected it's touches; if the straight line from the finger intersects the imageView, pinchGesture is applied. This works fine until multiple stickers(imageViews) are added and the gesture works simultaneously on multiple stickers.
So, I calculated the distance of intersection from the fingers to the rect of each view and the view with the longest distance would get the pinchGesture.
This doesn't work all the time.
Here is the code:
#objc func pinched(sender: UIPinchGestureRecognizer){
slider.isUserInteractionEnabled = false
if sender.numberOfTouches == 2 && sender.state != .ended{
selectedView = []
let finger1 = sender.location(ofTouch: 0, in: self.view)
let finger2 = sender.location(ofTouch: 1, in: self.view)
for subview in self.view.subviews{
if subview is UIImageView{
let rect = subview.frame
let width = rect.size.width
let height = rect.size.height
let Ax = rect.origin.x
let Ay = rect.origin.y
let Bx = rect.origin.x + width
let By = rect.origin.y
let Dx = rect.origin.x
let Dy = rect.origin.y + height
let Cx = rect.origin.x + height
let Cy = rect.origin.y + width
let A = CGPoint(x: Ax, y: Ay)
let B = CGPoint(x: Bx, y: By)
let C = CGPoint(x: Cx, y: Cy)
let D = CGPoint(x: Dx, y: Dy)
let lineToAB = getIntersectionOfLines(line1: (finger1, finger2), line2: (A, B))
let lineToBC = getIntersectionOfLines(line1: (finger1, finger2), line2: (B, C))
let lineToCD = getIntersectionOfLines(line1: (finger1, finger2), line2: (C, D))
let lineToAD = getIntersectionOfLines(line1: (finger1, finger2), line2: (A, D))
var dx: CGFloat!
if lineToAB != CGPoint.zero || lineToBC != CGPoint.zero || lineToCD != CGPoint.zero || lineToAD != CGPoint.zero{
print("touched")
touched = true
if lineToAB != CGPoint.zero{
dx = distance(lineToAB, lineToCD)
}else if lineToBC != CGPoint.zero{
dx = distance(lineToBC, lineToAD)
}else if lineToCD != CGPoint.zero{
dx = distance(lineToAB, lineToCD)
}else if lineToAD != CGPoint.zero{
dx = distance(lineToAD, lineToBC)
}else{
dx = 0.0
}
}else{
dx = 0.0
touched = false
}
selectedView.append(selectedViews(distance: dx, view: subview))
}
}
var max: CGFloat = 0.0
var viewtoscale: UIView!
for item in selectedView{
if item.distance > max{
max = item.distance
viewtoscale = item.view
}
}
if viewtoscale != nil{
if viewtoscale != self.slider || viewtoscale != tempImageView || viewtoscale != drawView{
if viewtoscale is UIImageView{
viewtoscale.transform = viewtoscale.transform.scaledBy(x: sender.scale, y: sender.scale)
}
}
}
sender.scale = 1
}
}
Also the functions for getting intersection and distance:
func getIntersectionOfLines(line1: (a: CGPoint, b: CGPoint), line2: (a: CGPoint, b: CGPoint)) -> CGPoint {
let distance = (line1.b.x - line1.a.x) * (line2.b.y - line2.a.y) - (line1.b.y - line1.a.y) * (line2.b.x - line2.a.x)
if distance == 0 {
print("error, parallel lines")
return CGPoint.zero
}
let u = ((line2.a.x - line1.a.x) * (line2.b.y - line2.a.y) - (line2.a.y - line1.a.y) * (line2.b.x - line2.a.x)) / distance
let v = ((line2.a.x - line1.a.x) * (line1.b.y - line1.a.y) - (line2.a.y - line1.a.y) * (line1.b.x - line1.a.x)) / distance
if (u < 0.0 || u > 1.0) {
print("error, intersection not inside line1")
return CGPoint.zero
}
if (v < 0.0 || v > 1.0) {
print("error, intersection not inside line2")
return CGPoint.zero
}
return CGPoint(x: line1.a.x + u * (line1.b.x - line1.a.x), y: line1.a.y + u * (line1.b.y - line1.a.y))
}
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt((xDist * xDist) + (yDist * yDist)))
}
You can set tag for each imageView and declare selectedSticker as Int, when user tapped in sticker set selectedSticker to its tag, then just scale the selected sticker.

Specific Image Spawn

How can I edit this code so the spawnRandomPosition function also doesn't allow the image to spawn 45 pixels down from the bottom of the screen? So the top 45 pixels of the screen cannot be spawned in.
class SecondViewController: UIViewController {
private var addOne = 0
func spawnRandomPosition() -> CGPoint
{
let width = UIScreen.main.fixedCoordinateSpace.bounds.width
let height = UIScreen.main.fixedCoordinateSpace.bounds.height
let centerArea = CGRect(x: UIScreen.main.fixedCoordinateSpace.bounds.midX - 75.0 / 2,
y: UIScreen.main.fixedCoordinateSpace.bounds.midY - 75.0 / 2,
width: 75.0,
height: 75.0)
while true
{
let randomPosition = CGPoint(x: Int(arc4random_uniform(UInt32(width))), y: Int(arc4random_uniform(UInt32(height))))
if !centerArea.contains(randomPosition)
{
return randomPosition
}
}
}
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint.zero, in: self.view)
if (WhiteDot.frame.contains(smallDot.frame) && smallDot.image != nil) {
addOne += 1
score.text = "\(addOne)"
smallDot?.center = spawnRandomPosition()
}
}
}
Just modify function spawnRandomPosition a bit:
func spawnRandomPosition() -> CGPoint
{
let minY: CGFloat = 45.0
let width = UIScreen.main.fixedCoordinateSpace.bounds.width
let height = UIScreen.main.fixedCoordinateSpace.bounds.height - minY
let centerArea = CGRect(x: UIScreen.main.fixedCoordinateSpace.bounds.midX - 75.0 / 2,
y: UIScreen.main.fixedCoordinateSpace.bounds.midY - 75.0 / 2,
width: 75.0,
height: 75.0)
while true
{
let randomPosition = CGPoint(x:CGFloat(arc4random()).truncatingRemainder(dividingBy: height),
y:minY + CGFloat(arc4random()).truncatingRemainder(dividingBy: width))
// Check for 'forbidden' area
if !centerArea.contains(randomPosition)
{
return randomPosition
}
}
}
Maybe you need a different approach, is not elegant but you could use if's
func spawnRandomPosition() -> CGPoint {
let screenWidth = UIScreen.main.fixedCoordinateSpace.bounds.width
let screenHeight = UIScreen.main.fixedCoordinateSpace.bounds.height
boolean valid = true;
repeat
{
let randomPosition = CGPoint(x:CGFloat(arc4random()),y:arc4random())
if(randomPosition.x >screenWidth || randomPosition.y < 45 || (randomPosition.x > ((screenWidth/2)- 37.5) && randomPosition.x< ((screenWidth/2)+ 37.5) || (randomPosition.y > ((screenHeight/2)- 37.5) && randomPosition.y< ((screenHeight/2)+ 37.5))
valid = false
}while(valid = false;)
}
But of course you should take in consideration that could arc4random() give back a very high number and end up with an inefficient solution, so you could use rand() instead

Rotating ImageView using UIPanGestureRecognizer- Swift 3

I am trying to rotate an ImageView I have depending on the X coordinate it is on. Basically, I want it to have a rotation of 0º when x = 300 and a rotation of 180º when x = 190.
I had to program the UIPanGestureRecognizer programmatically. Here is the code I currently have right now:
#objc func personDrag(recognizer: UIPanGestureRecognizer) {
let rotationSub: CGFloat = 1
let translation = recognizer.translation(in: rView)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x, y:view.center.y + translation.y)
view.transform = view.transform.rotated(by: CGFloat.pi - rotationSub)
}
recognizer.setTranslation(CGPoint.zero, in: rView)
}
I was going to attempt to change the rotation degree by 1 every time they panned but it doesn't really work/make sense. Any help would be appreciated. Thank you so much!
Cheers, Theo
You can build your implementation on this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageview: UIImageView!
private var currentRotation: Rotation = .none
/* Certain rotation points (rotation of 0º when x = 300 and a rotation of 180º when x = 190) */
enum Rotation {
case none, xPoint190, xPoint300
}
override func viewDidLoad() {
super.viewDidLoad()
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
imageview.addGestureRecognizer(gestureRecognizer)
imageview.isUserInteractionEnabled = true
}
#IBAction func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard gestureRecognizer.state == .began || gestureRecognizer.state == .changed else {
return
}
guard let imgView = gestureRecognizer.view else {
return
}
let translation = gestureRecognizer.translation(in: self.view)
imgView.center = CGPoint(x: imgView.center.x + translation.x, y: imgView.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
let angle: CGFloat = self.degreesToRadians(180.0)
/* After reaching x point case - rotating and setting rotation occured to prohibit further rotation */
if imgView.layer.frame.origin.x <= 190, currentRotation != .xPoint190 {
imgView.transform = imgView.transform.rotated(by: angle)
currentRotation = .xPoint190
} else if imgView.layer.frame.origin.x >= 300, currentRotation != .xPoint300 {
imgView.transform = imgView.transform.rotated(by: angle)
currentRotation = .xPoint300
}
private func degreesToRadians(_ deg: CGFloat) -> CGFloat {
return deg * CGFloat.pi / 180
}
}
I hope this will help you.
#objc func rotateViewPanGesture(_ recognizer: UIPanGestureRecognizer) {
touchLocation = recognizer.location(in: superview)
let center = CGRectGetCenter(frame)
switch recognizer.state {
case .began:
deltaAngle = atan2(touchLocation!.y - center.y, touchLocation!.x - center.x) - CGAffineTrasformGetAngle(transform)
initialBounds = bounds
initialDistance = CGpointGetDistance(center, point2: touchLocation!)
case .changed:
let ang = atan2(touchLocation!.y - center.y, touchLocation!.x - center.x)
let angleDiff = deltaAngle! - ang
let a = transform.a
let b = transform.b
let c = transform.c
let d = transform.d
let sx = sqrt(a * a + b * b)
let sy = sqrt(c * c + d * d)
let currentScale = CGPoint(x: sx, y: sy)
let scale = CGAffineTransform(scaleX: currentScale.x, y: currentScale.y)
self.transform = scale.rotated(by: -angleDiff)
layoutIfNeeded()
case .ended:
print("end gesture status")
default:break
}
}
Using Swift5
Programmatically
Rotate view by single point touch
import UIKit
class ViewController: UIViewController {
//Variable for rotating
private var deltaAngle:CGFloat = 0
let squareView : UIView = {
let anyView = UIView()
anyView.backgroundColor = .red
anyView.isUserInteractionEnabled = true
anyView.isMultipleTouchEnabled = true
return anyView
}()
let rotateButton : UIButton = {
let button = UIButton()
button.backgroundColor = .black
button.setImage(UIImage(systemName: "rotate.right"), for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
squareView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
rotateButton.frame = CGRect(x: 0, y: squareView.frame.height-30, width: 30, height: 30)
squareView.center = view.center
view.addSubview(squareView)
squareView.addSubview(rotateButton)
let PanToRotate = UIPanGestureRecognizer(target: self, action: #selector(handleRotateGesture(_:)))
rotateButton.addGestureRecognizer(PanToRotate)
}
#objc func handleRotateGesture(_ recognizer : UIPanGestureRecognizer){
let touchLocation = recognizer.location(in: squareView.superview)
let center = squareView.center
switch recognizer.state{
case .began :
self.deltaAngle = atan2(touchLocation.y - center.y, touchLocation.x - center.x) - atan2(squareView.transform.b, squareView.transform.a)
case .changed:
let angle = atan2(touchLocation.y - center.y, touchLocation.x - center.x)
let angleDiff = self.deltaAngle - angle
squareView.transform = CGAffineTransform(rotationAngle: -angleDiff)
default: break
}
}
}

Resources