How to Apply a Perspective Transform to a Uiview

How do I apply a perspective transform to a UIView?

As Ben said, you'll need to work with the UIView's layer, using a CATransform3D to perform the layer's rotation. The trick to get perspective working, as described here, is to directly access one of the matrix cells of the CATransform3D (m34). Matrix math has never been my thing, so I can't explain exactly why this works, but it does. You'll need to set this value to a negative fraction for your initial transform, then apply your layer rotation transforms to that. You should also be able to do the following:

Objective-C

UIView *myView = [[self subviews] objectAtIndex:0];
CALayer *layer = myView.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0f * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;

Swift 5.0

if let myView = self.subviews.first {
let layer = myView.layer
var rotationAndPerspectiveTransform = CATransform3DIdentity
rotationAndPerspectiveTransform.m34 = 1.0 / -500
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0 * .pi / 180.0, 0.0, 1.0, 0.0)
layer.transform = rotationAndPerspectiveTransform
}

which rebuilds the layer transform from scratch for each rotation.

A full example of this (with code) can be found here, where I've implemented touch-based rotation and scaling on a couple of CALayers, based on an example by Bill Dudney. The newest version of the program, at the very bottom of the page, implements this kind of perspective operation. The code should be reasonably simple to read.

The sublayerTransform you refer to in your response is a transform that is applied to the sublayers of your UIView's CALayer. If you don't have any sublayers, don't worry about it. I use the sublayerTransform in my example simply because there are two CALayers contained within the one layer that I'm rotating.

Combining a UIView perspective transform with other CATranform3Ds

Found the answer with a lot of debugging and the help of this question. The trick was to transform the view's perspective using:

spriteView.superview.layer.sublayerTransform = transform;

This recursively applies the transformation to the view's superview and any subviews contained in it. For more information about this, check out the documentation and Apple's Layer Style Properties guide too.

Converting UIView with perspective transform to UIImage

You can use UIView method drawViewHierarchyInRect. It will render a snapshot of the complete view hierarchy as visible onscreen into the current context.

Try like this:

UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0.0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

How to apply Perspective 3d Transform on image and save that image in document directory

You can do it by CoreImage Framework provided by Apple

- (UIImage *)imageByTransformingImage:(UIImage *)image {

if (image == nil) {
return nil; //image not found
}
CIImage *coreImage = [CIImage imageWithData:UIImagePNGRepresentation(image)];

if (!coreImage) {
return nil; // image is not converted in core image
}

//assuming that topLeft, topRight, bottomRight and bottomLeft stored in transformationDict
NSDictionary *dict = [self.transformationDict valueForKey:@"Corners"];

CGPoint topLeftPoint = CGPointFromString([dict valueForKey:kTopLeft]);
CGPoint topRightPoint = CGPointFromString([dict valueForKey:kTopRight]);
CGPoint bottomRightPoint = CGPointFromString([dict valueForKey:kBottomRight]);
CGPoint bottomLeftPoint = CGPointFromString([dict valueForKey:kBottomLeft]);

CIFilter *perspectiveTransformation = [CIFilter filterWithName:@"CIPerspectiveTransform"];
[perspectiveTransformation setValue:[CIVector vectorWithCGPoint:topLeftPoint] forKey:@"inputTopLeft"];
[perspectiveTransformation setValue:[CIVector vectorWithCGPoint:topRightPoint] forKey:@"inputTopRight"];
[perspectiveTransformation setValue:[CIVector vectorWithCGPoint:bottomRightPoint] forKey:@"inputBottomRight"];
[perspectiveTransformation setValue:[CIVector vectorWithCGPoint:bottomLeftPoint] forKey:@"inputBottomLeft"];
[perspectiveTransformation setValue:coreImage forKey:kCIInputImageKey];

CIImage *resultImage = [perspectiveTransformation outputImage];
CIContext *ciContext = [CIContext contextWithOptions:nil];
CGImageRef cgImageRef = [ciContext createCGImage:resultImage fromRect:[resultImage extent]];
UIImage *transformedImage = [UIImage imageWithCGImage:cgImageRef];

return transformedImage;
//transformed image will be flipped image you need to flip context to get correct UIImage
}

Swift version

