Rotate rectangle around center - iphone

I am playing with Brad Larsen's adaption of the trackball app.
I have two views at a 60 degree angle to each other and was wondering how I get the rotation to be in the center of this (non-closed) rectangle?
In the images below I would have liked the rotation to take place all within the blue lines.
Code (modified to only rotate around x axis):
#import "MyView.h"
//=====================================================
// Defines
//=====================================================
#define DEGREES_TO_RADIANS(degrees) \
(degrees * (M_PI / 180.0f))
//=====================================================
// Public Interface
//=====================================================
#implementation MyView
- (void)awakeFromNib
{
transformed = [CALayer layer];
transformed.anchorPoint = CGPointMake(0.5f, 0.5f);
transformed.frame = self.bounds;
[self.layer addSublayer:transformed];
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(10.0f, 4.0f, self.bounds.size.width / 2.0f, self.bounds.size.height / 2.0f);
imageLayer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(60.0f), 1.0f, 0.0f, 0.0f);
imageLayer.contents = (id)[[UIImage imageNamed:#"IMG_0051.png"] CGImage];
imageLayer.borderColor = [UIColor yellowColor].CGColor;
imageLayer.borderWidth = 2.0f;
[transformed addSublayer:imageLayer];
imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(10.0f, 120.0f, self.bounds.size.width / 2.0f, self.bounds.size.height / 2.0f);
imageLayer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(-60.0f), 1.0f, 0.0f, 0.0f);
imageLayer.contents = (id)[[UIImage imageNamed:#"IMG_0089.png"] CGImage];
imageLayer.borderColor = [UIColor greenColor].CGColor;
imageLayer.borderWidth = 2.0f;
transformed.borderColor = [UIColor whiteColor].CGColor;
transformed.borderWidth = 2.0f;
[transformed addSublayer:imageLayer];
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, self.bounds.size.height / 2.0f, self.bounds.size.width, 2)];
[line setBackgroundColor:[UIColor redColor]];
[self addSubview:line];
line = [[UIView alloc] initWithFrame:CGRectMake(0, self.bounds.size.height * (1.0f / 4.0f), self.bounds.size.width, 2)];
[line setBackgroundColor:[UIColor blueColor]];
[self addSubview:line];
line = [[UIView alloc] initWithFrame:CGRectMake(0, self.bounds.size.height * (3.0f / 4.0f), self.bounds.size.width, 2)];
[line setBackgroundColor:[UIColor blueColor]];
[self addSubview:line];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
previousLocation = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint location = [[touches anyObject] locationInView:self];
//location = CGPointMake(previousLocation.x, location.y);
CATransform3D currentTransform = transformed.sublayerTransform;
//CGFloat displacementInX = location.x - previousLocation.x;
CGFloat displacementInX = previousLocation.x - location.x;
CGFloat displacementInY = previousLocation.y - location.y;
CGFloat totalRotation = sqrt((displacementInX * displacementInX) + (displacementInY * displacementInY));
CGFloat angle = DEGREES_TO_RADIANS(totalRotation);
CGFloat x = ((displacementInX / totalRotation) * currentTransform.m12 + (displacementInY/totalRotation) * currentTransform.m11);
CATransform3D rotationalTransform = CATransform3DRotate(currentTransform, angle, x, 0, 0);
previousLocation = location;
transformed.sublayerTransform = rotationalTransform;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
}
- (void)dealloc {
[super dealloc];
}
#end

