I'm trying to create clockwise text in Xcode. My current coding is just displaying like that.
But I want to change outer text to as follow
By using this library : https://github.com/javenisme/CurvaView
CoreTextArcView * cityLabel = [[[CoreTextArcView alloc] initWithFrame:rect1
font:font1
text:#"Hello this is radious arc with text"
radius:85
arcSize:110
color:color1] autorelease];
Remove arc for one file like as follows (set -fno-objc-arc to that library's .m file from build phases of your project target) :
Output like this
This is my code to do this, it draw attributed strings.
I can't post images but the result it's just right.
Text is also automatically reversed if the angle is between 100° and 260°
#define degreesToRadians(degrees) (( degrees ) / 180.0 * M_PI )
#define radiansToDegrees(radians) (( radians ) * ( 180.0 / M_PI ) )
+ (void)drawCurvedStringOnLayer:(CALayer *)layer
withAttributedText:(NSAttributedString *)text
atAngle:(float)angle
withRadius:(float)radius {
// angle in radians
CGSize textSize = CGRectIntegral([text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil]).size;
float perimeter = 2 * M_PI * radius;
float textAngle = (textSize.width / perimeter * 2 * M_PI); // text curved width
float textRotation;
float textDirection;
if (angle > degreesToRadians(10) && angle < degreesToRadians(170))
{
//down
textRotation = 0.5 * M_PI ;
textDirection = - 2 * M_PI;
angle += textAngle / 2 + degreesToRadians(6);
}
else
{
//up
textRotation = 1.5 * M_PI ;
textDirection = 2 * M_PI;
angle -= textAngle / 2; // + degreesToRadians(6);
}
for (int c = 0; c < text.length; c++)
{
NSRange range = {c, 1};
NSAttributedString* letter = [text attributedSubstringFromRange:range];
CGSize charSize = CGRectIntegral([letter boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil]).size;
float letterAngle = ( (charSize.width / perimeter) * textDirection );
float x = radius * cos(angle + (letterAngle/2));
float y = radius * sin(angle + (letterAngle/2));
//NSLog(#"Char %# with size: %f x %f", letter, charSize.width, charSize.height);
//NSLog(#"Char %# with angle: %f x: %f y: %f", letter, angle, x, y);
CATextLayer *text = [layers drawTextOnLayer:layer
withText:letter
frame:CGRectMake(layer.frame.size.width/2 - charSize.width/2 + x,
layer.frame.size.height/2 - charSize.height/2 + y,
charSize.width, charSize.height)
bgColor:nil
opacity:1];
text.transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeRotation(angle - textRotation) );
angle += letterAngle;
}
}
+ (CATextLayer *)drawTextOnLayer:(CALayer *)layer
withText:(NSAttributedString *)text
frame:(CGRect)frame
bgColor:(UIColor *)bgColor
opacity:(float)opacity {
CATextLayer *textLayer = [[CATextLayer alloc] init];
textLayer.needsDisplayOnBoundsChange = true;
//[textLayer setFont:(__bridge CFTypeRef)(font)];
//[textLayer setFontSize:fontSize];
[textLayer setFrame:frame];
[textLayer setString:text];
[textLayer setAlignmentMode:kCAAlignmentCenter];
// [textLayer setForegroundColor:color.CGColor];
[textLayer setBackgroundColor:bgColor.CGColor];
[textLayer setContentsScale:[UIScreen mainScreen].scale];
[textLayer setOpacity:opacity];
[layer addSublayer:textLayer];
}
Related
I am creating a circular slider menu, I am using coregraphics to draw the control UI, Here I am trying to draw words on the circumference of a circle. But i cant able to make the spaces equally.
Here is my code and output. Please help me to give equal spaces between each word.
In drawRect
float angleStep = 2 * M_PI / [menuArray count];
float angle = degreesToRadians(90);
textRadius = textRadius - 12;
for (NSString* text in`enter code here` titleArray)
{
[self drawStringAtContext:ctx string:text atAngle:angle withRadius:textRadius];
angle -= angleStep;
}
//////////
- (void) drawStringAtContext:(CGContextRef) context string:(NSString*) text atAngle:(float)angle withRadius:(float) radis
{
CGSize textSize = [text sizeWithFont:[UIFont systemFontOfSize:11.0]];
float perimeter = 2 * M_PI * radis;
float textAngle = textSize.width / perimeter * 2 * M_PI;
angle += textAngle / 2;
for (int index = 0; index < [text length]; index++)
{
NSRange range = {index, 1};
NSString* letter = [text substringWithRange:range];
char* c = (char*)[letter cStringUsingEncoding:NSASCIIStringEncoding];
CGSize charSize = [letter sizeWithFont:[UIFont systemFontOfSize:11.0]];
float x = radis * cos(angle);
float y = radis * sin(angle);
float letterAngle = (charSize.width / perimeter * -2 * M_PI);
CGContextSaveGState(context);
CGContextTranslateCTM(context, x, y);
CGContextRotateCTM(context, (angle - 0.5 * M_PI));
CGContextShowTextAtPoint(context, 0, 0, c, strlen(c));
CGContextRestoreGState(context);
angle += letterAngle;
}
}
Output
I am trying hands at Core Animation on iphone.
Details of what I have done so far:
I am using layers and CAKeyFrameAnimation using path.
I have created a layer with Contents set to a bitmap file of a fly which I want to animate on a spiral path. The centre point of the spiral path is at CGPoint (150,150). The end point of the spiral path is a radius of 100.0f.
What I want to achieve:
I further want to increase the radius to a value so that the spiral can go beyond the bounds of the view frame, but when it reaches the bound, I desire the fly to trace back the path.
EDIT: (adding code):
-(IBAction) clickToAnimate:(id) sender
{
//create a path to animate the fly
CGFloat minRadius = 5.0f;
CGFloat maxRadius = 100.0f;
CGFloat radiusOffset = 5.0f;
float i;
int count =1;
int remainder;
curvePath = CGPathCreateMutable();
//this looping mimics the "spiral path"
for (i = minRadius; i <= maxRadius; i = i + 5.0f)
{
remainder = count % 2;
if (remainder == 0)
{
CGPathAddArc(curvePath, NULL, currentPoint.x, currentPoint.y, i, M_PI / 2, 1.5 * M_PI, NO);
}
else
{
CGPathAddArc(curvePath, NULL, currentPoint.x, currentPoint.y + radiusOffset, i, 1.5 * M_PI , M_PI / 2, NO);
}
count = count +1;
}
//add timer
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(checkCoordinatesOnTimer) userInfo:self repeats:YES];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.calculationMode = kCAAnimationPaced;
animation.path = curvePath;
animation.duration = 25.0f;
animation.autoreverses = YES;
animation.fillMode = kCAFillModeFrozen;
CGPathRelease(curvePath);
[animationLayer addAnimation:animation forKey:#"position"];
}
Approach taken:
I set up a timer, which would check the co-ordinates of the animation layer every half second. The co-ordinates would be checked against the bounding x and y co-ordinates. If the co-ordinates are found to cross the boundary, we will disable the timer and start with the reverse animation process.
The reverse animation process would first check the angle from horizontal at which the animation is when it crosses the boundary. Using this angle and radius from center point, create a path for reverse animation. Details can be found in code below.
- (void) checkCoordinatesOnTimer
{
if (!presentLayer)
{
presentLayer =[[CALayer alloc] init];
}
presentLayer = [animationLayer presentationLayer];
CGPoint curPt;
curPt.x = presentLayer.position.x;
curPt.y = presentLayer.position.y;
currRadius = sqrt(pow(fabsf(curPt.x - centerPoint.x), 2) + pow(fabsf(curPt.y - centerPoint.y), 2));
if ((curPt.x >= CGRectGetMaxX(self.view.frame)) || (curPt.y >= CGRectGetMaxY(self.view.frame)))
{
[timer invalidate];
[self reverseAnimation:curPt];
}
}
-(void) reverseAnimation:(CGPoint)curPoint
{
CGFloat angle = (CGFloat)[self calculateAngleInRadians:curPoint];
maxRadius = currRadius;
float i;
int count =1;
int remainder;
BOOL firstLap = YES;
CGPathRelease(curvePath);
curvePath = CGPathCreateMutable();
for (i = maxRadius; i >= minRadius; i = i - 5.0f)
{
remainder = count % 2 ;
if (firstLap)
{
if ((angle >= (M_PI/2)) && (angle <= 1.5 *M_PI))
//2nd and third quard,
//angle to change from final angle to 90
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, angle, 1.5 * M_PI, FALSE);
}
else
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, angle, M_PI / 2 , FALSE);
}
firstLap = NO;
continue;
}
else
{
if (remainder == 0)
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, 1.5* M_PI, M_PI / 2, FALSE);
}
else
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y + radiusOffset, i, M_PI / 2 ,1.5* M_PI, FALSE);
}
}
count = count +1;
}
animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.calculationMode =kCAAnimationPaced; // for even pacing of animation in segments.
animation.path = curvePath;
animation.duration = 40.0f;
animation.fillMode = kCAFillModeForwards;
CGPathRelease(curvePath);
[animationLayer addAnimation:animation forKey:#"position"];
}
-(CGFloat) calculateAngleInRadians:(CGPoint) pt
{
CGFloat xLen = fabsf(centerPoint.x - pt.x);
CGFloat yLen = fabsf(centerPoint.y - pt.y);
double value = yLen/xLen;
double angle = atan(value);
double angleInDeg = ( 180 * angle/ M_PI);
double finalAngle;
if ((pt.x > centerPoint.x) || (pt.y > centerPoint.y)) {
finalAngle = angle;
}
else if ((centerPoint.x > pt.x) || (pt.y > centerPoint.y))
{
finalAngle = M_PI/2 + angle;
}
else if ((centerPoint.x > pt.x) || (centerPoint.y > pt.y))
{
finalAngle = M_PI + angle;
}
else {
finalAngle = (1.5 * M_PI) + angle;
}
return finalAngle;
}
I'm trying to animate a view from the center of the screen to the upper left corner in a spiral-type motion. Here's what I'm doing:
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, viewOrigin.x, viewOrigin.y);
controlX1 = viewOrigin.x + halfScreenWidth*0.3;
controlY1 = viewOrigin.y + halfScreenHeight*0.1;
controlX2 = viewOrigin.x - halfScreenWidth*0.05;
controlY2 = viewOrigin.y + halfScreenHeight*0.5;
controlX3 = viewOrigin.x - halfScreenWidth*0.4;
controlY3 = viewOrigin.y;
rndX1 = viewOrigin.x + halfScreenWidth*.1;
rndX2 = viewOrigin.x - halfScreenWidth*.40;
rndY1 = viewOrigin.y + halfScreenHeight*.10;
rndY2 = viewOrigin.y + halfScreenHeight*.35;
}
CGPathAddQuadCurveToPoint(curvedPath, NULL, controlX1, controlY1, rndX1, rndY1);
CGPathAddQuadCurveToPoint(curvedPath, NULL, controlX2, controlY2, rndX2, rndY2);
CGPathAddQuadCurveToPoint(curvedPath, NULL, controlX3, controlY3, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
[group setAnimations:[NSArray arrayWithObjects: pathAnimation, nil]];
group.duration = 3.0f;
group.delegate = self;
[group setValue:self.theView forKey:#"imageViewBeingAnimated"];
The view moves to each point on the curve, but it's not smooth, and not very curvy either. It just moves in pretty much a straight line to each point. Why isn't the path curved? I'm using this as a reference.
If you want a more smooth spiral, you could use the following function. It' s possible to adjust the code to have an opening spiral also.
-(void)addEnteringSpiralOnPath: (CGMutablePathRef) path
size: (CGSize)size
turns: (int)turns
{
CGPathMoveToPoint(path, NULL, size.width/2, 0); //To begining: Right
for (int turn = turns; turn >= 1; turn--) {
CGSize largeSize = CGSizeMake(size.width * turn/turns, size.height * turn/turns);
CGSize smallSize = CGSizeMake(size.width * (turn-1) / turns, size.height * (turn-1) / turns);
NSLog(#"Large: %#; Small: %#", NSStringFromCGSize(largeSize), NSStringFromCGSize(smallSize));
CGFloat wStep = (largeSize.width/2 - smallSize.width/2 ) / 360;
CGFloat hStep = (largeSize.height/2 - smallSize.height/2 ) / 360;
for (CGFloat i = 0; i<=360; i = i + 10) {
CGFloat iRad = i * M_PI / 180.0f;
CGPoint p = CGPointMake(cosf(iRad) * (largeSize.width/2 - wStep * i),
sinf(iRad) * (largeSize.height/2 - hStep * i));
CGPathAddLineToPoint(path, nil, p.x, p.y);
}
}
}
-(void)addOpenningSpiralOnPath: (CGMutablePathRef) path
size: (CGSize)size
turns: (int)turns
{
CGPathMoveToPoint(path, NULL, 0, 0); //To begining: Center
for (int turn = 1; turn <= turns; turn++) {
CGSize largeSize = CGSizeMake(size.width * turn/turns, size.height * turn/turns);
CGSize smallSize = CGSizeMake(size.width * (turn-1) / turns, size.height * (turn-1) / turns);
NSLog(#"Large: %#; Small: %#", NSStringFromCGSize(largeSize), NSStringFromCGSize(smallSize));
CGFloat wStep = (largeSize.width/2 - smallSize.width/2 ) / 360;
CGFloat hStep = (largeSize.height/2 - smallSize.height/2 ) / 360;
for (CGFloat i = 0; i<=360; i = i + 10) {
CGFloat iRad = i * M_PI / 180.0f;
CGPoint p = CGPointMake(cosf(iRad) * (smallSize.width/2 + wStep * i),
sinf(iRad) * (smallSize.height/2 + hStep * i));
CGPathAddLineToPoint(path, nil, p.x, p.y);
}
}
}
Then, you will just need a simple code like that to make the path:
CGMutablePathRef pathToDraw = CGPathCreateMutable();
[self addEnteringSpiralOnPath:pathToDraw size:CGSizeMake(400, 400) turns:3];
I've downloaded Menu Path 2.0 from https://github.com/yourabi/PathMenuExample/downloads.
The "Add" button make expanding & collapsing an array of menu items (animated menu drawn along a curve).
But I want to make those button expanding/collapsing in a straight line.
Here is code
ExpandableNavigation.m:
- (void) expand {
transition = YES;
[UIView animateWithDuration:self.speed animations:^{
self.mainButton.transform = CGAffineTransformMakeRotation( 45.0 * M_PI/180 );
}];
for (UIView* view in self.menuItems) {
int index = [self.menuItems indexOfObject:view];
CGFloat oneOverCount = self.menuItems.count + 50<=1?1.0:(1.0/(self.menuItems.count-1));
CGFloat indexOverCount = index *oneOverCount;
CGFloat rad =(1.0 - indexOverCount) * 90.0 * M_PI/180;
CGAffineTransform rotation = CGAffineTransformMakeRotation( rad ) ;
CGFloat x = (self.radius + self.bounce * self.radius ) * rotation.a;
CGFloat y = (self.radius + self.bounce * self.radius ) * rotation.c;
CGPoint center = CGPointMake( view.center.x + x , view.center.y + y);
For anyone needs in future, change the angle with your desired angle of transformation.
- (void) expand {
transition = YES;
[UIView animateWithDuration:self.speed animations:^{
self.mainButton.transform = CGAffineTransformMakeRotation( 45.0 * M_PI/180 );
}];
for (UIView* view in self.menuItems) {
int index = [self.menuItems indexOfObject:view];
CGFloat oneOverCount = self.menuItems.count + 50<=1?1.0:(1.0/(self.menuItems.count-1));
CGFloat indexOverCount = index *oneOverCount;
CGFloat rad =(1.0 - indexOverCount) * 90.0 * M_PI/180;
CGAffineTransform rotation = CGAffineTransformMakeRotation( rad ) ;
CGFloat x = yourDesired_X; //should be same for all menu items
CGFloat y = (view.frame.size.hieght+8.0)*index; //Y should be increasing or decreasing for every item
CGPoint center = CGPointMake(x , y);
In my app, I'm the game is set for landscape mode allowing the user to be able to flip the device with the game auto-rotating to match the angle. When the user tilts the device to its right, the hero will travel to the right, and when tilted to the left, the hero goes left.
I have that part figured out, but the problem comes in when the user flips the iDevice over (because of the headphone jack or placement of the speaker), the app is auto-rotated, but the rollingY is setting the hero's position.x to be inverted. Tilting left makes hero go right, and tilting right makes hero go left. After looking at the NSLogs of the accelY value when flipping around the iDevice, it doesn't seem possible to be able to reverse the position.x when rotated?
I guess this is more of a math question.
Here is my code:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
#define kFilteringFactor 0.75
static UIAccelerationValue rollingX = 0, rollingY = 0, rollingZ = 0;
rollingX = (acceleration.x * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
rollingY = (acceleration.y * kFilteringFactor) + (rollingY * (1.0 - kFilteringFactor));
rollingZ = (acceleration.z * kFilteringFactor) + (rollingZ * (1.0 - kFilteringFactor));
float accelX = rollingX;
float accelY = rollingY;
float accelZ = rollingZ;
NSLog(#"accelX: %f, accelY: %f, accelZ: %f", accelX, accelY, accelZ);
CGSize winSize = [CCDirector sharedDirector].winSize;
#define kRestAccelX 0.6
#define kShipxMaxPointsPerSec (winSize.height * 1.0)
#define kMaxDiffX 0.2
#define kRestAccelY 0.0
#define kShipyMaxPointsPerSec (winSize.width * 0.5)
#define kMaxDiffY 0.2
float accelDiffX = kRestAccelX - ABS(accelX);
float accelFractionX = accelDiffX / kMaxDiffX;
float pointsPerSecX = kShipxMaxPointsPerSec * accelFractionX;
float accelDiffY = -accelY;
float accelFractionY = accelDiffY / kMaxDiffY;
float pointsPerSecY = kShipyMaxPointsPerSec * accelFractionY;
_shipPointsPerSecX = pointsPerSecY;
_shipPointsPerSecY = pointsPerSecX;
CCLOG(#"accelX: %f, pointsPerSecX: %f", accelX, pointsPerSecX);
CCLOG(#"accelY: %f, pointsPerSecY: %f", accelY, pointsPerSecY);
}
-(void)updateShipPos:(ccTime)dt
{
CGSize winSize = [CCDirector sharedDirector].winSize;
float maxX = winSize.width - _ship.contentSize.width;
float minX = _ship.contentSize.width / 2;
float maxY = winSize.height - _ship.contentSize.height / 2;
float minY = _ship.contentSize.height / 2;
float newX = _ship.position.x + (_shipPointsPerSecX * dt);
float newY = _ship.position.y + (_shipPointsPerSecY * dt);
newX = MIN(MAX(newX, minX), maxX);
newY = MIN(MAX(newY, minY), maxY);
_ship.position = ccp(newX, newY);
}
The idea is, I'm trying to make it so that pointsPerSecY is positive when tilting to the right and negative when tilted to the left. The problem is when the user flips the device, they are inverted since the device is flipped. Is there a way to make it so that it will stay positive when tilted to the right and negative to the left, no matter the orientation, when flipped while being in landscape?
How about to do something like this?
if ([[UIApplication sharedApplication] statusBarOrientaion]==UIInterfaceOrientationLandscapeLeft) {
pointsPerSecY *= -1;
}