Bigair Bigair - 25 days ago 13
Swift Question

How to add drag to move function to a transformed view?

I made a custom view which can be resized and rotated by dragging a corner circle button.

I need the view to be able to drag to change its position.

I tried several ways but none worked property( the view just acts strangely when combining rotation and resize actions).

What is a proper way to do this?

According to this answer quoting apple's document, What to use to move UIView self.frame or self.transform property?
I should not use frame property along with transformation so I assume I need to user CGAffineTransform to change position of the view?
(I tried this but did not work well)

import Foundation
import UIKit

class ResizeRotateView: UIImageView, Resizable {

private let buttonWidth: CGFloat = 40
private var themeColor: UIColor = UIColor.magentaColor()


// Rotation Resize
var radian: CGFloat = 0

private var tempAnglePanStart : Float = 0
private var tempAngleLastPanEnd: Float = 0


// UI Subviews
lazy var cornerButton: UIView = {
let b = UIView()

b.layer.cornerRadius = self.buttonWidth / 2
b.layer.borderWidth = 1
b.layer.borderColor = self.themeColor.CGColor

let panGesture = UIPanGestureRecognizer(target: self, action: #selector(buttonTouchMoved) )
b.addGestureRecognizer(panGesture)

return b
}()




// Init

override init(frame: CGRect) {
super.init(frame: frame)

setupSuviews()
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}


func setupSuviews() {
addSubview( cornerButton )
addConstraintsWithFormat("H:[v0(\(buttonWidth))]", views: cornerButton)
addConstraintsWithFormat("V:[v0(\(buttonWidth))]", views: cornerButton)
addConstraint(NSLayoutConstraint(item: cornerButton, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: cornerButton, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0))
}


override func hitTest(point: CGPoint, withEvent e: UIEvent?) -> UIView? { // This will enable gesture out of self.view's boundary. From stackoverflow.

if let result = super.hitTest(point, withEvent:e) {
return result
}

for sub in self.subviews.reverse() {
let pt = self.convertPoint(point, toView:sub)
if let result = sub.hitTest(pt, withEvent:e) {
return result
}
}
return nil
}







////// Event

func buttonTouchMoved(gestureRecognizer: UIPanGestureRecognizer) {

resize(gestureRecognizer)
rotate(gestureRecognizer)
}








////// Action

// Resize
func resize(gestureRecognizer: UIPanGestureRecognizer) {

// Temporary set aglet to 0
self.transform = CGAffineTransformMakeRotation( 0 )

// Resize
let locationInSelf = gestureRecognizer.locationInView(self)

resizeByKeepingAspectRatioByDistanceFromCenter(toLocation: locationInSelf)
layoutSubviews()

// Recover angle
self.transform = CGAffineTransformMakeRotation( radian )
layoutSubviews()

}



// Rotation
func rotate(gestureRecognizer: UIPanGestureRecognizer) {

guard let superView = self.superview else {
print("#Error: Super View not found")
return
}

let angle = self.angle(byLocation: gestureRecognizer.locationInView( superView ) )


if gestureRecognizer.state == .Began {
print("Pan begin:\(angle)")
tempAnglePanStart = angle - tempAngleLastPanEnd
}


let destinationAngle = angle - tempAnglePanStart
self.transform = CGAffineTransformMakeRotation( destinationAngle.degreesToRadians )


if gestureRecognizer.state == .Ended {
print("Pan ended")
tempAngleLastPanEnd = destinationAngle
}
}













////// Helper

func angle( firstPoint: CGPoint, secondPoint: CGPoint ) -> Float {

let dx = firstPoint.x - secondPoint.x
let dy = firstPoint.y - secondPoint.y
let angle = atan2(dy, dx).radiansToDegrees
let angleInFloat = Float( angle.double )

let formattedAngle = angleInFloat < 0 ? angleInFloat + 360.0 : angleInFloat

return formattedAngle
}


func angle(byLocation location: CGPoint) -> Float {

let targetViewCnter = self.center
return self.angle(targetViewCnter, secondPoint: location)
}

func setAngle(angle: Float) {
let radian = angle.degreesToRadians
self.transform = CGAffineTransformMakeRotation( radian )
self.radian = radian
}
}


enter image description here
enter image description here




Update



I came very close. I could move by keeping rotation angle and view's size. The remaining problem is view jumps a bit when I start dragging.

import Foundation
import UIKit

class ResizeRorateMovableView: ResizeRotateView {

var startCenter: CGPoint = CGPointZero


override init(frame: CGRect) {
super.init(frame: frame)

setupSuviews()

let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureEvent))
addGestureRecognizer( panGesture )
userInteractionEnabled = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}



func panGestureEvent(panGesture: UIPanGestureRecognizer) {

if panGesture.state == .Began {
self.startCenter = self.center
return
}

if panGesture.state == .Changed {

let location = panGesture.translationInView(self)

// Rotate first, then move
var transfrom = CGAffineTransformMakeRotation( self.radian )
transfrom = CGAffineTransformTranslate(transfrom, location.x, location.y)
self.transform = transfrom

return
}

}
}

Answer

All I need was just to add a superview to implement move function.

This case, I can completely separate rotation function and move function making it real easy to handle.

  • ViewA. Move function implemented. It is visible below to make it easy to understand but its color should be clear. I made touch for move function only enabled on ViewB(the subview).

  • ViewB. Rotation function implemented. It is a subview of ViewA enter image description here