func imageByTransformingImage(image: UIImage?) -> UIImage? {

guard image != nil else {
return nil;
}

if let coreImage = CIImage(data: UIImagePNGRepresentation(image!)!) {

//assuming that topLeft, topRight, bottomRight and bottomLeft stored in transformationDict
let dict = self.transformationDict.valueForKey("Corners");

let dict = [String : Any]();
let topLeftPoint = CGPointFromString(dict["TopLeftCorner"] as! String);
let topRightPoint = CGPointFromString(dict["TopRightCorner"] as! String);
let bottomRightPoint = CGPointFromString(dict["BottomRightCorner"] as! String);
let bottomLeftPoint = CGPointFromString(dict["BottomLeftCorner"] as! String);

if let perspectiveTransformation = CIFilter(name: "CIPerspectiveTransform") {
perspectiveTransformation.setValue(CIVector(CGPoint: topLeftPoint), forKey: "inputTopLeft");
perspectiveTransformation.setValue(CIVector(CGPoint:topRightPoint), forKey: "inputTopRight");
perspectiveTransformation.setValue(CIVector(CGPoint:bottomRightPoint), forKey: "inputBottomRight");
perspectiveTransformation.setValue(CIVector(CGPoint:bottomLeftPoint), forKey: "inputBottomLeft");
perspectiveTransformation.setValue(coreImage, forKey:kCIInputImageKey);

if let resultImage = perspectiveTransformation.outputImage {
let ciContext = CIContext()
let cgImageRef = ciContext.createCGImage(resultImage, fromRect: resultImage.extent) as CGImageRef

return UIImage(CGImage: cgImageRef);

//transformed image will be flipped image you need to flip context to get correct UIImage
}

}

}
return nil;
}

Apply a perspective transform without rotating

Just adding to David's answer: To get an output as in your image you have to rotate the view around the x-axis (the horizontal axis) so that the upper edge of the view rectangle appears "further away" from the viewer than the lower edge, e.g.

CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -200;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0f * M_PI / 180.0f, 1.0f, 0.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;

How to apply 3d perspective transform only in one part of compound UIView?

A few pointers:

  1. convert your UIView into an image: see this post; either you take 4 "snapshots" of your view, or:

    1.2 you take one, then crop the resulting image (see here for cropping);

  2. create a new view and add 4 CALayers as sublayers of self.layer; each layer has its contents property set to the corresponding UIImage;

  3. animate the view layers as you need; have a look at this file from the Leaves framework to see how this could be done; basically, this code uses a CATransaction and transformations to animate a property of the layers (which in this case represents the position of one layer respect another), so that when that property value changes, the layers are redrawn accordingly.

Hope it helps.

Apply a Transform to a UIView Itself

I figured out the problem after test by Krishnan confirmed that layer.transform itself does work without the aid of animation or sublayerTransform. The problem I encountered was caused by an animation that had been applied to my view's layer and was not subsequently removed. Since this animation had a toValue equal to the identity transform, I didn't realize it can actually prevent subsequent non-animated transforms from working. Setting removedOnCompletion = YES (which is default) on the animation solved the mystery.

perspective transform on uiview

Basically setting the anchor points before the animation block runs solves the issue. Anchor point for left -> (0.0f,0.5f) and for right -> (1.0f,0.5f).

How to twist perspective on a UIView?

As pointed in comments, the correct term is "Shear".

Here is how to reproduce this transformation with a given "proportion" :

CGAffineTransform transform = CGAffineTransformMake(1.0, proportion, 0.0, 1.0, 0.0, 0.0);
view.transform = transform;

Animate perspective of UIView to flip halfway up/away from bottom

Do you mean like this:

    [UIView animateWithDuration:3.0 animations:^{
[someView.layer setAffineTransform:
CGAffineTransformConcat(CGAffineTransformMakeScale(1.0f, .0001f), CGAffineTransformMakeTranslation(.0f, someView.frame.size.height*-.5f))];
}];

This is just fixing the issue you described, to get the result shown on your image you will require a 3D transformation.

To do a 3D transform things get quite tricky in your case:

    CGFloat viewHeight = someView.frame.size.height; //needed later as this value will change because of transformation applied
CATransform3D baseTransform = CATransform3DMakeTranslation(.0f, .0f, viewHeight*.5f); //put it towards user for rotation radius
baseTransform.m34 = -1.0/200.0; //this will make the perspective effect
someView.layer.zPosition = view.frame.size.height*.5f; //needed so the view isn't clipped
someView.layer.transform = baseTransform; //set starting transform (no visual effect here)

[UIView animateWithDuration:6.6 animations:^{
someView.layer.transform = CATransform3DConcat(CATransform3DConcat(CATransform3DMakeRotation(-M_PI_2, 1.0f, .0f, .0f), CATransform3DMakeTranslation(.0f, viewHeight*.5f, .0f)) , baseTransform);
//or to stay at the same center simply:
//someView.layer.transform = CATransform3DConcat(CATransform3DMakeRotation(-M_PI_2, 1.0f, .0f, .0f) , baseTransform);
}];

Now play around a bit with it.



Related Topics



Leave a reply



Submit