update point after transform - ios

I'm trying to change a view position using CGAffineTransform but the transform statement doesn't affect it.
For example, let's call the view that moves movingView:
At the start, movingView is at point CGPoint(x: 100, y: 100)
then I'm moving movingView by this code :
movingView.transform = CGAffineTransform(translationX: 100, y: 300)
now if I do : print(movingView.layer.position), I still get
CGPoint(x: 100, y:100)
instead of
CGPoint(x: 200, y: 400)

Applying a transform to a view (or layer) doesn't update its center location (or position). It changes how and where the view (or layer) appear on screen but not the values of its center, (or position), or bounds properties.
If you want to know what how the point is affected by a translation, then you can compute a new point by applying the transform to it:
let point = CGPoint(x: 100, y: 200)
let transform = CGAffineTransform(translationX: 20, y: 40)
let transformedPoint = point.applying(transform) // x: 120, y: 240

Actually What changes is the frame of View if the view is added to a SuperView.
Check : movingView.frame.origin.
The transform of CALayer of UIView has already changed:
CGPoint.init(x: movingView.layer.transform.m41, y: movingView.layer.transform.m42)
So the transform of view is based on transform of CALayer.

Related

How to rotate a CGRect about its centre?

Background:
I want to show a label that is rotated 90 degrees clockwise. The rotated label should be in a certain frame that I specify. Let's say (10, 100, 50, 100).
I know that I can rotate a view by using CGAffineTransform. I also know that I should not set the frame property after a transformation is done, according to the docs.
When the value of this property is anything other than the identity transform, the value in the frame property is undefined and should be ignored.
I tried to set the frame after the transform and although it worked, I don't want to do something that the docs told me not to.
label2.transform = CGAffineTransform(rotationAngle: -.pi / 2)
label2.frame = CGRect(x: 10, y: 100, width: 50, height: 100)
Then I thought, I could do this:
create a CGRect of the frame that I want
rotate that rect 90 degrees anticlockwise
set that rect as the frame of the label
rotate the label 90 degrees clockwise
And then the label would be in the desired frame.
So I tried something like this:
let desiredFrame = CGRect(x: 10, y: 100, width: 50, height: 100) // step 1
// I am using UIViews here because I am just looking at their frames
// whether it is a UIView or UILabel does not matter
// label1 is a view that is in the desired frame but not rotated
// If label2 has the correct frame, it should completely cover label1
let label1 = UIView(frame: desiredFrame)
let label2Frame = rotateRect(label1.frame) // step 2
let label2 = UIView(frame: label2Frame) // step 3
view.addSubview(label1)
view.addSubview(label2)
label1.backgroundColor = .red
label2.backgroundColor = .blue
label2.transform = CGAffineTransform(rotationAngle: -.pi / 2) // step 4
Where rotateRect is declared like this:
func rotateRect(_ rect: CGRect) -> CGRect {
return rect.applying(CGAffineTransform(rotationAngle: .pi / 2))
}
This didn't work. label2 does not overlap label1 at all. I can't even see label2 at all on the screen.
I suspect that this is because the applying method in CGRect rotates the rect about the origin, instead of the centre of the rect. So I tried to first transform the rect to the origin, rotate it, then transform it back, as this post on math.SE said:
func rotateRect(_ rect: CGRect) -> CGRect {
let x = rect.x
let y = rect.y
let transform = CGAffineTransform(translationX: -x, y: -y)
.rotated(by: .pi / 2)
.translatedBy(x: x, y: y)
return rect.applying(transform)
}
However, I still cannot see label2 on the screen.
I think that the order of your transformations are incorrect. If you do it so, that,
Translate(x, y) * Rotate(θ) * Translate(-x, -y)
And using that with your rotateRect seem to work correctly. Since, you are rotating the view by 90 degrees, the blue view completely block red view. Try it out with some other angle and you shall see effect more prominently.
func rotateRect(_ rect: CGRect) -> CGRect {
let x = rect.midX
let y = rect.midY
let transform = CGAffineTransform(translationX: x, y: y)
.rotated(by: .pi / 2)
.translatedBy(x: -x, y: -y)
return rect.applying(transform)
}
Since I only want to rotate the view by 90 degrees, the width and height of the initial frame will be the reverse of the width and height of the rotated frame.
The centers of the initial frame and rotated are the same, so we can set the center of the label to the centre of the desired frame before the transformation.
let desiredFrame = CGRect(x: 10, y: 100, width: 50, height: 100)
let label1 = UIView(frame: desiredFrame)
// get the centre of the desired frame
// this is also equal to label1.center
let center = CGPoint(x: desiredFrame.midX, y: desiredFrame.midY)
let label2Frame = CGRect(x: 0, y: 0, width: desiredFrame.height, height: desiredFrame.width)
let label2 = UIView(frame: label2Frame)
view.addSubview(label1)
view.addSubview(label2)
label1.backgroundColor = .red
label2.backgroundColor = .blue
label2.center = center // set the centre of label2 before the transform
label2.transform = CGAffineTransform(rotationAngle: -.pi / 2)

