I am trying to apply a perspective transformation to an MKMapView. I am following the example of this Stackoverflow question, so my code looks like this (inserted into the view controller's viewDidLoad method):
MKMapView *map = [[MKMapView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 480.0)];
[self.view addSubview:map];
CLLocationDistance distance = 2000000.0;
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(52.5, 13.4);
[map setRegion:MKCoordinateRegionMakeWithDistance(coordinate, distance, distance)];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -1200.0;
transform = CATransform3DRotate(transform, M_PI_4, 1.0, 0.0, 0.0);
map.layer.transform = transform;
This code, as written, works nicely, I get a map of Europe with the perspective applied. The problem is, if I decrease the distance variable to get a closer view, or even zoom in from this working initial state, the map does not zoom in properly, it just scales the map as it was in the zoomed-out state. On the other hand, if I don't set the m34 field of transform, i.e., only tilt the map without perspective, it works just fine.
Is there a way to get a street-level map with perspective?
I managed to get the perspective working by adding
[map.layer setShouldRasterize:YES];
So apparently there is a problem with direct rendering of the map content when perspective is applied.
Related
I have a simple rotation gesture implemented in my code, but the problem is when I rotate the image it goes off the screen/out of the view always to the right.
The image view that is being rotated center X gets off or increases (hence it going right off the screen out of the view).
I would like it to rotate around the current center, but it's changing for some reason. Any ideas what is causing this?
Code Below:
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *l = [self.viewCase layer];
[l setMasksToBounds:YES];
[l setCornerRadius:30.0];
self.imgUserPhoto.userInteractionEnabled = YES;
[self.imgUserPhoto setClipsToBounds:NO];
UIRotationGestureRecognizer *rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotationDetected:)];
[self.view addGestureRecognizer:rotationRecognizer];
rotationRecognizer.delegate = self;
}
- (void)rotationDetected:(UIRotationGestureRecognizer *)rotationRecognizer
{
CGFloat angle = rotationRecognizer.rotation;
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, angle);
rotationRecognizer.rotation = 0.0;
}
You want to rotate the image around it's center, but that's not what it is actually happening. Rotation transforms take place around the origin. So what you have to do is to apply a translate transform first to map the origin to the center of the image, and then apply the rotation transform, like so:
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, self.imageView.bounds.size.width/2, self.imageView.bounds.size.height/2);
Please note that after rotating you'll probably have to undo the translate transform in order to correctly draw the image.
Hope this helps
Edit:
To quickly answer your question, what you have to do to undo the Translate Transform is to subtract the same difference you add to it in the first place, for example:
// The next line will add a translate transform
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 10, 10);
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, radians);
// The next line will undo the translate transform
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, -10, -10);
However, after creating this quick project I realized that when you apply a rotation transform using UIKit (like the way you're apparently doing it) the rotation actually takes place around the center. It is only when using CoreGraphics that the rotation happens around the origin. So now I'm not sure why your image goes off the screen. Anyway, take a look at the project and see if any code there helps you.
Let me know if you have any more questions.
The 'Firefox' image is drawn using UIKit. The blue rect is drawn using CoreGraphics
You aren't rotating the image around its centre. You'll need correct this manually by translating it back to the correct position
I'm having an odd problem with CATransform3DMakeRotation. When the rotated view is in the center of the superview, it looks like this:
This is how it's supposed to look. However, when it's somewhere else in the superview, say, in the lower left corner, it looks like this:
Note that it is tilted to the right. Same thing happens when it's in the lower right corner, only that it tilts to the left. Is there some way to make it tilt the way it should all the time and in every position? The code to achieve this transformation is as follows:
CALayer *layer = view.layer;
CATransform3D aTransform = CATransform3DIdentity;
float zDistance = 1000;
aTransform.m34 = 1.0 / -zDistance;
self.view.layer.sublayerTransform = aTransform;
CABasicAnimation *rotateAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
rotateAnim.fromValue= [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0, 0, 0, 0)];
rotateAnim.duration=0.12;
rotateAnim.toValue=[NSValue valueWithCATransform3D:CATransform3DMakeRotation(20*M_PI/180, 1, 0, 0)];
rotateAnim.removedOnCompletion = NO;
rotateAnim.fillMode = kCAFillModeForwards;
[layer addAnimation:rotateAnim forKey:#"rotateAnim"];
You've applied perspective at the superview's layer, and that's what you're getting. If you want perspective based on the individual sublayers (without considering the overall perspective), then you have to apply perspective to them individually. For example:
CALayer *layer = view.layer;
CATransform3D aTransform = CATransform3DIdentity;
CGFloat zDistance = 2000.;
aTransform.m34 = 1.0 / -zDistance;
CABasicAnimation *rotateAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
rotateAnim.fromValue= [NSValue valueWithCATransform3D:aTransform];
aTransform = CATransform3DRotate(aTransform, 20*M_PI/180, 1, 0, 0);;
rotateAnim.duration=0.12;
rotateAnim.toValue=[NSValue valueWithCATransform3D:aTransform];
layer.transform = aTransform;
[layer addAnimation:rotateAnim forKey:#"rotateAnim"];
In this example, I apply perspective (m34) in both the fromValue and the toValue of the layer. This then gives you perspective relative to an observer standing in front of the layer itself. Your original code had an observer standing in front of the center of the view, and so you get some skew perspective.
I made some other small changes here. I typically put the observer at 2000 rather than 1000. I find the perspective at 1000 is often more than is desired (unless you're actively going for strong perspective). Depending on your problem, you might want to get rid of perspective entirely. Don't apply it just out of habit.
More importantly, I changed how you were applying the animation at the end. Using removedOnCompletion=NO and kCAFillModeForwards makes things much more complicated than just applying the animation to the model (layer.transform=aTransform) and letting the animation be removed normally. In particular, using removedOnCompletion=NO means that later requests for layer.transform will always return the old value, not the current value. It also makes things complicated if you apply further animations to this layer. This is why the default is to remove the animation when complete.
I have a UIView object that rotates using CALayer's transform:
// Create uiview object.
UIImageView *block = [[UIImageView alloc] initWithFrame....]
// Apply rotation.
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 = 1.0/-distance;
blockImage.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
After rotating the edges of the object are not antialiasing. I need to antialias them.
Help me, please. How can it be done?
One way to do this is by placing the image inside another view that's 5 pixels bigger. The bigger view should have a transparent rasterized border that will smooth the edges of the UIImageView:
view.layer.borderWidth = 3;
view.layer.borderColor = [UIColor clearColor].CGColor;
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Then, place your UIImageView inside this parent view and center it (With 2.5 pixels around each edge).
Finally, rotate the parent view instead of the image view.
It works very well - you can also encapsulate the whole thing in class that creates the hierarchy.
Simply add this key-value pair to your Info.plist: UIViewEdgeAntialiasing set to YES.
check allowsEdgeAntialiasing property of CALayer.
block.layer.allowsEdgeAntialiasing = YES; // iOS7 and above.
I had a similar issue when rotating around the z-axis. Setting shouldRasterize = YES prevented the jagged edges however it came at a performance cost. In my case I was re-using the views (and its layers) and keeping the shouldRasterize = YES was slowing things down.
The solution was, to turn off rasterization right after I didn't need it anymore. However since animation runs on another thread, there was no way of knowing when the animation was complete...until I found out about an extremely useful CATransaction method. This is an actual code that I used and it should illustrate its use:
// Create a key frame animation
CAKeyframeAnimation *wiggle = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSInteger frequency = 5; // Higher value for faster vibration
NSInteger amplitude = 25; // Higher value for lower amplitude
// Create the values it will pass through
NSMutableArray *valuesArray = [[NSMutableArray alloc] init];
NSInteger direction = 1;
[valuesArray addObject:#0.0];
for (NSInteger i = frequency; i > 0; i--, direction *= -1) {
[valuesArray addObject:#((direction * M_PI_4 * (CGFloat)i / (CGFloat)amplitude))];
}
[valuesArray addObject:#0.0];
[wiggle setValues:valuesArray];
// Set the duration
[wiggle setAdditive:YES];
[wiggle setValueFunction:[CAValueFunction functionWithName:kCAValueFunctionRotateZ]];
[wiggle setDuration:0.6];
// Turn on rasterization to prevent jagged edges (anti-aliasing issues)
viewToRotate.layer.shouldRasterize = YES;
// ************ Important step **************
// Very usefull method. Block returns after ALL animations have completed.
[CATransaction setCompletionBlock:^{
viewToRotate.layer.shouldRasterize = NO;
}];
// Animate the layer
[viewToRotate.layer addAnimation:wiggle forKey:#"wiggleAnimation"];
worked like a charm for me.
I have not tried using this with implicit animations (i.e. animations that happen due to value change in animatable property for a non-view associated layer), however I would expect it to work as long as the CATransaction method is called before the property change, just as a guarantee the block is given to CATransaction before an animation starts.
I'm using a CATransform3D transformation to apply a pseudo 3D perspective effect to a view.
I'm using the gyroscope in the iPhone 4 to control the parameters of the transform.
The view I'm transforming has some subviews:
The result is something like this:
My next task is to either prevent the subviews from being transformed as the main view is transformed, or to apply an inverse transform so that the subviews remain unaffected.
My aim is to have each subview perpendicular to the superview, giving the impression that they are "standing".
The code I'm using for the transform:
- (void)update:(NSTimer *)timer
{
roll = [[[_manager deviceMotion] attitude] roll];
pitch = [[[_manager deviceMotion] attitude] pitch];
yaw = [[[_manager deviceMotion] attitude] yaw];
CALayer *layer = [self.view layer];
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, -90 * M_PI / 180.0f, 0.0f, 0.0f, 1.0f); // make landscape again
rotationAndPerspectiveTransform.m34 = 1.0 / -500; // apply the perspective
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, roll, 1.0f, 0.0f, 0.0f);
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, yaw, 0.0f, 0.0f, 1.0f);
[layer setTransform:rotationAndPerspectiveTransform];
}
I have tried the following code in an attempt to invert the transform, by applying a 90 degree perspective rotation to the subview around the Y axis:
for (UIView *subview in [self.view subviews])
{
CALayer *sublayer = [subview layer];
//CATransform3D inverseTransformation = [sublayer transform];
CATransform3D inverseTransformation = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -500; // apply perspective
inverseTransformation = CATransform3DRotate(inverseTransformation, 90 * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
[sublayer setTransform:inverseTransformation];
}
but all this succeeds in doing is making the subviews disappear. If I try to use the existing transform from the layer with [sublayer transform] and apply the 3d rotation to it, then the subviews flicker, but don't rotate.
My knowledge of Matrix math isn't great, so I don't know if this is the correct approach.
How can I make the subviews sit perpendicular to the super view?
I recommend not trying to make the basic approach of calculating each individual transform work. Also, this would be easier using CALayers for each of the little sub views instead of the higher level UIView objects. The basic approach of building a 3D model, and transforming the entire model should work much better for you.
Start with the main layer, add a sublayer for each of the little squares, transform those sublayers into position, then your update: method listed in your question should work to transform everything without modification. John Blackburn has a nice little tutorial that should help greatly.
I am working on an application which needs to rotate the slider at one end. However, by using CGAffineTransformMakeRotation(), I can only rotate the slider at the centre.
What should I do in order to rotate the slider at the end point? If possible, please give a short paragraph of sample code. Thanks a lot.
I rotate a UIImageView by setting the anchorPoint property for the view's layer. e.g.
myView.layer.anchorPoint = CGPointMake(0.5, 1.0);
and then applying a CATransform3DRotate as the layer's transform property
CATransform3D rotationTransform = CATransform3DIdentity;
rotationTransform = CATransform3DRotate(rotationTransform, positionInRadians, 0.0, 0.0, 1.0);
myView.layer.transform = rotationTransform;
I found this from Apple's metronome example, so it's worth checking that out. Hope that helps
I second cidered's answer. To do what you want using CGAffineTransforms, you have to compose transformations until you get the effect you're looking for:
CGAffineTransform transform = CGAffineTransformMakeRotation(1.0);
transform = CGAffineTransformTranslate(transform, slider.frame.size.width / 2, 0.0);
slider.transform = transform;