I am trying to make sidebar menu like in app Euro Sport! When the menu slides from left , the sourceviewcontroller slide to left and becomes smaller.
var percentWidthOfContainer = containerView.frame.width * 0.2 // this is 20 percent of width
var widthOfMenu = containerView.frame.width - percentWidthOfContainer
bottomView.transform = self.offStage(widthOfMenu)
bottomView.frame.origin.y = 60
bottomView.frame.size = CGSizeMake(widthOfMenu, 400)
bottomView.updateConstraints()
menucontroller.view.frame.size = CGSizeMake(widthOfMenu, containerView.frame.height)
menucontroller.updateViewConstraints()
Here, the bottom view is sourceviewcontroller.view. So, the question is how to scale bottom view. In my case , i can change the size but everything inside view is still in the same size.
You can use CGAffineTransformScale to scale instance of UIView
For Instance
Suppose you have an instance of UIView as
UIView *view;
// lets say you have instantiated and customized your view
..
..
// Keep the original transform of the view in a variable as
CGAffineTransform viewsOriginalTransform = view.transform;
// to scale down the view use CGAffineTransformScale
view.transform = CGAffineTransformScale(viewsOriginalTransform, 0.5, 0.5);
// again to scale up the view
view.transform = CGAffineTransformScale(viewsOriginalTransform, 1.0, 1.0);
As per Apple doc's
The CGAffineTransform data structure represents a matrix used for
affine transformations. A transformation specifies how points in one
coordinate system map to points in another coordinate system. An
affine transformation is a special type of mapping that preserves
parallel lines in a path but does not necessarily preserve lengths or
angles. Scaling, rotation, and translation are the most commonly used
manipulations supported by affine transforms, but skewing is also
possible.
So your solution to minimize the size of bottomView :-
bottomView.transform = CGAffineTransformMakeScale(0.2, 0.2) // you can change it as per your requirement
If you want to resize it or maximize it to its original size:
bottomView.transform = CGAffineTransformMakeScale(1.0, 1.0)
Just in case you want to expand the bottom view more than its size:-
bottomView.transform = CGAffineTransformMakeScale(1.3, 1.3) // you can change it as per your requirement
Related
I am expanding/reducing an image perfectly using CGAffineTransform, BUT it changes around the centre points of the image. I want to keep it fixed/anchored to the baseline? Is this possible? The image is displayed in a UIImageView.
UIView.animate(withDuration: 0.0, animations: {
// Sunrise
let multiplierSunrise = CGFloat(self.sunriseTime/self.sunriseMax)
var transformSunrise = CGAffineTransform.identity
transformSunrise = transformSunrise.translatedBy(x: 0, y: (self.imageHeight*(1-(multiplierSunrise/2))-(self.imageHeight/2)))
transformSunrise = transformSunrise.scaledBy(x: 1, y: 1*multiplierSunrise )
self.sunriseView.transform = transformSunrise
})
Transforms are applied around the view's layer's anchor point. This is the layer's bounds center by default, but you can move it, describing the desired anchor point in percentage terms. So if you want the transform to be applied around the bottom of the view, you could say:
self.sunriseView.layer.anchorPoint = CGPoint(x:0.5, y:1)
However, when you say that, the view itself will move! To prevent that, also move the view's center to compensate:
self.sunriseView.center.y += self.sunriseView.bounds.height/2
self.sunriseView.layer.anchorPoint = CGPoint(x:0.5, y:1)
Here's an example where, having done that, I proceed to apply a y-axis scale transform. As you can see, we are holding the bottom steady:
I have a SKSpriteNode create with the level generator.
I need to create exactly the same shape using CGPath.
self.firstSquare = childNodeWithName("square") as! SKSpriteNode
var transform = CGAffineTransformMakeRotation(self.firstSquare.zRotation)
let rect = CGRect(origin: self.firstSquare.position, size: self.firstSquare.size)
let firstSquareCGPath:CGPath=CGPathCreateWithRect(rect, &transform)
print(self.firstSquare.position)
=> firstSquare position (52.8359451293945, -52.9375076293945)
To check if my CGPath has been created as I want, I created a SKShapeNode with my CGPath:
let shape:SKShapeNode=SKShapeNode(path:path)
shape.fillColor = self.getRandomColor()
addChild(shape)
print(shape.position)
=> shape position (52.8359451293945, -52.9375076293945)
The result is not what I expected.
So I don't know if my CGPath is wrong, or if it's when I convert it in SKShapeNode that I lose the initial sprite properties.
To understand why I need to do that, please read this stack
EDIT 1,2
I added:
shape.position = self.firstSquare.position
And I obtained:
EDIT 3 :
I updated my explanations above, the anchor point of my firstSquare is now (0.5, 0.5)
Usually I don't use .SKS files, I preefer to write only code, but here seems you simply have two squares , one with the original .sks position, the other added in second time without any position.
If you want to overlay your first square simply:
shape.position = self.firstSquare.position
EDIT:
I also seen your anchorPoint was setted to (0.0,0.0) but the default anchor point is (0.5,0.5). Try to correct also this parameter to match with your sks.
You .sks size layout could be not the same as scene, so:
scene.size = skView.bounds.size
Please take a look also to this parameter to change before you present your scene:
scene.scaleMode = .ResizeFill
In fact, the scale mode affects also to the positioning.
What is scaleMode?
The scaleMode of a scene determines how the scene will be updated to fill the view when the iOS device is rotated. There are four different scaleModes defined:
SKSceneScaleModeFill – the x and y axis are each scaled up or down so that the scene fills the view. The content of the scene will not maintain its current aspect ratio. This is the default if you do not specify a scaleMode for your scene.
SKSceneScaleModeAspectFill – both the x and y scale factors are calculated. The larger scale factor will be used. This ensures that the view will be filled, but will usually result in parts of the scene being cropped. Images in the scene will usually be scaled up but will maintain the correct aspect ratio.
SKSceneScaleModeAspectFit – both the x and y scale factors are calculated. The smaller scale factor will be used. This ensures that the entire scene will be visible, but will usual result in the scene being letterboxed. Images in the scene will usually be scaled down but will maintain the correct aspect ratio.
SKSceneScaleModeResizeFill – The scene is not scaled. It is simply resized so that its fits the view. Because the scene is not scaled, the images will all remain at their original size and aspect ratio. The content will all remain relative to the scene origin (lower left).
I found a solution, I need to use SKShapeNode(path:, centered:), then apply the transformation and set the position.
self.firstSquare = childNodeWithName("square") as! SKSpriteNode
let firstSquareCGPath:CGPath=CGPath(rect: CGRect(origin: self.firstSquare.position, size: self.firstSquare.size), transform: nil)
let shape:SKShapeNode=SKShapeNode(path: firstSquareCGPath, centered: true) //important
shape.zRotation = self.firstSquare.zRotation
shape.position = self.firstSquare.position
shape.fillColor = SKColor.blue()
addChild(shape)
It works perfectly!
Just a pending question, as I set the transformation and the position in last (because of the SKShapeNode properties), I don't know if my CGPath above is set correctly (like my SKSpriteNode).
Before writing this question, I've
had experience with Affine transforms for views
read the Transforms documentation in the Quartz 2D Programming Guide
seen this detailed CALayer tutorial
downloaded and run the LayerPlayer project from Github
However, I'm still having trouble understanding how to do basic transforms on a layer. Finding explanations and simple examples for translate, rotate and scale has been difficult.
Today I finally decided to sit down, make a test project, and figure them out. My answer is below.
Notes:
I only do Swift, but if someone else wants to add the Objective-C code, be my guest.
At this point I am only concerned with understanding 2D transforms.
Basics
There are a number of different transforms you can do on a layer, but the basic ones are
translate (move)
scale
rotate
To do transforms on a CALayer, you set the layer's transform property to a CATransform3D type. For example, to translate a layer, you would do something like this:
myLayer.transform = CATransform3DMakeTranslation(20, 30, 0)
The word Make is used in the name for creating the initial transform: CATransform3DMakeTranslation. Subsequent transforms that are applied omit the Make. See, for example, this rotation followed by a translation:
let rotation = CATransform3DMakeRotation(CGFloat.pi * 30.0 / 180.0, 20, 20, 0)
myLayer.transform = CATransform3DTranslate(rotation, 20, 30, 0)
Now that we have the basis of how to make a transform, let's look at some examples of how to do each one. First, though, I'll show how I set up the project in case you want to play around with it, too.
Setup
For the following examples I set up a Single View Application and added a UIView with a light blue background to the storyboard. I hooked up the view to the view controller with the following code:
import UIKit
class ViewController: UIViewController {
var myLayer = CATextLayer()
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// setup the sublayer
addSubLayer()
// do the transform
transformExample()
}
func addSubLayer() {
myLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
myLayer.backgroundColor = UIColor.blue.cgColor
myLayer.string = "Hello"
myView.layer.addSublayer(myLayer)
}
//******** Replace this function with the examples below ********
func transformExample() {
// add transform code here ...
}
}
There are many different kinds of CALayer, but I chose to use CATextLayer so that the transforms will be more clear visually.
Translate
The translation transform moves the layer. The basic syntax is
CATransform3DMakeTranslation(_ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat)
where tx is the change in the x coordinates, ty is the change in y, and tz is the change in z.
Example
In iOS the origin of the coordinate system is in the top left, so if we wanted to move the layer 90 points to the right and 50 points down, we would do the following:
myLayer.transform = CATransform3DMakeTranslation(90, 50, 0)
Notes
Remember that you can paste this into the transformExample() method in the project code above.
Since we are just going to deal with two dimensions here, tz is set to 0.
The red line in the image above goes from the center of the original location to the center of the new location. That's because transforms are done in relation to the anchor point and the anchor point by default is in the center of the layer.
Scale
The scale transform stretches or squishes the layer. The basic syntax is
CATransform3DMakeScale(_ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat)
where sx, sy, and sz are the numbers by which to scale (multiply) the x, y, and z coordinates respectively.
Example
If we wanted to half the width and triple the height, we would do the following
myLayer.transform = CATransform3DMakeScale(0.5, 3.0, 1.0)
Notes
Since we are only working in two dimensions, we just multiply the z coordinates by 1.0 to leave them unaffected.
The red dot in the image above represents the anchor point. Notice how the scaling is done in relation to the anchor point. That is, everything is either stretched toward or away from the anchor point.
Rotate
The rotation transform rotates the layer around the anchor point (the center of the layer by default). The basic syntax is
CATransform3DMakeRotation(_ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat)
where angle is the angle in radians that the layer should be rotated and x, y, and z are the axes about which to rotate. Setting an axis to 0 cancels a rotation around that particular axis.
Example
If we wanted to rotate a layer clockwise 30 degrees, we would do the following:
let degrees = 30.0
let radians = CGFloat(degrees * Double.pi / 180)
myLayer.transform = CATransform3DMakeRotation(radians, 0.0, 0.0, 1.0)
Notes
Since we are working in two dimentions, we only want the xy plane to be rotated around the z axis. Thus we set x and y to 0.0 and set z to 1.0.
This rotated the layer in a clockwise direction. We could have rotated counterclockwise by setting z to -1.0.
The red dot shows where the anchor point is. The rotation is done around the anchor point.
Multiple transforms
In order to combine multiple transforms we could use concatination like this
CATransform3DConcat(_ a: CATransform3D, _ b: CATransform3D)
However, we will just do one after another. The first transform will use the Make in its name. The following transforms will not use Make, but they will take the previous transform as a parameter.
Example
This time we combine all three of the previous transforms.
let degrees = 30.0
let radians = CGFloat(degrees * Double.pi / 180)
// translate
var transform = CATransform3DMakeTranslation(90, 50, 0)
// rotate
transform = CATransform3DRotate(transform, radians, 0.0, 0.0, 1.0)
// scale
transform = CATransform3DScale(transform, 0.5, 3.0, 1.0)
// apply the transforms
myLayer.transform = transform
Notes
The order that the transforms are done in matters.
Everything was done in relation to the anchor point (red dot).
A Note about Anchor Point and Position
We did all our transforms above without changing the anchor point. Sometimes it is necessary to change it, though, like if you want to rotate around some other point besides the center. However, this can be a little tricky.
The anchor point and position are both at the same place. The anchor point is expressed as a unit of the layer's coordinate system (default is 0.5, 0.5) and the position is expressed in the superlayer's coordinate system. They can be set like this
myLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
myLayer.position = CGPoint(x: 50, y: 50)
If you only set the anchor point without changing the position, then the frame changes so that the position will be in the right spot. Or more precisely, the frame is recalculated based on the new anchor point and old position. This usually gives unexpected results. The following two articles have an excellent discussion of this.
About the anchorPoint
Translate rotate translate?
See also
Border, rounded corners, and shadow on a CALayer
Using a border with a Bezier path for a layer
I have a UIView called container that I want to move (offset) using affine transfrom. This view contains UIImageView and is a subview of UICollectionViewCell.
So it should be simple:
container.transform = CGAffineTransformMakeTranslation(100, 200) //render container 100 points right and 200 points down
Instead it is very hard, because theat code does not do anything. The view is rendered excatly on the same place as if I delete that line. So I added 'print' to verify what affine translation was set:
container.transform = CGAffineTransformMakeTranslation(100, 200)
print(container.transform) //prints: CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 100.0, ty: 200.0)
That seems all right. So I tried rotating the container view instead with CGAffineTransformMakeRotation and it rotates the view just not around its center as it should according to documentation. I tried different combinations of translate, rotation and scale transforms just to find that the affine transformation matrixes set are OK, but attributes tx and ty seems to be ignored and a, b, c and d seems to be using different anchor point then the centre of the view (cannot say what that point is).
Any ideas on what can be causing this and how to fix it?
There must be something like auto layout messing things up for you. In the absence of outside influence, setting a view's affine transform to CGAffineTransformMakeTranslation(100, 200) will shift it right 100 points and down 200. I verified this by making a new Single View Project in Xcode and changing the viewDidLoad method in the ViewController.swift class to:
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.blueColor();
let container = UIView(frame: CGRectMake(0,0,100,100));
container.backgroundColor = UIColor.greenColor();
container.transform = CGAffineTransformMakeTranslation(100, 200);
view.addSubview(container);
}
As expected this makes the green container view appear 100 points to the right and 200 points down, even though its frame is (0,0,100,100).
So please check for auto layout and other such things that might influence the placement of this view, and if you can't find anything please post more code. Also, if your container view doesn't have a background color, please give it one so that you can see its position directly, instead of deducing its position by looking at the image view.
n.b. Setting a view's transform doesn't actually move the view itself, it just changes how/where it draws its content.
I have a view that I draw using Core Graphics, which in this example is a segmented circle. The user can touch the circle to create a point along its circumference; this creates a subview on the UIView that contains the circle graphic.
Then I've implemented a pinch-zoom gesture which causes the circle to redraw to its new size. I've seen most implementations of pinch zoom use transform properties, but I've chosen to redraw because it's all vectors and gives a clean result.
My problem is repositioning the point views. I calculate the required position of those points based on the scale of the parent view: as it changes I update the x/y coords of the point views. However, it seems there are some precision issues: as the circle shape size increases, the points drift so they aren't right on the line anymore. Here's a couple examples:
This is where the circle is at 100% scale. Note the perfect positioning of that black point. But when you zoom in...
The point drifts off-line.
And here's some code. I derive the new size of the circle from the pinch gesture's scale (I modify if a bit to constrain and slow it down for UI purposes, so that's deltaScale) and then draw it like so:
let currentSize = self.shape!.bounds.size
let newSize = CGSize(width: self.originalSize.width * deltaScale, height: self.originalSize.height * deltaScale)
self.shape?.frame.size = newSize
self.shape?.center = self.originalCentre!
self.shape?.shapeSize = newSize
self.shape?.setNeedsDisplay()
As the pinch-zoom gesture completes, I calculate the factor:
let xScale = Double(newSize.width) / Double(currentSize.width)
let yScale = Double(newSize.height) / Double(currentSize.height)
self.points = self.points.map{(thisPoint) -> UIView in
thisPoint.center = CGPoint(x: Double(thisPoint.center.x) * xScale, y: Double(thisPoint.center.y) * yScale)
return thisPoint
}
(I was using CGFloats, but switched to Doubles in the hope that it would give me the precision I needed. Alas.)
You're accumulating roundoff errors. This is getting executed repeatedly:
thisPoint.center = CGPoint(x: Double(thisPoint.center.x) * xScale, y: Double(thisPoint.center.y) * yScale)
Repeating any calculation of the form 'x=f(x)' with anything less than unlimited precision will result in drift.
Trick is to not have 'thisPoint.center' on both sides of the equal sign. Best way to do that is to have thisPoint.center be a pure function of some other state. Commenter suggested storing desired angle, that would work well. Then you could do:
thisPoint.center = f(thisPoint.someRadians), where 'f' converts from polar to rectangular coordinates, factoring in the scale of the circle.