Animate Image Rotation of a Button - ios

I'm creating a UIButton and adding it above a UITabBar, in a custom UITabBarController.
var button = new UIButton(new CGRect(x, y, 50, 50));
button.Layer.BorderWidth = 2;
button.Layer.CornerRadius = button.Frame.Height / 2;
button.SetImage(UIImage.FromBundle("Name"), UIControlState.Normal);
this.View.AddSubview(button);
this.View.LayoutIfNeeded();
When I try to add a rotation transform on the image view, it doesn't rotate around the center and z axis, as I'd expect (and found in every documentation I read).
Instead, it animates somehow different.
It's the same regardless if I add the transform on the ImageView or its layer.
This for example is a rotation of 30 degrees (pi / 180 * 30) and 10 degrees.
However, when I add a SublayerTransform to the button layer, it transforms as expected. The problem with that approach is that it is not animateable with UIView.Animate or CATransaction.Begin.
Position, AnchorPoint and everything else is set to default. I tried playing around with these properties too, but to no avail.
As you can see from the images, it's a + sign that should animate to become an x.
Any hint is very much appreciated.
The 3D transforms I have defined like this:
CATransform3D.MakeRotation((float)(Math.PI / 180 * 45), 0, 0, 1)
and the normal ones like this:
CGAffineTransform.MakeRotation((float)(Math.PI / 180 * 45))

If you need to rotate just the button image view for some reason, try this:
button.ImageView.Layer.Transform = CATransform3D.MakeRotation((float)(Math.PI / 180 * 45), 0.0001f, 0.0001f, 1);
With the x and y values set to 0, it seems that there is still rotation on the x and y axis, though there should not be. With a value of 45 degrees, it rotates the x and y as well so that you are seeing the image edge on (so seems to be rotating x and y by 90 degrees), i.e. it disappears. Putting in a small value for X and y seems to avoid this error. I am going to see if this is exclusive to Xamarin or if it is an iOS thing.
EDIT: I just tested this in Obj-C in XCode and got the exact same behavior, so this is not a Xamarin issue but is an issue with iOS and how it is processing transforms for the UIImageView that is in a UIButton. Same issue does not occur with a UIImageView that is not in the UIButton.

Related

Scale UIVIew upwards

I have an UIView which I want to scale to double its size. I've tried these 2 commands:
self.bottomView.transform = CGAffineTransform(translationX: 0, y: -125)
This one moves to the point I want but it moves the entire view so I get a gap at the bottom.(125 is my original height)
self.bottomView.transform = CGAffineTransform(scaleX: 1, y: 2)
This one stretches the view but it stretches both ways, up and down. I want it to only stretch in an upward Y-axis direction and not to both ways.
Which one should I continue with? Is there any way to choose which way the view should stretch? Furthermore, scaleX: y: stretches the subviews as well which isn't optimal for my cause.
I think you can use the below API.
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
Update only one axis: X or Y with recognizer.scale and keep the other one 1.0f to achieve one direction scale.
Here is a simple Solution that may help you -- Just Animate your stretching operation on UIView.
UIView.animate(withDuration: 0.2) {
self.bottomView.frame.size.height = self.bottomView.frame.size.height * 2
}

SKLabelNode showing on iPhone simulation but not iPad

I have this code to put a score label for my game in the top left corner of the screen.
scoreLabel = SKLabelNode(text:"bats avoided: \(score)")
scoreLabel.fontSize = 50
scoreLabel.horizontalAlignmentMode = .left
scoreLabel.position = CGPoint (x:10 - (frame.size.width/2), y: (frame.size.height/2) - 50)
but the label appears on iPhone simulator only, not iPad.
If I print the portion of the label at runtime, it shows that the position is x: -365 y:617 with a screen width/2 of 375 and screen height/2 of 667, so it should definitely be there.
what gives?
The coordinate system in iOS represents 'top left' as 0,0. That is to say that as you increase x, you move to the right and as you increase y, you move down.
You are setting a value of 10 - (frame.size.width/2) for x. Suppose your frame.size.width is 300, for example, this would be equal to -140. Remember how I said 'top left' is represented as 0,0? Well, if you have an x coordinate of -140, the element will be further left than 0,0, and subsequently offscreen.
To place something in the top left, inset by 10 points. You would simply set it's position to be CGPoint(x: 10, y: 10).
It is worth noting that when working with sprites, as in your case, that Y0 is actually the bottom, rather than the top of the screen. This a somewhat annoying discrepancy but one to keep in mind.

