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:
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);
create a new view and add 4 CALayers as sublayers of
self.layer
; each layer has itscontents
property set to the correspondingUIImage
;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
Wkwebview Origin Null Is Not Allowed by Access-Control-Allow-Origin
Loading/Downloading Image from Url on Swift
How to Set Up a Simple Delegate to Communicate Between Two View Controllers
Ios 7 Status Bar Back to iOS 6 Default Style in Iphone App
Remove HTML Tags from an Nsstring on the Iphone
Programmatically Set the Initial View Controller Using Storyboards
Uialertview Addsubview in Ios7
Change Paid App to Free But Know If User Previously Purchased It
Why It Shows "Ld: Framework Not Found Bolts"
Detect Tap on a Button in Uitableviewcell for Uitableview Containing Multiple Sections
Symbolicating Iphone App Crash Reports
Launch an App from Within Another (Iphone)
Evenly Space Multiple Views Within a Container View
How to Symbolicate Crash Log Xcode