5

Skew Example Hi,

I am trying to achieve a transformation like in the image but I only have got a similar transformation but with perspective (which I don't want) using CGATransform3D. How could I achieve this using objective-c? The transformed image should have the same heigh and width than the original but with the base tilted by a given angle.

Thanks in advance for your help.

2 Answers2

16

This is a “shear” or “skew” transformation. Luckily for you, shear transformations are affine transformations, and iOS supports affine transformations at both the UIView and CALayer levels. However, it has no convenience functions to construct shear transforms, so let's write one:

static CGAffineTransform affineTransformMakeShear(CGFloat xShear, CGFloat yShear) {
    return CGAffineTransformMake(1, yShear, xShear, 1, 0, 0);
}

It's not obvious exactly what xShear and yShear mean. xShear is the tangent of the angle by which the y-axis is skewed, and yShear is the tangent of the angle by which the x-axis is skewed. This may seem backwards, but it has the visual effect you expect: a non-zero xShear tilts the figure left or right, and a non-zero yShear tilts the figure up or down.

Since tan π/4 = 1 (or, if you prefer, tan 45° = 1), the transform returned by affineTransformMakeShear(1, 0) turns a rectangle into a parallelogram whose left and right edges are at π/4 radians (or 45°).

Demo:

shear demo

Note that shearing on both axes can be identical to the combination of a rotation and a scale.

Here's my demo code:

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) IBOutlet UIView *shearedView;
@property (strong, nonatomic) IBOutlet UISlider *verticalShearSlider;
@property (strong, nonatomic) IBOutlet UISlider *horizontalShearSlider;
@property (strong, nonatomic) IBOutlet UILabel *verticalShearLabel;
@property (strong, nonatomic) IBOutlet UILabel *horizontalShearLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self updateAppearance:nil];
}

static CGAffineTransform affineTransformMakeShear(CGFloat xShear, CGFloat yShear) {
    return CGAffineTransformMake(1, yShear, xShear, 1, 0, 0);
}

- (IBAction)updateAppearance:(id)sender {
    CGFloat xShear = self.horizontalShearSlider.value;
    CGFloat yShear = self.verticalShearSlider.value;
    self.shearedView.transform = affineTransformMakeShear(xShear, yShear);
    self.horizontalShearLabel.text = [NSString stringWithFormat:@"%.2f", xShear];
    self.verticalShearLabel.text = [NSString stringWithFormat:@"%.2f", yShear];
}

@end
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks a lot!! this is awesome and easier than i thought! :D – user2248891 Mar 03 '16 at 09:17
  • @rob you have the best demos dude :) it would be cool if someone changed these classics to Swift one day... – Fattie May 21 '16 at 17:56
  • @rob mayoff i still don't understand, how to provide angle value in radians or degrees? – Evgeniy Kleban Apr 10 '19 at 12:51
  • Pass `tan(angle)` to `affineTransformMakeShear`. – rob mayoff Apr 10 '19 at 16:09
  • Interesting note: while UIView is being transformed based off its center, CGRect is transformed based of its top left vertex. – rommex Feb 21 '20 at 12:50
  • What has affineTransformMakeShear been renamed in Swift 6? The compiler says No ;-) and can't find any references. – Edward Hasted Nov 19 '21 at 14:24
  • There is no Swift 6 yet (and probably won't be for the next several years). Also, `affineTransformMakeShear` is a function defined in this answer. It has never been part of the iOS SDK. – rob mayoff Nov 19 '21 at 14:48
  • Sorry, I wasn't trying to humour you. We're on 5.5.1 so there is hope. And apologies for not reading the opening bit of your exceptionally well documented answer. – Edward Hasted Nov 23 '21 at 13:05
0

@Fattie. Quick Swift version of Rob's demo:

class AffineTester: UIViewController
{
    @IBOutlet weak var shearedView: UIView!
    @IBOutlet weak var verticalShearSlider: UISlider!
    @IBOutlet weak var horizontalShearSlider: UISlider!
    @IBOutlet weak var verticalShearLabel: UILabel!
    @IBOutlet weak var horizontalShearLabel: UILabel!

    override func viewDidLoad()
    {
        super.viewDidLoad()
        updateAppearance(self)
    }
    
    @IBAction func updateAppearance(_ sender: Any)
    {
        let xShear = CGFloat(horizontalShearSlider.value)
        let yShear = CGFloat(verticalShearSlider.value)
        shearedView.transform = CGAffineTransform(1, yShear, xShear, 1, 0, 0)
        horizontalShearLabel.text = String(format: "%.2f", xShear)
        verticalShearLabel.text = String(format: "%.2f", yShear)
    }
}
Troy Sartain
  • 163
  • 1
  • 4
  • 15