Drawing rectangles using the CGPoint coordinate system - SWIFT

I am able to draw a rectangle using the code below (works). However, I am using the Vison framework to detect rectangles but it is giving me back CGPoint values that is less than 1.0. When I enter these coordinates to draw a rectangle I get nothing back. Please can some advise?
Works - i get a rectangle
let rectangle = UIBezierPath.init()
rectangle.move(to: CGPoint.init(x: 100, y: 100))
rectangle.addLine(to: CGPoint.init(x: 200, y: 130))
rectangle.addLine(to: CGPoint.init(x: 300, y: 400))
rectangle.addLine(to: CGPoint.init(x: 100, y: 500))
rectangle.close()
let rec = CAShapeLayer.init()
rec.path = rectangle.cgPath
rec.fillColor = UIColor.red.cgColor
self.view.layer.addSublayer(rec)
Does not work (no rectangle):
let rectangle = UIBezierPath.init()
rectangle.move(to: CGPoint.init(x: 0.154599294066429, y: 0.904223263263702))
rectangle.addLine(to: CGPoint.init(x: 0.8810795545578, y: 0.970198452472687))
rectangle.addLine(to: CGPoint.init(x: 0.16680309176445, y: 0.0157230049371719))
rectangle.addLine(to: CGPoint.init(x: 0.878569722175598, y: 0.128135353326797))
rectangle.close()
let rec = CAShapeLayer.init()
rec.path = rectangle.cgPath
rec.fillColor = UIColor.red.cgColor
self.view.layer.addSublayer(rec)
The points returned by the Vision framework are coordinates represented by a percentage from the full size of the viewport. If your viewport is 640 x 480 (for example) then your first point is CGPoint(x: 0.154599294066429 * 640, y: 0.904223263263702 * 480). Taking that into account change your code to something like this and it should be fine:
let rectangle = UIBezierPath.init()
let width = // your width - Maybe UIScreen.main.bounds.size.width ?
let height = // your height - Maybe UIScreen.main.bounds.size.height ?
rectangle.move(to: CGPoint.init(x: width * 0.154599294066429, y: height * 0.904223263263702))
rectangle.addLine(to: CGPoint.init(x: width * 0.8810795545578, y: height * 0.970198452472687))
rectangle.addLine(to: CGPoint.init(x: width * 0.16680309176445, y: height * 0.0157230049371719))
rectangle.addLine(to: CGPoint.init(x: width * 0.878569722175598, y: height * 0.128135353326797))
rectangle.close()
let rec = CAShapeLayer.init()
rec.path = rectangle.cgPath
rec.fillColor = UIColor.red.cgColor
self.view.layer.addSublayer(rec)
Let's just analyse what it is you've got here. In the first example, you're drawing a rectangle of size of roughly 200px by 400px, ish... Of course, this will display exactly as you expect it to.
In the second example, you're drawing a rectangle of roughly 0.7px by 0.8px, ish. Now think about this logically, how is the screen supposed to represent 0.7 of a pixel? It can't! A pixel is the smallest representation you can have, that is, a single coloured square.
The code doesn't work because of physical constraints of the screen/system. You need to be using size (and position) values greater than 1 to see the rectangle. The Vision framework is no different, it will only see what you can see, if that makes sense.

How to make a CGRect slashed at bottom?