Using CATransform3DRotate with perspective: how to correct the 2D size increase?

I'm trying to create a paper folding effect in Swift using CALayers and CATransform3DRotate. There are some libraries out there, but those are pretty outdated and don't fit my needs (they don't have symmetric folds, for example).
My content view controller will squeeze to the right half side of the screen, revealing the menu at the left side.
Everything went well, until I applied perspective: then the dimensions I calculate are not correct anymore.
To explain the problem, I created a demo to show you what I'm doing.
This the content view controller with three squares. I will use three folds, so each square will be on a separate fold.
The even folds will get anchor point (0, 0.5) and the odd folds will get anchor point (1, 0.5), plus they'll receive a shadow.
When fully folded, the content view will be half of the screen's width.
On an iPhone 7, each fold/plane will be 125 points unfolded and 62.5 points fully folded when looked at.
To calculate the rotation needed to achieve this 62.5 points width, we can use a trigonometric function. To illustrate, look at this top-down view:
We know the original plane size (125) and the 2D width (62.5), so we can calculate the angle α using arccos:
let angle = acos(width / originalWidth)
The result is 1.04719755 rad or 60 degrees.
When using this formula with CATransform3DRotate, I get the correct result:
Now for the problem: when I add perspective, my calculation isn't correct anymore. The planes are bigger. Probably because of the now different projection.
You can see the planes are now overlapping and being clipped.
I reconstructed the desired result on the right by playing with the angle, but the correction needed is not consistent, unfortunately.
Here's the code I use. It works perfectly without perspective.
// Loop layers
for i in 0..<self.layers.count {
// Get layer
let layer = self.layers[i]
// Get dimensions
let width = self.frame.size.width / CGFloat(self.numberOfFolds)
let originalWidth = self.sourceView.frame.size.width / CGFloat(self.numberOfFolds)
// Calculate angle
let angle = acos(width / originalWidth)
// Set transform
layer.transform = CATransform3DIdentity
layer.transform.m34 = 1.0 / -500
layer.transform = CATransform3DRotate(layer.transform, angle * (i % 2 == 0 ? -1 : 1), 0, 1, 0)
// Update position
if i % 2 == 0 {
layer.position = CGPoint(x: (width * CGFloat(i)), y: layer.position.y)
} else {
layer.position = CGPoint(x: (width * CGFloat(i + 1)), y: layer.position.y)
}
}
So my question is: how do I achieve the desired result? Do I need to correct the angle, or should I calculate the projected/2D width differently?
Thanks in advance! :)

rotating a CALayer in iOS 7 translates the layer randomly

