Facing Issue with CGAffineTransform in NIB - ios

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)
}

Related

How to scale view from top right corner

I'm changing anchor point by methods in this topic:
Changing my CALayer's anchorPoint moves the view but it's not working for me
What I see
The view is misplaced
What I Expect
The grey rectangle must expand from 1.0 in x and 0.0 in y without misplacement
My code:
private func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
let oldOrigin = view.frame.origin
view.layer.anchorPoint = anchorPoint
let newOrigin = view.frame.origin
let translation = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)
view.center = CGPoint(x: view.center.x - translation.x, y: view.center.y - translation.y)
}
setAnchorPoint(anchorPoint: CGPoint(x: 1.0, y: 0.0), view: self.actionLayer)
What I've tried
Recording in a variable the frame before changing the anchor point and setting again after, same for layer position or view center. Not working.

Resizing UIView using CGAffineTransform scale doesn't update frame

I'm trying to resize a UIView (Parent) with a few subviews in it using CGAffineTransform scale.
I'm resizing the parent by dragging it from one corner using pan gesture.
The resizing works as expected but if I try to resize it again, it jumps back to the initial frame. It's like it never knew it was resized.
These are the steps I am doing so far:
1.- Just when the pan gesture begins I get the initial frame and the touch location in superview:
if gesture.state == .began {
//We get all initial values from the first touch
initialFrame = self.frame;
touchStart = gesture.location(in: superview)
}
2.- Then I go to the handle I'm dragging (Top right in this case), set the anchor point, calculate deltas (Initial touch - gesture distance traveled), calculate new frame, scales, and apply transform.
case topRight:
if gesture.state == .began {
self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 1))
}
let deltaX = -1 * (touchStart.x - gesture.location(in: superview).x)
let deltaY = 1 * (touchStart.y - gesture.location(in: superview).y)
let newWidth = initialFrame.width + deltaX;
let newHeight = initialFrame.height + deltaY;
let scaleX:CGFloat = newWidth / initialFrame.width;
let scaleY:CGFloat = newHeight / initialFrame.height;
self.transform = CGAffineTransform.identity.scaledBy(x: scaleX, y: scaleY)
3.- Finally I reset the anchor point to the middle of the UIView.
if gesture.state == .ended {
self.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
}
I attached a gif where you can see the UIView is resized from the top right handle. When I try to resize it again, it jumps back to the initial frame. (It seems that the video is restarted, but this is the jump)
what am I missing? do I need to update something else?
Thank you all!
So the answer is actually quite simple, this following line of code was incorrect:
self.transform = CGAffineTransform.identity.scaledBy(x: scaleX, y: scaleY)
Here I am applying the scaling to the identity transform which is always the original size.
Just by changing this to:
self.transform = self.initialTransform.scaledBy(x: scaleX, y: scaleY)
Now I am applying the scaling to the current transform which is always saved when the gesture begins.
#objc func handleUserPan(gesture:UIPanGestureRecognizer) {
if gesture.state == .began {
self.initialTransform = self.transform;
self.touchStart = gesture.location(in: resizableSelf.superview);
}
switch gesture.view! {
case self.topRight: //This is just the circle view
if gesture.state == .began {
self..setAnchorPoint(anchorPoint: CGPoint(x: 1, y: 1))
}
let deltaX = 1 * (self.touchStart.x - gesture.location(in: superview).x)
let deltaY = 1 * (self.touchStart.y - gesture.location(in: superview).y)
let newWidth = self.initialFrame.width + deltaX;
let newHeight = self.initialFrame.height + deltaY;
let scaleX:CGFloat = newWidth / self.initialFrame.width;
let scaleY:CGFloat = newHeight / self.initialFrame.height;
self.transform = self.initialTransform.scaledBy(x: scaleX, y: scaleY)
}
default:()
}
gesture.setTranslation(CGPoint.zero, in: self)
self.updateDragHandles()
if gesture.state == .ended {
self.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
}
}
Hopefully this can be helpful for those who would like to scale a UIView similar to like Photoshop does with it's layers. It even works if the subviews were rotated.
Keep in mind that once the view is transformed, you cannot set the frame value anymore. That's why by having all views inside a superView is more convenient so it takes care of all resizing.
The goal was to replicate the same behavior that Photoshop does when resizing layers.

Rotate a view from its origin towards another view

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.

How to rotate UIVIew in its top left coordinates?

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.

UIView layer moves when rotating about non-centre anchor point

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.

Resources