I'm trying to implement the following:
A custom NavigationBar which is slashed at the bottom, how can I do this using CGRect?
Another alternative would be to make a image of it and push the list up to overlap the transparent part in the image. I'd prefer the first solution if possible tho. Any suggestions?
This is my current WIP state:
You can use Bezier path
Just add lines and make your custom shape
like this:
let path = UIBezierPath()
shape.move(to: CGPoint(x: 0, y: 0))
shape.addLine(to: CGPoint(x: 0, y: 320))
shape.addLine(to: CGPoint(x: 94, y: 320))
shape.addLine(to: CGPoint(x: 64, y: 0))
shape.close()

Playground code: AffineTransform and AnchorPoint don't work as I want

I want to shrink a triangle downward as below:
But the triangle shrink upward as below:
The transform code is below:
layer.setAffineTransform(CGAffineTransformMakeScale(1.0, 0.5))
I tried to use anchorPoint but I can't scceed.
//: Playground - noun: a place where people can play
import UIKit
let view = UIView(frame: CGRectMake(0, 0, 200, 150))
let layer = CAShapeLayer()
let triangle = UIBezierPath()
triangle.moveToPoint (CGPointMake( 50, 150))
triangle.addLineToPoint(CGPointMake(100, 50))
triangle.addLineToPoint(CGPointMake(150, 150))
triangle.closePath()
layer.path = triangle.CGPath
layer.strokeColor = UIColor.blueColor().CGColor
layer.lineWidth = 3.0
view.layer.addSublayer(layer)
view
layer.setAffineTransform(CGAffineTransformMakeScale(1.0, 0.5))
view
Anish gave me a advice but doesn't work well:
layer.setAffineTransform(CGAffineTransformMakeScale(2.0, 0.5))
Transforms operate around the layer's anchorPoint, which by default is at the center of the layer. Thus, you shrink by collapsing inwards, not by collapsing downwards.
If you want to shrink downward, you will need to scale down and change the view's position (or use a translate transform in addition to your scale transform), or change the layer's anchorPoint before performing the transform.
Another serious problem with your code is that your layer has no assigned size. This causes the results of your transform to be very misleading.
I ran this version of your code and got exactly the results you are asking for. I have put a star next to the key lines that I added. (Note that this is Swift 3; you really need to step up to the plate and update here.)
let view = UIView(frame:CGRect(x: 0, y: 0, width: 200, height: 150))
let layer = CAShapeLayer()
layer.frame = view.layer.bounds // *
let triangle = UIBezierPath()
triangle.move(to: CGPoint(x: 50, y: 150))
triangle.addLine(to: CGPoint(x: 100, y: 50))
triangle.addLine(to: CGPoint(x: 150, y: 150))
triangle.close()
layer.path = triangle.cgPath
layer.strokeColor = UIColor.blue.cgColor
layer.lineWidth = 3
view.layer.addSublayer(layer)
view
layer.anchorPoint = CGPoint(x: 0.5, y: 0) // *
layer.setAffineTransform(CGAffineTransform(scaleX: 1, y: 0.5))
view
Before:
After:

Swift: position an SKshapeNode to the far left/right but not offscreen

Im trying to get two SKshapeNodes to be positioned on the far left and far right of the screen without any part of the nodes offscreen like this http://i.stack.imgur.com/9rhAH.png. I don't want one node to be positioned according to the other node though.
Ive been trying to use minX and maxX and use the width of the screen and SKshapeNodes and I can't figure it out.
This is what I have so far
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let firstShapeNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 50, height: 50))
firstShapeNode.position = CGPoint(x: 0, y: self.frame.height / 2)
firstShapeNode.fillColor = SKColor.purpleColor()
self.addChild(firstShapeNode)
let secondShapeNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 50, height: 50))
secondShapeNode.position = CGPoint(x: 0, y: self.frame.height / 2)
secondShapeNode.fillColor = SKColor.cyanColor()
self.addChild(secondShapeNode)
}
Any suggestions?
To align the nodes to the left and right sides of the screen, respectively, try this:
firstShapeNode.position = CGPoint(x: firstShapeNode.size.width / 2, y: self.frame.height / 2)
secondShapeNode.position = CGPoint(x: self.frame.maxX - secondShapeNode.size.width / 2, y: self.frame.height / 2)
In SpriteKit, unlike UIKit, the default origin (0,0) point on the screen is in the bottom left corner. Additionally, by default, the position of an SKNode is the center of the node, which is why they need to be offset by half of their width.

Resources