I'm trying to rotate an image layer by given angle, but it is showing a really weird behavior in iOS 7. It works as expected in iOS 8 and I can't figure out what I'm doing wrong.
Here's the expected image (iOS 8 result). (45 degrees rotated)
This is what I get from iOS 7.
The arrow is supposed to have the same center as the image, but it moves to an unexpected position.
Here's my code. (initial translate and anchor point is to adjust the rotating center because the arrow image's rotation should not happen around the actual center of that image -- and even without these lines, it doesn't make any difference in iOS 7)
if (CATransform3DIsIdentity(self.compassImageView.layer.transform)) {
self.compassImageView.layer.transform = CATransform3DTranslate(CATransform3DIdentity, 0, 5, 0);
}
self.compassImageView.layer.anchorPoint = CGPointMake(0.5, 95 / 180.0);
NSString *zRotationKeyPath = #"transform.rotation.z";
[self.compassImageView.layer setValue:#(targetAngle * M_PI / 180.0) forKeyPath:zRotationKeyPath];
Thanks!

Understanding CGAfflineTransform in the context of Rotation

I am working on a few experiments to learn gestures and animations in iOS. Creating a Tinder-like interface is one of them. I am following this guide: http://guti.in/articles/creating-tinder-like-animations/
I understand the changing of the position of the image, but don't understand the rotation. I think I've pinpointed my problem to not understanding CGAfflineTransform. Particularly, the following code:
CGFloat rotationStrength = MIN(xDistance / 320, 1);
CGFloat rotationAngle = (CGFloat) (2 * M_PI * rotationStrength / 16);
CGFloat scaleStrength = 1 - fabsf(rotationStrength) / 4;
CGFloat scale = MAX(scaleStrength, 0.93);
CGAffineTransform transform = CGAffineTransformMakeRotation(rotationAngle);
CGAffineTransform scaleTransform = CGAffineTransformScale(transform, scale, scale);
self.draggableView.transform = scaleTransform;
Where are these values and calculations, such as: 320, 1-fabs(strength) / 4 , .93, etc, coming from? How do they contribute to the eventual rotation?
On another note, Tinder seems to use a combination of swiping and panning. Do they add a swipe gesture to the image, or do they just take into account the velocity of the pan?
That code has a lot of magic constants, most of which are likely chosen because they resulted in something that "looked good". This can make it hard to follow. It's not so much about the actual transforms, but about the values used to create them.
Let's break it down, line by line, and see if that makes it clearer.
CGFloat rotationStrength = MIN(xDistance / 320, 1);
The value 320 is likely assumed to be the width of the device (it was the portrait width of all iPhones until the 6 and 6+ came out).
This means that xDistance / 320 is a factor of how far along the the x axis (based on the name xDistance) that the user has dragged. This will be 0.0 when the user hasn't dragged any distance and 1.0 when the user has dragged 320 points.
MIN(xDistance / 320, 1) Takes the smallest value of the dragged distance factor and 1). This means that if the user drags further than 320 points (so that the distance factor would be larger than 1, the rotation strength would never be larger than 1.0. It doesn't protect agains negative values (if the user dragged to the left, xDistance would be a negative value, which would always be smaller than 1. However, I'm not sure if the guide accounted for that (since 320 is the full width, not the half width.
So, the first line is a factor between 0 and 1 (assuming no negative values) of how much rotation should be applied.
CGFloat rotationAngle = (CGFloat) (2 * M_PI * rotationStrength / 16);
The next line calculates the actual angle of rotation. The angle is specified in radians. Since 2π is a full circle (360°), the rotation angle is ranging from 0 and 1/16 of a full circle (22.5°). Th value 1/16 is likely chosen because it "looked good".
The two lines together means that as the user drags further, the view rotates more.
CGFloat scaleStrength = 1 - fabsf(rotationStrength) / 4;
From the variable name, it would look like it would calculate how much the view should scale. But it's actually calculating what scale factor the view should have. A scale of 1 means the "normal" or unscaled size. When the rotation strength is 0 (when the xDistance is 0), the scale strength will be 1 (unscaled). As rotation strength increase, approaching 1, this scale factor approaches 0.75 (since that's 1 - 1/4).
fabsf is simply the floating point absolute value (fabsf(-0.3) is equal to 0.3)
CGFloat scale = MAX(scaleStrength, 0.93);
On the next line, the actual scale factor is calculated. It's simply the largest value of the scaleStrength and 0.93 (scaled down to 93%). The value 0.93 is completely arbitrary and is likely just what the author found appealing.
Since the scale strength ranges from 1 to 0.75 and the scale factor is never smaller than 0.93, the scale factor only changes for the first third of the xDistance. All scale strength values in the next two thirds will be smaller than 0.93 and thus won't change the scale factor.
With the scaleFactor and rotationAngle calculated as above, the view is first rotated (by that angle) and then scaled down (by that scale factor).
Summary
So, in short. As the view is dragged to the right (as xDistance approaches 320 points), The view linearly rotates from 0° to 22.5° over the full drag and scales from 100% to 93% over the first third of the drag (and then stays at 93% for the remainder of the drag gesture).

Resources