I am trying to rotate a view towards another views center point(Remember not around, its towards).
Assume I have 2 views placed like this
Now I want to rotate the topview to point the bottom view like this
so this what I did
Change the top views anchor point to its origin. so that it can rotate and point its edge to the bottom view
Calculated the angle between the first views origin point and the bottom views center
And applied the calculated transform to the top view.
Below the code I am using
let rect = CGRect(x: 70, y: 200, width: 300, height: 100)
let rectView = UIView(frame: rect)
rectView.layer.borderColor = UIColor.green.cgColor;
rectView.layer.borderWidth = 2;
let endView = UIView(frame: CGRect(x: 250, y: 450, width: 70, height: 70))
endView.layer.borderColor = UIColor.green.cgColor;
endView.layer.borderWidth = 2;
let end = endView.center;
self.view.addSubview(endView)
self.view.addSubview(rectView!)
rectView.setAnchorPoint(CGPoint.zero)
let angle = rectView.bounds.origin.angle(to: end);
UIView.animate(withDuration: 3) {
rectView.transform = rectView.transform.rotated(by: angle)
}
I am using this extension from Get angle from 2 positions to calculate the angle between 2 points
extension CGPoint {
func angle(to comparisonPoint: CGPoint) -> CGFloat {
let originX = comparisonPoint.x - self.x
let originY = comparisonPoint.y - self.y
let bearingRadians = atan2f(Float(originY), Float(originX))
var bearingDegrees = CGFloat(bearingRadians).degrees
while bearingDegrees < 0 {
bearingDegrees += 360
}
return bearingDegrees
}
}
extension CGFloat {
var degrees: CGFloat {
return self * CGFloat(180.0 / M_PI)
}
}
extension UIView {
func setAnchorPoint(_ point: CGPoint) {
var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y);
newPoint = newPoint.applying(transform)
oldPoint = oldPoint.applying(transform)
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.position = position
layer.anchorPoint = point
}
}
But this isn't working as expected, the rotation is way off. Check the below screen capture of the issue
I assume this issue is related to how the angle is calculated, but I could't figure out what?
any help is much appreciated.
Angles need to be in radians
The rotation angle needs to be specified in radians:
Change:
rectView.transform = rectView.transform.rotated(by: angle)
to:
rectView.transform = rectView.transform.rotated(by: angle / 180.0 * .pi)
or change your angle(to:) method to return radians instead of degrees.
Use frame instead of bounds
Also, you need to use the frame of your rectView when computing the angle. The bounds of a view is its internal coordinate space, which means its origin is always (0, 0). You want the frame which is the coordinates of the view in its parent's coordinate system.
Change:
let angle = rectView.bounds.origin.angle(to: end)
to:
let angle = rectView.frame.origin.angle(to: end)
Note: Because your anchorPoint is the corner of rectView, this will point the top edge of rectView to the center of endView. One way to fix that would be to change your anchorPoint to the center of the left edge of rectView and then use that point to compute your angle.
Related
I want to make a rotation of point by -90 degrees
Initial
Final
Let's take a look on top left and top right points of Initial. Their coordinates are:
let topLeft = CGPoint(x: 2, y: 1)
let topRight = CGPoint(x: 3, y: 1)
And after rotation coordinates of them should become:
topLeft 1:0
topRight 2:0
How can i do it ?
I have tried several answers but none of them give me my final results.
did not work:
Rotating a CGPoint around another CGPoint
What is the best way to rotate a CGPoint on a grid?
Here are some code from my playground:
let topLeft = CGPoint(x: 2, y: 1)
let topRight = CGPoint(x: 3, y: 1)
func rotatePoint1(_ point: CGPoint, _ degrees: CGFloat) -> CGPoint {
let s = CGFloat(sinf(Float(degrees)))
let c = CGFloat(cosf(Float(degrees)));
return CGPoint(x: c * point.x - s * point.y, y: s * point.x + c * point.y)
}
func rotatePoint2(_ point: CGPoint, _ degrees: CGFloat, _ origin: CGPoint) -> CGPoint {
let dx = point.x - origin.x
let dy = point.y - origin.y
let radius = sqrt(dx * dx + dy * dy)
let azimuth = atan2(dy, dx) // in radians
let newAzimuth = azimuth + degrees * CGFloat(M_PI / 180.0) // convert it to radians
let x = origin.x + radius * cos(newAzimuth)
let y = origin.y + radius * sin(newAzimuth)
return CGPoint(x: x, y: y)
}
func rotatePoint3(_ point: CGPoint, _ degrees: CGFloat) -> CGPoint {
let translateTransform = CGAffineTransform(translationX: point.x, y: point.y)
let rotationTransform = CGAffineTransform(rotationAngle: degrees)
let customRotation = (rotationTransform.concatenating(translateTransform.inverted())).concatenating(translateTransform)
return point.applying(customRotation)
}
print(rotatePoint1(topLeft, -90))
print(rotatePoint1(topRight, -90))
You are really describing two rotations with your example:
The points are rotated by -90 degrees around the center of the 3x3 grid. When this happens, the topLeft point becomes bottomLeft, and topRight becomes topLeft.
Then you rotate those points around the center of the square 90 degrees (ie. the other direction) to make them topLeft and topRight again.
Using this function from this answer:
func rotatePoint(target: CGPoint, aroundOrigin origin: CGPoint, byDegrees: CGFloat) -> CGPoint {
let dx = target.x - origin.x
let dy = target.y - origin.y
let radius = sqrt(dx * dx + dy * dy)
let azimuth = atan2(dy, dx) // in radians
let newAzimuth = azimuth + byDegrees * .pi / 180 // convert it to radians
let x = origin.x + radius * cos(newAzimuth)
let y = origin.y + radius * sin(newAzimuth)
return CGPoint(x: x, y: y)
}
let topLeft = CGPoint(x: 2, y: 1)
let topRight = CGPoint(x: 3, y: 1)
let squareCenter = CGPoint(x: 2.5, y: 1.5)
// First rotate around the center of the 3 x 3 square
let centerOfRotation = CGPoint(x: 1.5, y: 1.5)
let tl1 = rotatePoint(target: topLeft, aroundOrigin: centerOfRotation, byDegrees: -90) // (x 1 y 1)
let tr1 = rotatePoint(target: topRight, aroundOrigin: centerOfRotation, byDegrees: -90) // (x 1 y 0)
let sc1 = rotatePoint(target: squareCenter, aroundOrigin: centerOfRotation, byDegrees: -90) // (x 1.5 y 0.5)
// Now rotate the 1x1 square the other way around new position of square center
let tl2 = rotatePoint(target: tl1, aroundOrigin: sc1, byDegrees: 90) // (x 1 y 0)
let tr2 = rotatePoint(target: tr1, aroundOrigin: sc1, byDegrees: 90) // (x 2 y 0)
Note: As #MBo noted in the comments, if your cell is always 1x1, it is sufficient to rotate the center of your cell and then just add and subtract the 0.5 offsets to find the four corners.
You can just transform you view like so
yourView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi/2)
EDIT:
My bad.
Use CGFloat.pi when working with degrees
print(rotatePoint1(topLeft, -CGFloat.pi/2))
Use sin and cos functions directly
let s = sin(degrees)
let c = cos(degrees)
iOS coordinate system is a bit flipped compared to standard one so you will have to adjust the angle (you can see simulation)
print(rotatePoint1(topLeft, -CGFloat.pi/2)) // (1.0000000000000002, -2.0)
print(rotatePoint1(topRight, -CGFloat.pi/2)) // (1.0000000000000002, -3.0)
I have 2 Views (viewLeft, viewRight). I am rotating these views with angel 0 (initial setup) by calculating the same anchor point so that it looks like views are rotating across hinge.
I have done below code, which is working fine if I took the views in storyboard (viewcontroller) and it does not work if I copy and paste same views and code in nib. In Nib, at 0 angel views leaves some space between them. It is weird behavior, I checked all code outlets and constraints, everything is fine but unable to figure out why it is not working.
I want it to be working in NIB.
Any help will be appreciable.
Github dummy project reference here
Outputs :
From Storyboard
From XIB
Code work
Set Anchor point
extension UIView {
func setAnchorPoint(_ point: CGPoint) {
var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y);
newPoint = newPoint.applying(transform)
oldPoint = oldPoint.applying(transform)
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.position = position
layer.anchorPoint = point
}
}
Rotation function
func rotateView(_ addAngel : CGFloat) {
let rotateToAngel = angel + addAngel
guard rotateToAngel <= 0.6 && rotateToAngel >= -0.6 else {
return
}
angel = rotateToAngel
let anchorPointLeft = CGPoint(x: (viewLeft.frame.width - viewLeft.frame.height/2)/viewLeft.frame.width,
y: ( viewLeft.frame.height/2)/viewLeft.frame.height)
viewLeft.setAnchorPoint(anchorPointLeft)
let anchorPointRight = CGPoint(x: (viewRight.frame.height/2/viewRight.frame.width),
y: ( viewRight.frame.height/2)/viewRight.frame.height)
viewRight.setAnchorPoint(anchorPointRight)
self.viewLeft.transform = CGAffineTransform.init(rotationAngle: rotateToAngel)
self.viewRight.transform = CGAffineTransform.init(rotationAngle: -rotateToAngel)
}
Existing CGAffineTransform performs this job well, But it only rotates in its center points. I want to rotate the UIView in its Top-Left coordinates,
Rotation Code:
// here rectangle is UIView
rectangle.transform = CGAffineTransform(rotationAngle: 90.degreesToRadians)
Extension:
// use to convert degree to radian
extension BinaryInteger {
var degreesToRadians: CGFloat { return CGFloat(Int(self)) * .pi / 180 }
}
I cant find my answer in suggested question.
Just change anchorPoint of layer before transformation. For example:
rectangle.layer.anchorPoint = CGPoint.zero
rectangle.transform = CGAffineTransform(rotationAngle: 90.degreesToRadians)
Thanks Here Trix and the4kman for the idea. I solved my problem. Here Trix answer not fully helped me. But He shows some hints. Using that hints I solved my answer.
Step 1
Create this extension for UIView to set anchorPoint
extension UIView{
func setAnchorPoint(anchorPoint: CGPoint) {
var newPoint = CGPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x: self.bounds.size.width * self.layer.anchorPoint.x, y: self.bounds.size.height * self.layer.anchorPoint.y)
newPoint = newPoint.applying(self.transform)
oldPoint = oldPoint.applying(self.transform)
var position : CGPoint = self.layer.position
position.x -= oldPoint.x
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
self.layer.position = position;
self.layer.anchorPoint = anchorPoint;
}
}
Usage
rectangle.setAnchorPoint(anchorPoint: CGPoint.zero)
rectangle.transform = CGAffineTransform(rotationAngle: 90.degreesToRadians)
This working perfectly.
I have a CGRect which I have rotated using translation and rotation function as below:-
let ctx: CGContext = UIGraphicsGetCurrentContext()!
ctx.saveGState()
let halfWidth = (selectedCropRect?.size.width)! / 2.0
let halfHeight = (selectedCropRect?.size.height)! / 2.0
let center = CGPoint(x: (selectedCropRect?.origin.x)! + halfWidth, y: (selectedCropRect?.origin.y)! + halfHeight)
// Move to the center of the rectangle:
ctx.translateBy(x: center.x, y: center.y)
// Rotate:
ctx.rotate(by: rotationAngle!);
// Draw the rectangle centered about the center:
let rect = CGRect(x: -halfWidth, y: -halfHeight, width: (selectedCropRect?.size.width)!, height: (selectedCropRect?.size.height)!)
let path = UIBezierPath(rect: rect).cgPath
ctx.addPath(path)
ctx.restoreGState()
Now the problem is i need to get all the corner points of the rotated rect and place circular views on the corner points so that the user can drag the corner points and increase/decrease size of rect. Any idea how i can place the circular views on the 4 corner points of rotated rect?
Untested but this should help point you in the right direction
guard let rect = selectedCropRect else {
return
}
let rectCenter = CGPoint(x: rect.width/2, y: rect.height/2)
// Or whatever angle you supply
let rotAngle = CGFloat.pi/4.0
// Rotate around center of rect
/*
Instead of creating a new transform you should store a transform
inside of the object you are transforming like UIView does
*/
var transform = CGAffineTransform(translationX: rectCenter.x, y: rectCenter.y)
transform = transform.rotated(by: rotAngle)
transform = transform.translatedBy(x: -rectCenter.x, y: -rectCenter.y)
// Get transformed center
let originalCenter = rectCenter.applying(transform)
var topLeft = originalCenter
topLeft.x -= rectCenter.x
topLeft.y -= rectCenter.y
topLeft = topLeft.applying(transform)
var topRight = originalCenter
topRight.x += rectCenter.x
topRight.y -= rectCenter.y
topRight = topRight.applying(transform)
var botLeft = originalCenter
botLeft.x -= rectCenter.x
botLeft.y += rectCenter.y
botLeft = botLeft.applying(transform)
var botRight = originalCenter
botRight.x += rectCenter.x
botRight.y += rectCenter.y
botRight = botRight.applying(transform)
// Create new path using the orignal supplied rect and transform
let path = CGPath(rect: rect, transform: &transform)
let bez = UIBezierPath(cgPath: path)
// Do whatever you need with the context
I have a UIView which covers another UIView. When the covering view is tapped, I want it to “fall”, this is where we animate it around its x axis about the bottom centre point.
I’ve implemented this like so:
private func showCategories(animated: Bool)
{
UIView.animateWithDuration(2,
delay: 0,
options: [.CurveEaseIn],
animations: {
self.setAnchorPoint(CGPoint(x: 0.5, y: 1), forView: self.titleView)
var rotationAndPerspectiveTransform = CATransform3DIdentity
rotationAndPerspectiveTransform.m34 = 1 / -500
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, CGFloat(-179 * M_PI / 180), 1, 0, 0);
self.titleView.layer.transform = rotationAndPerspectiveTransform;
}) { (success) -> Void in
//
}
}
I found a solution which said that changing the anchor point moves the view, so to fix it you need to move it taking in to consider the anchor point and transform. It provided this code:
private func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView)
{
var newPoint = CGPoint(x: view.bounds.width * anchorPoint.x, y: view.bounds.height * anchorPoint.y)
var oldPoint = CGPoint(x: view.bounds.width * view.layer.anchorPoint.x, y: view.bounds.height * view.layer.anchorPoint.y)
newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);
var position = view.layer.position;
position.x -= oldPoint.x;
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
view.layer.position = position;
view.layer.anchorPoint = anchorPoint;
}
However, this still doesn’t work for me. Am I using it wrong?
P.s, I realise the animated parameter isn’t being taken in to account yet… It’s for debugging purposes.
Okay, so moving the setAnchor:forView: method to before the animation block works.
My bad.