You need to set the imageLayer.zPosition of each of the triangle sides to the distance from the center of the triangle (which is an equilateral triangle in your case).
If sideHeight = self.bounds.size.height / 2.0f;
Then distanceFromCenter = ((sideHeight / 2.0f) / sqrt(3));
Also, when setting the rotation on the sides, you need to move them into their required positions (in your code they are hardcoded).
Updated Code
- (void)awakeFromNib
{
transformed = [CALayer layer];
transformed.anchorPoint = CGPointMake(0.5f, 0.5f);
transformed.frame = self.bounds;
[self.layer addSublayer:transformed];
CGFloat sideHeight = self.bounds.size.height / 2.0f;
CGFloat distanceFromCenter = ((sideHeight / 2.0f) / sqrt(3));
CGFloat sideC = sideHeight / 2.0f;
CGFloat sideA = sideC / 2.0f;
CGFloat sideB = (sqrt(3) * sideA);
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(10.0f, (self.bounds.size.height / 2.0f) - (sideHeight / 2.0f), self.bounds.size.width / 2.0f, sideHeight);
imageLayer.transform = CATransform3DConcat(CATransform3DMakeRotation(-DEGREES_TO_RADIANS(60.0f), 1.0f, 0.0f, 0.0f),
CATransform3DMakeTranslation(0, -sideA, -sideB));
imageLayer.contents = (id)[[UIImage imageNamed:#"IMG_0051.png"] CGImage];
imageLayer.borderColor = [UIColor yellowColor].CGColor;
imageLayer.borderWidth = 2.0f;
imageLayer.zPosition = distanceFromCenter;
[transformed addSublayer:imageLayer];
imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(10.0f, (self.bounds.size.height / 2.0f) - (sideHeight / 2.0f), self.bounds.size.width / 2.0f, sideHeight);
imageLayer.transform = CATransform3DConcat(CATransform3DMakeRotation(DEGREES_TO_RADIANS(60.0f), 1.0f, 0.0f, 0.0f),
CATransform3DMakeTranslation(0, sideA, -sideB));
imageLayer.contents = (id)[[UIImage imageNamed:#"IMG_0089.png"] CGImage];
imageLayer.borderColor = [UIColor greenColor].CGColor;
imageLayer.zPosition = distanceFromCenter;
imageLayer.borderWidth = 2.0f;
transformed.borderColor = [UIColor whiteColor].CGColor;
transformed.borderWidth = 2.0f;
[transformed addSublayer:imageLayer];
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, self.bounds.size.height / 2.0f, self.bounds.size.width, 2)];
[line setBackgroundColor:[UIColor redColor]];
[self addSubview:line];
line = [[UIView alloc] initWithFrame:CGRectMake(0, self.bounds.size.height * (1.0f / 4.0f), self.bounds.size.width, 2)];
[line setBackgroundColor:[UIColor blueColor]];
[self addSubview:line];
line = [[UIView alloc] initWithFrame:CGRectMake(0, self.bounds.size.height * (3.0f / 4.0f), self.bounds.size.width, 2)];
[line setBackgroundColor:[UIColor blueColor]];
[self addSubview:line];
}

Use also other transformation (I think you'll need translation) and concatenate them with rotation. This example even scales the image, so you get a scaled, shifted and rotated layer:
CATransform3D translate = CATransform3DMakeTranslation(yourShiftByX, yourShiftByY, 0);
CATransform3D scale = CATransform3DMakeScale(yourRateX, yourRateY, 1);
CATransform3D rotate = CATransform3DMakeRotation(DEGREES_TO_RADIANS(60.0f), 1.0, 0.0, 0.0);
CATransform3D transform = CATransform3DConcat(CATransform3DConcat(rotate, scale), translate);
// the order is important: FIRST we rotate, THEN we scale and AT LAST we position the object
// now apply 'transform' to your layer

you should make your imageView superView to show the perspective :
CATransform3D tranfrom = CATransform3DIdentity;
tranfrom.m34 = -1.0 / z;
imageView.superview.layer.transform = transfrom;

Related

Perspective issue in 3D Rotation

I want to achieve something like this:
Which is a snapshot of my view controller
The code I have tried is like this:
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
// Take a snapshot
//
_screenshotView = [[UIImageView alloc] initWithFrame:CGRectNull];
_screenshotView.image = [self getScreenSnapshot];
_screenshotView.frame = CGRectMake(-160, -284, _screenshotView.image.size.width, _screenshotView.image.size.height);
_screenshotView.userInteractionEnabled = YES;
_screenshotView.layer.anchorPoint = CGPointMake(0, 0);
_originalSize = _screenshotView.frame.size;
[window addSubview:_screenshotView];
[self minimizeFromRect:CGRectMake(0, 0, _originalSize.width, _originalSize.height)];
- (void)minimizeFromRect:(CGRect)rect
{
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CGFloat m = 0.7;
CGFloat newWidth = _originalSize.width * m;
CGFloat newHeight = _originalSize.height * m;
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:0.6] forKey:kCATransactionAnimationDuration];
[self addAnimation:#"position.x" view:_screenshotView startValue:rect.origin.x endValue:window.frame.size.width - 80.0];
[self addAnimation:#"position.y" view:_screenshotView startValue:rect.origin.y endValue:(window.frame.size.height - newHeight) / 2.0];
[self addAnimation:#"bounds.size.width" view:_screenshotView startValue:rect.size.width endValue:newWidth];
[self addAnimation:#"bounds.size.height" view:_screenshotView startValue:rect.size.height endValue:newHeight];
_screenshotView.layer.position = CGPointMake(window.frame.size.width - 80.0, (window.frame.size.height - newHeight) / 2.0);
_screenshotView.layer.bounds = CGRectMake(window.frame.size.width - 80.0, (window.frame.size.height - newHeight) / 2.0, newWidth, newHeight);
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -600;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, -20.0f * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
_screenshotView.layer.transform = rotationAndPerspectiveTransform;
[CATransaction commit];
}
The code works fine except that it looks like the rotation is shown from a different perspective because the snapshot after animation turns out to be like this:
What am I missing?
One way around could be that you can use two images, one for upper half and one for lower half. And you can stitch the two images together so that the gap is not visible and you can get the desired output.
Set the property to one positive and one negative value for upper and one lower image or vice verse. You can get the two images of desired result. Then you can set the frame to make them stick together so that they look as one image.
Property :: rotationAndPerspectiveTransform.m34 = 1.0 / 600.0;
Property :: rotationAndPerspectiveTransform.m34 = -1.0 / 600.0;
You might need to do some hit and trial.
Solved by replacing UIImageView object with CALayer image layer object.
Thanks to https://github.com/kristopherjohnson/perspectivetest
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
// Take a snapshot
//
UIImage *image = [self getScreenSnapshot];
CGImageRef pictureImage = image.CGImage;
CGFloat zDistance = 1200.0f;
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f / -zDistance;
_screenshotView = [[UIView alloc] initWithFrame:CGRectNull];
_screenshotView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
_screenshotView.userInteractionEnabled = YES;
_imageLayer = [CALayer layer];
_imageLayer.frame = _screenshotView.frame;;
_imageLayer.contents = (__bridge id)pictureImage;
_imageLayer.transform = transform;
[_screenshotView.layer addSublayer:_imageLayer];
_originalSize = _screenshotView.frame.size;
[window addSubview:_screenshotView];
[self minimizeFromRect:CGRectMake(0, 0, _originalSize.width, _originalSize.height)];
- (void)minimizeFromRect:(CGRect)rect
{
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CGFloat m = 0.6;
CGFloat newWidth = _originalSize.width * m;
CGFloat newHeight = _originalSize.height * m;
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:0.4] forKey:kCATransactionAnimationDuration];
[self addAnimation:#"position.x" view:_screenshotView startValue:rect.origin.x endValue:window.frame.size.width - 40.0];
[self addAnimation:#"position.y" view:_screenshotView startValue:rect.origin.y endValue:(window.frame.size.height - newHeight) / 2.0];
[self addAnimation:#"bounds.size.width" view:_screenshotView startValue:rect.size.width endValue:newWidth];
[self addAnimation:#"bounds.size.height" view:_screenshotView startValue:rect.size.height endValue:newHeight];
[self addAnimationForLayer:#"transform.rotation.y" view:_imageLayer startValue:0.0f endValue:-0.4 * M_PI];
_screenshotView.layer.position = CGPointMake(window.frame.size.width - 80.0, (window.frame.size.height - newHeight) / 2.0);
_screenshotView.layer.bounds = CGRectMake(window.frame.size.width - 80.0, (window.frame.size.height - newHeight) / 2.0, newWidth, newHeight);
[CATransaction commit];
}

draw embossed arc using core graphics

I am trying to implement a custom slider as shown in figure below.
what I have done so far looks something like this
please help me out for drawing the arc with such effect. my code is as below, what I am doing is drawing the arc using CGContextAddArc with line width kLineWidth.
- (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext: (CGContextRef)context {
UIGraphicsPushContext(context);
CGContextBeginPath(context);
CGContextMoveToPoint(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y);
CGImageRef imageRef = [UIImage imageNamed:#"circle25.png"].CGImage;
CGRect rect = CGRectMake(sliderButtonCenterPoint.x - kThumbRadius, sliderButtonCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2);
CGContextDrawImage(context, rect, imageRef);
//CGContextAddArc(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y, kThumbRadius, 0.0, 2*M_PI, NO);
CGContextFillPath(context);
UIGraphicsPopContext();
}
- (CGPoint)drawArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius inContext:(CGContextRef)context {
UIGraphicsPushContext(context);
CGContextBeginPath(context);
float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, M_PI/3);// 2*M_PI
CGFloat startAngle = (4*M_PI)/3;
CGFloat endAngle = startAngle + angleFromTrack;
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, NO);
CGPoint arcEndPoint = CGContextGetPathCurrentPoint(context);
CGContextStrokePath(context);
UIGraphicsPopContext();
return arcEndPoint;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint middlePoint;
middlePoint.x = self.bounds.origin.x + self.bounds.size.width/2;
middlePoint.y = self.bounds.origin.y + self.bounds.size.width;
CGContextSetLineWidth(context, kLineWidth);
CGFloat radius = [self sliderRadius];
[self.maximumTrackTintColor setStroke];
[self drawArcTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context];
[self.minimumTrackTintColor setStroke];
self.thumbCenterPoint = [self drawArcTrack:self.value atPoint:middlePoint withRadius:radius inContext:context];
[self.thumbTintColor setFill];
[self drawThumbAtPoint:self.thumbCenterPoint inContext:context];
}
Unless you are going to be changing the shape dynamically, you would probably be better off just creating the image in an image editor. I know it's easy to create that effect in Photoshop, Illustrator, or Fireworks.
That said, drawing an inner shadow like that with Core Graphics requires several steps:
Clip to the shape (using e.g. CGContextClip or CGContextClipToMask).
Make a path or mask of everything but the shape.
Set your shadow parameters (using CGContextSetShadowWithColor).
Fill the path or mask from step 2. This casts a shadow inside the shape, and only the shadow is drawn because you clipped to the shape in step 1.
If you do all of that correctly, you can get a nice result like this:
Here's the code I wrote to draw that. I wrote it in the drawRect: of a custom view subclass, but you can easily use this code to draw into any graphics context.
- (void)drawRect:(CGRect)rect {
CGContextRef gc = UIGraphicsGetCurrentContext();
First, I create a path that's just an arc:
static CGFloat const kArcThickness = 20.0f;
CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f);
CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds));
CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness);
UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO];
Next, I ask Core Graphics to make a new path that is the outline of the arc path. Note how I ask it for a stroke width of kArcThickness and round line caps:
CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
I also need the inverse of that path: a path that includes every point except the points in shape. It so happens (although I don't think it's documented) that CGContextCreateCopyByStrokingPath and CGPathAddRect draw in opposite directions. So if I copy shape and draw an enormous rectangle around it, the non-zero winding rule means that the new path will be the inverse of shape:
CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
Now I can actually start drawing. First, I'll fill in the shape with a light gray color:
CGContextBeginPath(gc);
CGContextAddPath(gc, shape);
CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor);
CGContextFillPath(gc);
Next I actually perform the four steps I listed above. I have to save the graphics state so I can undo the clipping and shadow parameters when I'm done.
CGContextSaveGState(gc); {
Step 1: clip to the shape:
CGContextBeginPath(gc);
CGContextAddPath(gc, shape);
CGContextClip(gc);
Step 2: Well, I did this step already when I created shapeInverse.
Step 3: I set the shadow parameters:
CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
Step 4: I fill the inverse shape from step 2:
CGContextBeginPath(gc);
CGContextAddPath(gc, shapeInverse);
CGContextFillPath(gc);
Now I restore the graphics state, which specifically restores the clipping path and unsets the shadow parameters.
} CGContextRestoreGState(gc);
Finally, I'll stroke shape with a light gray to make the edge crisper:
CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor);
CGContextSetLineWidth(gc, 1);
CGContextSetLineJoin(gc, kCGLineCapRound);
CGContextBeginPath(gc);
CGContextAddPath(gc, shape);
CGContextStrokePath(gc);
Of course I clean up when I'm done:
CGPathRelease(shape);
CGPathRelease(shapeInverse);
}
For more complex shapes, you can look at my answer here and my answer here.
Here's all the code together for easy copying:
- (void)drawRect:(CGRect)rect {
CGContextRef gc = UIGraphicsGetCurrentContext();
static CGFloat const kArcThickness = 20.0f;
CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f);
CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds));
CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness);
UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO];
CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
CGContextBeginPath(gc);
CGContextAddPath(gc, shape);
CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor);
CGContextFillPath(gc);
CGContextSaveGState(gc); {
CGContextBeginPath(gc);
CGContextAddPath(gc, shape);
CGContextClip(gc);
CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
CGContextBeginPath(gc);
CGContextAddPath(gc, shapeInverse);
CGContextFillPath(gc);
} CGContextRestoreGState(gc);
CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor);
CGContextSetLineWidth(gc, 1);
CGContextSetLineJoin(gc, kCGLineCapRound);
CGContextBeginPath(gc);
CGContextAddPath(gc, shape);
CGContextStrokePath(gc);
CGPathRelease(shape);
CGPathRelease(shapeInverse);
}
check the below code for fully functional version of the above component, its working (may be its bit messy).
#import "UIArcSlider.h"
#interface UIArcSlider()
#property (nonatomic) CGPoint thumbCenterPoint;
#pragma mark - Init and Setup methods
- (void)setup;
#pragma mark - Thumb management methods
- (BOOL)isPointInThumb:(CGPoint)point;
#pragma mark - Drawing methods
- (CGFloat)sliderRadius;
- (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context;
- (CGPoint)drawTheArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius AndColor:(Boolean) isWhite inContext:(CGContextRef)context;
- (CGPoint)drawTheLineTrack:(float)track withColor:(Boolean) isWhite inContext:(CGContextRef)context;
#end
#pragma mark -
#implementation UIArcSlider
#synthesize sliderStyle = _sliderStyle;
- (void)setSliderStyle:(UISliderStyle)sliderStyle {
if (sliderStyle != _sliderStyle) {
_sliderStyle = sliderStyle;
[self setNeedsDisplay];
}
}
#synthesize value = _value;
- (void)setValue:(float)value {
if (value != _value) {
if (value > self.maximumValue) { value = self.maximumValue; }
if (value < self.minimumValue) { value = self.minimumValue; }
_value = value;
[self setNeedsDisplay];
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
#synthesize minimumValue = _minimumValue;
- (void)setMinimumValue:(float)minimumValue {
if (minimumValue != _minimumValue) {
_minimumValue = minimumValue;
if (self.maximumValue < self.minimumValue) { self.maximumValue = self.minimumValue; }
if (self.value < self.minimumValue) { self.value = self.minimumValue; }
}
}
#synthesize maximumValue = _maximumValue;
- (void)setMaximumValue:(float)maximumValue {
if (maximumValue != _maximumValue) {
_maximumValue = maximumValue;
if (self.minimumValue > self.maximumValue) { self.minimumValue = self.maximumValue; }
if (self.value > self.maximumValue) { self.value = self.maximumValue; }
}
}
#synthesize thumbCenterPoint = _thumbCenterPoint;
/** #name Init and Setup methods */
#pragma mark - Init and Setup methods
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
- (void)setup {
self.value = 0.0;
self.minimumValue = 0.0;
self.maximumValue = 100.0;
self.thumbCenterPoint = CGPointZero;
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureHappened:)];
[self addGestureRecognizer:tapGestureRecognizer];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureHappened:)];
panGestureRecognizer.maximumNumberOfTouches = panGestureRecognizer.minimumNumberOfTouches;
[self addGestureRecognizer:panGestureRecognizer];
}
/** #name Drawing methods */
#pragma mark - Drawing methods
#define kLineWidth 22.0
#define kThumbRadius 20.0
#define kPadding 20.0
- (CGFloat)sliderRadius {
CGFloat radius = self.bounds.size.width;
radius -= MAX(kLineWidth + kPadding, kThumbRadius + kPadding);
return radius;
}
-(CGFloat)sliderWidth {
return self.bounds.size.width - kThumbRadius*2;
}
- (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context {
UIGraphicsPushContext(context);
CGContextBeginPath(context);
CGContextMoveToPoint(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y);
CGRect rect = CGRectMake(sliderButtonCenterPoint.x - kThumbRadius, sliderButtonCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2);
CGImageRef imageRef = [UIImage imageNamed:#"circle25.png"].CGImage;
CGContextDrawImage(context, rect, imageRef);
UIGraphicsPopContext();
}
- (CGPoint)drawTheArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius AndColor:(Boolean) isWhite inContext:(CGContextRef)context
{
static CGFloat const kArcThickness = kLineWidth;
CGPoint arcCenter = center;
CGFloat arcRadius = radius;
float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, M_PI/3);// 2*M_PI
CGFloat startAngle = (4*M_PI)/3;
CGFloat endAngle = startAngle + angleFromTrack;
UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
CGPoint arcEndPoint = [arc currentPoint];
CGContextBeginPath(context);
CGContextAddPath(context, shape);
if (isWhite) {
CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:.9 alpha:1].CGColor);
} else {//70,172, 220
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:70/255.0 green:172/255.0 blue:220/255.0 alpha:1].CGColor);
}
CGContextFillPath(context);
CGContextSaveGState(context); {
CGContextBeginPath(context);
CGContextAddPath(context, shape);
CGContextClip(context);
if (isWhite) {
CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
} else {
CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithRed:85/255.0 green:183/255.0 blue:230/255.0 alpha:.25].CGColor);
}
CGContextBeginPath(context);
CGContextAddPath(context, shapeInverse);
CGContextFillPath(context);
} CGContextRestoreGState(context);
if (isWhite) {
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:.75 alpha:1].CGColor);
} else {
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:62/255.0 green:135/255.0 blue:169/255.0 alpha:1].CGColor);
}
CGContextSetLineWidth(context, 1);
CGContextSetLineJoin(context, kCGLineCapRound);
CGContextBeginPath(context);
CGContextAddPath(context, shape);
CGContextStrokePath(context);
CGPathRelease(shape);
CGPathRelease(shapeInverse);
return arcEndPoint;
}
- (CGPoint)drawTheLineTrack:(float)track withColor:(Boolean) isWhite inContext:(CGContextRef)context
{
CGFloat sliderWidth = [self sliderWidth];
CGFloat xStart = self.bounds.origin.x + (self.bounds.size.width - sliderWidth)/2;
CGFloat lineRectX = (self.bounds.size.width - sliderWidth)/2;
CGFloat lineThickness = kLineWidth/2;
CGFloat lineRectY = (self.bounds.size.height - lineThickness)/2;
CGFloat xPoint = translateValueToPoint(track, xStart, self.maximumValue, sliderWidth);
sliderWidth = xPoint;
CGRect lineRect = CGRectMake(lineRectX, lineRectY, sliderWidth, lineThickness);
UIBezierPath *line = [UIBezierPath bezierPathWithRoundedRect:lineRect cornerRadius:lineThickness/2];
CGPathRef shape = CGPathCreateCopyByStrokingPath(line.CGPath, NULL, lineThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
CGPoint arcEndPoint = CGPointMake(lineRectX + xPoint, lineRectY + (lineThickness/2));
CGContextBeginPath(context);
CGContextAddPath(context, shape);
if (isWhite) {
CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:.9 alpha:1].CGColor);
} else {//70,172, 220
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:70/255.0 green:172/255.0 blue:220/255.0 alpha:1].CGColor);
}
CGContextFillPath(context);
CGContextSaveGState(context); {
CGContextBeginPath(context);
CGContextAddPath(context, shape);
CGContextClip(context);
if (isWhite) {
CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
} else {
CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithRed:85/255.0 green:183/255.0 blue:230/255.0 alpha:.25].CGColor);
}
CGContextBeginPath(context);
CGContextAddPath(context, shapeInverse);
CGContextFillPath(context);
} CGContextRestoreGState(context);
if (isWhite) {
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:.75 alpha:1].CGColor);
} else {
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:62/255.0 green:135/255.0 blue:169/255.0 alpha:1].CGColor);
}
CGRect outlineRect = CGRectMake(lineRectX - (lineThickness/2), lineRectY - (lineThickness/2), sliderWidth + lineThickness, lineThickness * 2);
UIBezierPath *outline = [UIBezierPath bezierPathWithRoundedRect:outlineRect cornerRadius:lineThickness];
CGContextSetLineWidth(context, 1);
CGContextSetLineJoin(context, kCGLineCapSquare);
CGContextBeginPath(context);
CGContextAddPath(context, outline.CGPath);
CGContextStrokePath(context);
CGPathRelease(shape);
CGPathRelease(shapeInverse);
return arcEndPoint;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint middlePoint;
switch (self.sliderStyle) {
case UISliderStyleLine:
[self drawTheLineTrack:self.maximumValue withColor:YES inContext:context];
self.thumbCenterPoint = [self drawTheLineTrack:self.value withColor:NO inContext:context];
[self drawThumbAtPoint:self.thumbCenterPoint inContext:context];
break;
case UISliderStyleArc:
default:
middlePoint.x = self.bounds.origin.x + self.bounds.size.width/2;
middlePoint.y = self.bounds.origin.y + self.bounds.size.width;
CGFloat radius = [self sliderRadius];
[self drawTheArcTrack:self.maximumValue atPoint:middlePoint withRadius:radius AndColor:YES inContext:context];
self.thumbCenterPoint = [self drawTheArcTrack:self.value atPoint:middlePoint withRadius:radius AndColor:NO inContext:context];
[self drawThumbAtPoint:self.thumbCenterPoint inContext:context];
break;
}
}
/** #name Thumb management methods */
#pragma mark - Thumb management methods
- (BOOL)isPointInThumb:(CGPoint)point {
CGRect thumbTouchRect = CGRectMake(self.thumbCenterPoint.x - kThumbRadius, self.thumbCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2);
return CGRectContainsPoint(thumbTouchRect, point);
}
/** #name UIGestureRecognizer management methods */
#pragma mark - UIGestureRecognizer management methods
- (void)panGestureHappened:(UIPanGestureRecognizer *)panGestureRecognizer {
CGPoint tapLocation = [panGestureRecognizer locationInView:self];
switch (panGestureRecognizer.state) {
case UIGestureRecognizerStateChanged: {
CGFloat radius;
CGPoint sliderCenter;
CGPoint sliderStartPoint;
CGFloat angle;
CGFloat xStart;
CGFloat maximumValue;
CGFloat sliderWidth;
CGFloat point;
switch (self.sliderStyle) {
case UISliderStyleLine:
sliderWidth = [self sliderWidth];
xStart = self.bounds.origin.x + (self.bounds.size.width - sliderWidth)/2;
maximumValue = self.maximumValue;
point = tapLocation.x;
self.value = translatePointToValue(point, xStart, maximumValue, sliderWidth);
break;
case UISliderStyleArc:
default:
radius = [self sliderRadius];
sliderCenter = CGPointMake(self.bounds.size.width/2, self.bounds.size.width);
sliderStartPoint = CGPointMake(sliderCenter.x - (radius * 1.15), sliderCenter.y - (radius * 2.1) );
angle = angleBetweenThreePoints(sliderCenter, sliderStartPoint, tapLocation);
if (angle < 0) {
angle = -angle;
}
else {
angle = 0.0;
//angle = M_PI/3 - angle;
}
self.value = translateValueFromSourceIntervalToDestinationInterval(angle, 0, M_PI/3, self.minimumValue, self.maximumValue);
break;
}
}
default:
break;
}
}
- (void)tapGestureHappened:(UITapGestureRecognizer *)tapGestureRecognizer {
if (tapGestureRecognizer.state == UIGestureRecognizerStateEnded) {
CGPoint tapLocation = [tapGestureRecognizer locationInView:self];
if ([self isPointInThumb:tapLocation]) {
// do something on tap
}
else {
}
}
}
#end
/** #name Utility Functions */
#pragma mark - Utility Functions
float translateValueFromSourceIntervalToDestinationInterval(float sourceValue, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum) {
float a, b, destinationValue;
a = (destinationIntervalMaximum - destinationIntervalMinimum) / (sourceIntervalMaximum - sourceIntervalMinimum);
b = destinationIntervalMaximum - a*sourceIntervalMaximum;
destinationValue = a*sourceValue + b;
return destinationValue;
}
float translateValueToPoint(float value, float xStart, float maximum, float width)
{
float point = (width * value) / maximum;
//
// point = xStart + point;
//
return point;
}
float translatePointToValue(float point, float xStart, float maximum, float width)
{
float value = (point) * maximum;
value = value / width;
return value;
}
CGFloat angleBetweenThreePoints(CGPoint centerPoint, CGPoint p1, CGPoint p2) {
CGPoint v1 = CGPointMake(p1.x - centerPoint.x, p1.y - centerPoint.y);
CGPoint v2 = CGPointMake(p2.x - centerPoint.x, p2.y - centerPoint.y);
CGFloat angle = atan2f(v2.x*v1.y - v1.x*v2.y, v1.x*v2.x + v1.y*v2.y);
return angle;
}
You'll require the thumb image as well

