ios : rotating and "un"-rotating views does not work as expected - ios

I have the following code :
#define ANG_TO_RAD(angle) ( angle/180*M_PI)
CGFloat degree = x;
CGAffineTransform transform = CGAffineTransformMakeRotation ( ANG_TO_RAD(degree) );
image.transform = transform;
This rotates the image. However, when I want to rotate it back to the original setting, and call the above function again but with degree=-degree : the image is not rotated exactly to the same position as it was before. There is always some tilt..
I tried to make degree = 180-degree when trying to un-rotate it but no luck
thanks

You're directly setting the transformation, that is applied to the image, the transformations are not chained automatically, like you seem to be expecting.
To get back to the original you have to apply CGAffineTransformIdentity.
For chaining you use CGAffineTransformConcat e.g.
image.transform = CCGAffineTransformConcat(image.transform, some_other_transformation);

Related

CGAffineTransform concatenation: appropriate order of transformations

I know that whenever we want to apply a series of transform at once to a point, we have to specify the sequence in the opposite direction to what we want to perceive. If I want to translate(T) and then rotate(R) a point x we need to end with a concatenation matrix RT, then every point is transformed as (RT)x.
Apple Transform documentation shows that CGAffineTransform work as a transpose version of the above expression. i.e instead of transform a points as Ax for A being any transform matrix they do it as xB for B being the transpose of A.
Also CGAffineTransform.concatenating(T2) documentation state that it stack transforms to the right, ending in an transform matrix of the form as T1 * T2.
Now what are my concerns? If want a series of transformation that translate(T) and then rotate(R), I can call T.concatenating(R) which result in a transform TR, then the points are transformed as xTR, it seem that the order must be respected, but actually I'm getting the wrong result(at least that's what i think), I expect the image A but getting B,
It seem that they are transforming points as TRx, but this contradict the documentation and also not match the result when you apply the transform TR to a point as CGPoint.applying(TR) which also is consistent with the order xTR.
why's that? there's something that I'm missing about how transforms works in iOS?
It's hard to see what the question is here, because you've already answered it. Basically, the order you must use when calling concatenating is the opposite of the order you would use when chaining transformations. Thus for example:
v2.transform =
CGAffineTransform(rotationAngle: 45 * .pi/180).translatedBy(x: 100, y: 0)
In this screen shot, v2 is the green view, and started out where the purple view is:
If you wanted to do that with concatenating, you'd say:
let r = CGAffineTransform(rotationAngle: 45 * .pi/180)
let t = CGAffineTransform(translationX:100, y:0)
v2.transform = t.concatenating(r) // not r.concatenating(t)
If I'm not mistaken, your confusion lies not with order of operations, but with the rotate transformation. Rotation does not take place about the object's center, but about the origin of the frame, which remains fixed in place. When you rotate an object not centered at the frame's origin, it pivots around like the end of the hand of a clock. To achieve image A, you must
Translate your object so that the point you wish to rotate it about (presumably its center) lies at the origin.
Rotate the object.
Translate it to the desired position.
let rotationAngle = CGFloat.pi / 4
let slidingLength : CGFloat = 50
let duration = 0.3
UIView.animate(withDuration: duration) { [weak self] in
self?.slidingView.transform = CGAffineTransform(rotationAngle: rotationAngle)
self?.slidingView.center.x += slidingLength
}

UIView transform animation - scale and translate

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.

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

UIDynamicItem update transform manually

I know that external change to center, bounds and transform will be ignored after UIDynamicItems init.
But I need to manually change the transform of UIView that in UIDynamicAnimator system.
Every time I change the transform, it will be covered at once.
So any idea? Thanks.
Any time you change one of the animated properties, you need to call [dynamicAnimator updateItemUsingCurrentState:item] to let the dynamic animator know you did it. It'll update it's internal representation to match the current state.
EDIT: I see from your code below that you're trying to modify the scale. UIDynamicAnimator only supports rotation and position, not scale (or any other type of affine transform). It unfortunately takes over transform in order to implement just rotation. I consider this a bug in UIDynamicAnimator (but then I find much of the implementation of UIKit Dynamics to classify as "bugs").
What you can do is modify your bounds (before calling updateItem...) and redraw yourself. If you need the performance of an affine transform, you have a few options:
Move your actual drawing logic into a CALayer or subview and modify its scale (updating your bounds to match if you need collision behaviors to still work).
Instead of attaching your view to the behavior, attach a proxy object (just implement <UIDyanamicItem> on an NSObject) that passes the transform changes to you. You can then combine the requested transform with your own transform.
You can also use the .action property of the UIDynamicBehavior to set your desired transform at every tick of the animation.
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];
attachment.damping = 0.8f;
attachment.frequency = 0.8f;
attachment.action = ^{
CGAffineTransform currentTransform = item.transform;
item.transform = CGAffineTransformScale(currentTransform, 1.2, 1.2)
};
You would need to add logic within the action block to determine when the scale should be changed, and by how much, otherwise your view will always be at 120%.
Another way to fix this (I think we should call it bug) is to override the transform property of the UIView used. Something like this:
override var transform: CGAffineTransform {
set (value) {
}
get {
return super.transform
}
}
var ownTransform: CGAffineTransform. {
didSet {
super.transform = ownTransform
}
}
It's a kind of a hack, but it works fine, if you don't use rotation in UIKitDynamics.

UIImage transform/scaling issues

Finally, I have a reason to ask something, instead of scouring endless hours of the joys of Stack Overflow.
Here's my situation: I have an UIImageView with one UIImage inside it. I'm transforming the entire UIImageView via CGAffineTranforms, to scale it height-wise and keep it at a specific angle.
I'm feeding it this transform data through two CGPoints, so it's essentially just calculating the angle and scale between these two points and transforming.
Now, the transforming is working like a charm, but I recently came across the UIImage method resizableImageWithCapInsets, which works just fine if you set the frame of the image manually (ie. scale using a frame), but it seems that using transforms overrides this, which I guess is sort of to be expected since it's Core Graphics doing it's thing.
My question is, how would I go about either a) adding cap insets after transforming the image or b) doing the angle & scaling via a frame?
Note that the two points providing the data are touch points, so they can differ very much, which is why creating a scaled rectangle at a specific angle is tricky at best.
To keep you code hungry geniuses happy, here's a snippet of the current way I'm handling scaling (only doing cap insets when creating the UIImage):
float xDiff = (PointB.x - PointA.x) / 2;
float yDiff = (PointB.y - PointA.y) / 2;
float angle = [self getRotatingAngle:PointA secondPoint:PointB];
CGPoint pDiff = CGPointMake(PointA.x + xDiff, PointA.y + yDiff);
self.center = pDiff;
// Setup a new transform
// Set it up with a scale and an angle
double distance = sqrt(pow((PointB.x - PointA.x), 2.0) + pow((PointB.y - PointA.y), 2.0));
float scale = 1.0 * (distance / self.image.size.height);
CGAffineTransform transformer = self.transform;
transformer = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, scale), CGAffineTransformMakeRotation(angle));
// Apply the transformer
self.transform = transformer;
Adding a proper answer to this. The answer to the problem can be found here.

Resources