UIView transform animation - scale and translate - ios

I have 4 category views (they are rotated initially by -M_PI/4), and I want to zoom in one of them (let's say view2) to be the size of whole this part. Which means I need animation of transform property, where I should use translate and scale.
Just scale works fine, and I used:
let zoomTransform = CGAffineTransformScale(newCategoryView.transform, 2.5, 2.5)
But, translate itself doesn't work, then I plan to use them both with CGAffineTransformConcat()
I tried to translate with this:
let translateTransform:CGAffineTransform = CGAffineTransformTranslate(newCategoryView.transform, newCategoryView.frame.origin.x - self.view.center.x, newCategoryView.frame.origin.y - self.view.center.y);
But somehow it does't respect old rotation and starts from weird position.

Related

Swift sprite kit vertical background infinite image

I have 3 images:
topBg.png
midBg.png
botBg.png
I want to set topBg.png at top scene and height = 200
middleBg.png should be infinite scale or repeat vertically
botBg.png - should be in bottom and height = 200
i have next code:
override func didMove(to view: SKView) {
self.bgTopSpriteNode = self.childNode(withName: "//bgTopNode") as? SKSpriteNode
self.bgMiddleSpriteNode = self.childNode(withName: "//bgMiddleNode") as? SKSpriteNode
self.bgBottomSpriteNode = self.childNode(withName: "//bgBottomNode") as? SKSpriteNode
if let bgTopSpriteNode = self.bgTopSpriteNode,
let bgMiddleSpriteNode = self.bgMiddleSpriteNode,
let bgBottomSpriteNode = self.bgBottomSpriteNode {
bgTopSpriteNode.size.width = self.frame.width
bgTopSpriteNode.size.height = 200
bgTopSpriteNode.position.x = 0
bgMiddleSpriteNode.size.width = self.frame.width
bgMiddleSpriteNode.size.height = self.frame.height-400
bgMiddleSpriteNode.position.x = 0
bgBottomSpriteNode.size.width = self.frame.width
bgBottomSpriteNode.size.height = 200
bgBottomSpriteNode.position.x = 0
}
}
But how to set Y position of images. Because coordinates begin from center of screen, not from left top and i don't know how to convert them.
There are a couple of different ways to achieve what you're looking to do.
First, you can compute the y position of the top and the bottom of the screen using simply size.height / 2 if you have the anchorPoint of your scene at (0.5,0.5). (Don't use frame - use size. That way, you take into account the scaleMode of the scene.)
It sounds like you are frustrated that the origin of the scene is in the center. If you'd like to move it to the corner, you can easily do so by setting the scene's anchorPoint property, say, to (0.0, 0.0) for the lower left corner. Then, your y-values are 0 and size.height. If you are using the .sks editor, this is exposed in the interface - you can just set it there. Otherwise, you can set it programmatically.
Finally, you can set the scaleMode of your scene to something like .aspectFill, set the size of the scene directly (say, to 1024x768 for an iPad), and just place the images wherever they need to go. This approach works particularly well with .sks files, if you are using them; when you load up a scene, you can set the size of the scene based on the aspect ratio of the view it's in to accommodate different aspect ratios. For instance, you could adopt a 320x480 "reference size" for your iPhone scenes. Whenever you load up the scene, you could set the size of the scene to be 320 points wide and however many points tall to match the aspect ratio of the device. Then, all your graphics would be produced at 320pt wide, and you could slide them up or down proportionally across the scene's size for layout. This is a little more complicated, but it's a lot easier than trying to deal with separate layout considerations for multiple devices.
I should also point out a couple of things.
You can use the anchorPoint property of a sprite to dictate where the sprite's coordinates are measured from. This is handy for cases where you want images to be flush up against something. For instance, if you want an image flush against the left side of the screen, set its position to be exactly the left side of the screen, and then set its anchorPoint.x to 0.0; this will put the left edge of the sprite against the left edge of the screen. This also works for scenes, as you encountered - moving the anchorPoint of the scene moves everything in the scene relative to its size.
You don't need three images for what you're describing. You can use a single sprite and just set its centerRect property to tell it to use the top and bottom of an image and stretch the center part vertically. You have to do a little math to set the right xScale and yScale (not width and height, IIRC), but then you can draw all of that with one sprite instead of three. This would be really handy in your case, because you could just leave the sprite at (0,0), set its scale to match the size of the entire scene, and set the centerRect property - you wouldn't have to do any positioning math at all.

Ios Swift Animate a view in non linear path

I am trying to animate a UIView through non linear path(i'm not trying to draw the path itself) like this :
The initial position of the view is determinated using a trailing and bottom constraint (viewBottomConstraint.constant == 100 & viewTrailingConstraint.constant == 300)
I am using UIView.animatedWithDuration like this :
viewTrailingConstraint.constant = 20
viewBottomConstraint.constant = 450
UIView.animateWithDuration(1.5,animation:{
self.view.layoutIfNeeded()
},completition:nil)
But it animate the view in a linear path.
You can use keyFrame animation with path
let keyFrameAnimation = CAKeyframeAnimation(keyPath:"position")
let mutablePath = CGPathCreateMutable()
CGPathMoveToPoint(mutablePath, nil,50,200)
CGPathAddQuadCurveToPoint(mutablePath, nil,150,100, 250, 200)
keyFrameAnimation.path = mutablePath
keyFrameAnimation.duration = 2.0
keyFrameAnimation.fillMode = kCAFillModeForwards
keyFrameAnimation.removedOnCompletion = false
self.label.layer.addAnimation(keyFrameAnimation, forKey: "animation")
Gif
About this function
void CGContextAddQuadCurveToPoint (
CGContextRef _Nullable c,
CGFloat cpx,
CGFloat cpy,
CGFloat x,
CGFloat y
);
(cpx,cpy) is control point,and (x,y) is end point
Leo's answer of using Core Animation and CAKeyframeAnimation is good, but it operates on the view's "presentation layer" and only creates the appearance of moving the view to a new location. You'll need to add extra code to actually move the view to it's final location after the animation completes. Plus Core Animation is complex and confusing.
I'd recommend using the UIView method
animateKeyframesWithDuration:delay:options:animations:completion:. You'd probably want to use the option value UIViewKeyframeAnimationOptionCalculationModeCubic, which causes the object to move along a curved path that passes through all of your points.
You call that on your view, and then in the animation block, you make multiple calls to addKeyframeWithRelativeStartTime:relativeDuration:animations: that move your view to points along your curve.
I have a sample project on github that shows this and other techniques. It's called KeyframeViewAnimations (link)
Edit:
(Note that UIView animations like animateKeyframes(withDuration:delay:options:animations:completion:) don't actually animate your views along the path you specify. They use a presentation layer just like CALayer animations do, and while the presentation layer makes the view look like it's moving along the specified path, it actually snaps from the beginning position to the end position at the beginning of the animation. UIView animations do move the view to its destination position, where CALayer animations move the presentation layer while not moving the layer/view at all.)
Another subtle difference between Leo's path-based UIView animation and my answer using UIView animateKeyframes(withDuration:delay:options:animations:completion:)is that CGPath curves are cubic or quadratic Bezier curves, and my answer animates using a different kind of curve called a Katmull-Rom spline. Bezier paths start and end at their beginning and ending points, and are attracted to, but don't pass through their middle control points. Catmull-Rom splines generate a curve that passes through every one of their control points.

Setting UIView.transform to arbitrary translate CGAffineTransform does nothing

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.

how to change the scale of view

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

iOS: convert CGPoint in screen coordinates to CATransform3D in iCarousel?

I wanted to add a possibility to drag elements inside the carousel and I have some troubles with positioning and transformations of a draggable element.
As I understand it is better to temporarily hide/delete the draggable element and create its proxy outside the carousel item array which represents the position and transform properties of the real one.
The problem is how to set the correct transform of such proxy item according to its position. One view from items array has its own index and carousel scroll offset (which is common for all the items). But the separated view has a position only (in better case it stores the transform of the original carousel item and I can find the nearest view/views).
So how to convert the given screen point to offset in iCarousel coordinate system? The transform matrices are enough complex: rotation, perspective and translation.
I have found a solution but in my particular case it looks awful. I need to move along y only so on each drag position changed event:
CGFloat newY = currentTouchPosition.y - positionOnTouchStartDragging.y + (draggedPageIndex - carousel.scrollOffset) * spacing * carousel.bounds.size.height;
CGFloat delta = self.bounds.size.height * spacing;
CATransform3D result = [carousel transformForItemViewWithOffset:newY / deltaY];
where spacing is a variable given in carousel or via its delegate.

Resources