Why are CALayers stacking ontop of each other as I scroll my UITableView?

I have a UITableViewCell with a UIImage that I'm drawing. As I scroll, I get a ton of sublayers getting added which makes performance really jerky. How can I make sure my CALayer only gets added once?
- (void) drawContentView:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor whiteColor] set];
CGContextFillRect(context, rect);
NSString* caption = [NSString stringWithFormat:#"%#",[info objectForKey:#"caption"]];
NSString* text = [info stringForKey:#"text"];
CGFloat widthr = self.frame.size.width - 70;
[[UIColor grayColor] set];
[text drawInRect:CGRectMake(63.0, 25.0, widthr, 20.0) withFont:system14 lineBreakMode:UILineBreakModeTailTruncation];
if (self.image) {
UIImage *imageToDisplay;
imageToDisplay = self.image;
imageToDisplay = [self imageWithImage:imageToDisplay scaledToSize:CGSizeMake(imageToDisplay.size.width / 1.5, imageToDisplay.size.height / 1.5)];
CGFloat width;
CGFloat height;
CGRect r;
if (imageToDisplay.size.width < 310 && imageToDisplay.size.height > 290) {
imageToDisplay = [self imageByCropping:imageToDisplay toRect:CGRectMake(0, 20, imageToDisplay.size.width, 270)];
}
else if (imageToDisplay.size.width > 310 && imageToDisplay.size.height < 20) {
imageToDisplay = [self imageByCropping:imageToDisplay toRect:CGRectMake(30, 0, 290, imageToDisplay.size.height)];
}
else {
if (![caption isEqualToString:#""]) {
imageToDisplay = [self imageByCropping:imageToDisplay toRect:CGRectMake(30, 0, 290, 230)];
}
else {
imageToDisplay = [self imageByCropping:imageToDisplay toRect:CGRectMake(30, 0, 290, 270)];
}
}
width = imageToDisplay.size.width;
height = imageToDisplay.size.height;
r = CGRectMake(5.0, 5.0, width, height);
//[imageToDisplay drawInRect:r];
CALayer *sublayer = [CALayer layer];
sublayer.contents = (id)imageToDisplay.CGImage;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectMake(5.0, 5.0, imageToDisplay.size.width, imageToDisplay.size.height);
[self.layer addSublayer:sublayer];
//Experimental shadow stuff with images
/*CALayer *layer = [CALayer layer];
layer = [CALayer layer];
layer.bounds = CGRectMake(5.0, 5.0, imageToDisplay.size.width, imageToDisplay.size.height);
layer.position = CGPointMake(150, 140);
layer.contents = (id)imageToDisplay.CGImage;
layer.shadowOffset = CGSizeMake(0, 2);
layer.shadowOpacity = 0.70;
[self.layer addSublayer:layer];
[self bezierPathWithCurvedShadowForRect:layer.bounds];*/
[[UIColor blackColor] set];
[caption drawInRect:CGRectMake(10.0, height + 20 , widthr, 20.0) withFont:system14 lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentCenter];
}
}
drawContentView: is not the right method from which to call addSublayer:. You should add that layer at the point when you construct your object with an image, or when you set an image on an existing object. Alternatively, you can draw the image yourself in the drawContentView:, but it is probably not going to be as fast.

How to rotate an UIImageView with CATransform3DRotate make an effect like Door Opening?

I've read and try this article (Opening door effect using Core Animation)
And I implement following code in my app:
CALayer *layer = threeHomeView.layer;
CATransform3D initialTransform = threeHomeView.layer.transform;
initialTransform.m34 = 1.0 / -900;
[UIView beginAnimations:#"Scale" context:nil];
[UIView setAnimationDuration:3];
layer.transform = initialTransform;
CATransform3D rotationAndPerspectiveTransform = threeHomeView.layer.transform;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform
, 40.0f * M_PI / 180.0f
, 0.0f
, 1.0f
, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(threeHomeFlyOut)];
[UIView commitAnimations];
threeHomeView is an UIImageView.
MY QUESTION IS: the image can only rotate by middle vertical line of the image, but I want it to rotate by left vertical line of the image.
Use CATransform3DRotate like you did in combination with CATransform3DTranslate to rotate around the edge, like I did in this answer. Here's a little excerpt (you will need to modify this for your own uses):
CATransform3D t = CATransform3DIdentity;
t = CATransform3DTranslate(t, 0, -self.view.bounds.size.height/2, 0);
t = CATransform3DRotate(t, rec.scale * M_PI, 1, 0, 0);
t = CATransform3DTranslate(t, 0, -self.view.bounds.size.height/2, 0);
self.view.layer.transform = t;
- (void)awakeFromNib {
transformed = [CALayer layer];
transformed.frame = self.bounds;
[self.layer addSublayer:transformed];
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(10.0f, 4.0f, 300.0f, 226.0f);
imageLayer.transform = CATransform3DMakeRotation(20.0f * M_PI / 180.0f,
1.0f, 0.0f, 0.0f);
imageLayer.contents = (id)[[UIImage imageNamed:#"IMG_0051.png"] CGImage];
[transformed addSublayer:imageLayer];
imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(10.0f, 234.0f, 300.0f, 226.0f);
imageLayer.transform = CATransform3DMakeRotation(-20.0f * M_PI / 180.0f,
1.0f, 0.0f, 0.0f);
imageLayer.contents = (id)[[UIImage imageNamed:#"IMG_0089.png"] CGImage];
[transformed addSublayer:imageLayer];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
previousLocation = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInView:self];
// BJL: The following is the code I used in Molecules to do 3-D rotation
CATransform3D currentTransform = transformed.sublayerTransform;
CGFloat displacementInX = location.x - previousLocation.x;
CGFloat displacementInY = previousLocation.y - location.y;
CGFloat totalRotation = sqrt(displacementInX * displacementInX + displacementInY * displacementInY);
CATransform3D rotationalTransform = CATransform3DRotate(currentTransform, totalRotation * M_PI / 180.0,
((displacementInX/totalRotation) * currentTransform.m12 + (displacementInY/totalRotation) * currentTransform.m11),
((displacementInX/totalRotation) * currentTransform.m22 + (displacementInY/totalRotation) * currentTransform.m21),
((displacementInX/totalRotation) * currentTransform.m32 + (displacementInY/totalRotation) * currentTransform.m31));
previousLocation = location;
transformed.sublayerTransform = rotationalTransform;
}

TableView oval button for Index/counts

Can someone help me create an index/count button for a UITableView, like this one?
iTunes http://img.skitch.com/20091107-nwyci84114dxg76wshqwgtauwn.preview.jpg
Is there an Apple example, or other tutorial? Thanks, Jordan
Wow... aaa... ok... I've got an easier way:
#import <QuartzCore/QuartzCore.h>
.....
UILabel *label = [[UILabel alloc] initWithFrame:
CGRectMake(cell.contentView.frame.size.width - 50, 0, 35, 35)];
label.layer.cornerRadius = 5;
label.backgroundColor = [UIColor blueColor]; //feel free to be creative
label.clipToBounds = YES;
label.text = #"7"; //Your text here
[cell.contentView addSubview: label];
[label release];
Basically, you're making a UILabel with rounded corners using the QuartzCore framework - don't forget to include it. Extra note: it only works on OS > 3.0.
You need to create a custom view, and then draw the oval and number in manually. Finally, assign that custom view as the accessory view of the cell. Here's the drawing code, using Core Graphics. It's not too tricky:
CGRect bounds = self.bounds;
CGContextRef context = UIGraphicsGetCurrentContext();
float radius = bounds.size.height / 2.0;
NSString *countString = [NSString stringWithFormat: #"%d", _count];
if (_count < 100) bounds = CGRectMake(5, 0, bounds.size.width - 10, bounds.size.height);
CGContextClearRect(context, bounds);
CGContextSetFillColorWithColor(context, _color.CGColor);
CGContextBeginPath(context);
CGContextAddArc(context, radius + bounds.origin.x, radius, radius, M_PI / 2 , 3 * M_PI / 2, NO);
CGContextAddArc(context, (bounds.size.width + bounds.origin.x) - radius, radius, radius, 3 * M_PI / 2, M_PI / 2, NO);
CGContextClosePath(context);
CGContextFillPath(context);
[[UIColor whiteColor] set];
UIFont *font = [UIFont boldSystemFontOfSize: 14];
CGSize numberSize = [countString sizeWithFont: font];
bounds.origin.x += (bounds.size.width - numberSize.width) / 2;
[countString drawInRect: bounds withFont: font];