Based on an earlier stackoverflow suggestion, I am trying to create a playing card using CALayers added to a CATransformLayer. The idea is to create front and back CALayers and combine them in the CATransformLayer which can then be rotated, flipped, etc, with the proper side automatically showing. Below is example code trying to create a single card with a green front and a red back.
The red back is flipped about the Y-axis so that it is facing away from the green front. The green front calayer has a "higher" Z location than the red back.
When I do the transform though, I am simply seeing the card in what appears to be its non-flipped state. Any ideas where I am going wrong here?
#define DEGREES_TO_RADIANS(angle) (angle * M_PI / 180.0)
- (void)viewDidLoad
{
[super viewDidLoad];
CATransformLayer *cardContainer = [CATransformLayer layer];
cardContainer.bounds = CGRectMake(0,0, 150,200);
CALayer *cardFront = [CALayer layer];
cardFront.frame = cardContainer.bounds;
cardFront.backgroundColor = [UIColor greenColor].CGColor;
cardFront.borderColor = [UIColor blackColor].CGColor;
cardFront.borderWidth = 2.0;
cardFront.cornerRadius = 30.0;
cardFront.zPosition = 2; // Put front of card on top relative to back of card
cardFront.doubleSided = NO;
[cardContainer addSublayer:cardFront];
CALayer *cardBack = [CALayer layer];
cardBack.frame = cardContainer.bounds;
cardBack.backgroundColor = [UIColor redColor].CGColor;
cardBack.zPosition = 1;
cardBack.doubleSided = NO;
// Flip cardBack image so it is facing outward and visible when flipped
cardBack.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(180),0.0,1.0,0.0);
[cardContainer addSublayer:cardBack];
UIView* cardView = [[UIView alloc] initWithFrame:cardContainer.bounds];
cardView.center = CGPointMake(self.view.center.x, self.view.center.y);
[cardView.layer addSublayer:cardContainer];
[self.view addSubview:cardView];
// Show the card flipped over (desired the red side to be showing, but instead shows green)
cardView.layer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(180),0.0,1.0,0.0);
}
Found the problem, you must apply the transform to the sublayerTransform property, as in:
cardView.layer.sublayerTransform = ...
Related
I am new to iOS programming. Please help me to sort out the following issue
I have one set of view controller(.h, .m and .XIB)
I have one set of view(.h&.m)
3.The view class is responsible for drawing gauge using
-->drawRect
-->CALayer and sublayers
In this view I have initialize method, and this method only i set the bounds for my layers and sublayers
here i am pasting some sort of code..
- (void)initialize {
CGFloat span = fmin(self.frame.size.width, self.frame.size.height);
frameLayerObject_ = [[OuterFrameLayer alloc] init];
frameLayer = [[CALayer layer] retain];
frameLayer.bounds = CGRectMake(250, 50, 710, 320);
frameLayer.position = self.frame.origin;
frameLayer.needsDisplayOnBoundsChange = YES;
frameLayer.delegate = self.frameLayerObject;
[self.layer addSublayer:frameLayer];
[frameLayer setNeedsDisplay];
self.minValue = 0.0;
self.maxValue = 100.0;
self.tickStartAngle = M_PI;
self.tickArcLength = M_PI
self.lineWidth = span / 200.0;
self.tickLength = span / 12.0;
self.minorTickLength = span / 16.0;
self.tickInset = self.lineWidth;
textLabel_ = [[UILabel alloc] initWithFrame:CGRectMake(0, 3.0 * self.frame.size.height / 10.0, self.frame.size.width, self.frame.size.height / 10.0)];
self.textLabel.textColor = [UIColor whiteColor];
self.textLabel.backgroundColor = [UIColor clearColor];
self.textLabel.font = [UIFont systemFontOfSize:span / 17.77];
[self addSubview:self.textLabel];
needleLayerObject_ = [[gaugeNeedle alloc] init];
self.needleLayerObject.tintColor = [UIColor whiteColor];
needleLayer = [[CALayer layer] retain];
needleLayer.bounds = self.bounds;
needleLayer.position =CGPointMake(((self.bounds.size.width / 2.0)), ((self.bounds.size.height / 2.0)));
needleLayer.needsDisplayOnBoundsChange = YES;
needleLayer.delegate = self.needleLayerObject;
[self.layer addSublayer:needleLayer];
[needleLayer setNeedsDisplay];
}
In my view controller, I have created 5 views in my xib and .h and i am assigning the same view class for all the 5 views.
Now the problem is, all 5 views differ in their position and bounds. But my drawRect view class has only one set of bounds for all views. So if I hardcode the bounds and position in initialize method with respect to one view, the remaining 4 views are getting affected. Please let me know if u know where i am going wrong.
Thanks in advance..
each of your five views should be executing the draw code relative to their own coordinate space, and there would be no problem. Use self.bounds as the rectangle in your drawing code and the five should all draw identically in their own space. Don't use self.frame, this is in the parent view's coordinate space, and introduces the problem you are experiencing.
You may also want to experiment with the clipsToBounds property, when this is set to YES a UIView can't draw beyond its own borders.
In the iPhone calendar app if you have 2 tiles overlaying ontop of each other the text from the bottom tile gets cut off and cannot be seen through the top transparent tile. How would I be able to keep the tiles transparent but not have the text from the bottom tiles show through to the top tiles?
Here is my code
APCalendarDayTile *tile = (APCalendarDayTile *)view;
CGFloat startPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.startDate]];
CGFloat endPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.endDate]];
tile.frame = CGRectMake(kLeftSideBuffer, startPos, (self.bounds.size.width - kLeftSideBuffer - kRightSideBuffer) , endPos - startPos);
tile.backgroundColor = [UIColor colorWithHexString:tile.appointment.appointmentColor]; <-- This also sets the alpha that makes it transparent.
tile.layer.borderColor = [UIColor colorWithHexString:tile.appointment.appointmentColor alpha:1.0].CGColor;
tile.layer.borderWidth = 1.0f;
Tile code
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
CALayer *layer = [self layer];
[layer setMasksToBounds:YES];
[layer setCornerRadius:kCornerRadius];
}
return self;
}
- (id)init
{
if (self = [super init]) {
self.clipsToBounds = YES;
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = NO;
tileTitle = [[UILabel alloc] init];
tileTitle.textColor = [UIColor blackColor];
tileTitle.backgroundColor = [UIColor clearColor];
tileTitle.font = [UIFont boldSystemFontOfSize:13.0f];
[tileTitle setAutoresizesSubviews:YES];
[tileTitle setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
tileDescription = [[UILabel alloc] init];
tileDescription.textColor = [UIColor blackColor];
tileDescription.backgroundColor = [UIColor clearColor];
tileDescription.font = [UIFont systemFontOfSize:11.0f];
tileDescription.lineBreakMode = UILineBreakModeTailTruncation;
[tileDescription setAutoresizesSubviews:YES];
[tileDescription setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[self setAutoresizesSubviews:YES];
[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[self addSubview:tileTitle];
[self addSubview:tileDescription];
}
return self;
}
- (void)layoutSubviews
{
CGRect myBounds = self.bounds;
CGSize stringSize = [tileTitle.text sizeWithFont:tileTitle.font];
if (myBounds.size.height <= 22.0) {
tileTitle.frame = CGRectMake(3, 0, myBounds.size.width, stringSize.height);
tileDescription.frame = CGRectMake(stringSize.width + 6, -1, myBounds.size.width, 14);
} else {
tileTitle.frame = CGRectMake(3, 0, myBounds.size.width, stringSize.height);
tileDescription.frame = CGRectMake(5, tileTitle.frame.size.height, myBounds.size.width, 14);
}
}
I would like the text in this photo to not show in the front tiles like the second photo.
#user1272965 is right insofar as each tile needs to know what tiles are above it, then you can get the text effect you're looking for by making the tile views a custom subclass and implementing their drawing manually:
Each tile will need access to tiles that are above it (or their frames at least):
NSInteger tileCount = [tilesAboveMine count];
CGRect framesAboveMine[tileCount];
for (int i=0; i<tileCount; i++) {
framesAboveMine[i] = [tilesAboveMine objectAtIndex:i].frame;
}
In drawRect of your tile view, draw the tile, and then draw the text clipped by the views above it:
// draw aspects of the tile not to be clipped, like the background image.
// the setup for clipping:
CGContextClipToRects(context, framesAboveMine, tileCount);
then draw the text, which will be clipped by the frames above
[myCalendarString drawAtPoint:CGPointMake(10,10)];
What about adding a UIView in between the two boxes? This middle UIView would cover up the letters (and have the same background color as the rear event), which solves your problem: no text coming through, but you'll see the color behind the other label!
You might be able to create the frame for this UIView using the frame attribute of the UILabel as well as the frame attribute of the frontmost calendar appointment.
I think the native calendar app squeezes tiles that occur at the very same start time as you do, but it also squeezes tiles whose times overlap, i.e. if the start time of the later tile is between the start and end time of the earlier tile.
But if you need your UI to draw these tiles overlapping, then there's no way around it: the obscured tile needs to know that it's underneath (starting after and overlapping), and it needs to either hide its labels, or reduce their alpha level.
I am trying to add a CALayer as a sublayer of another CALayer. However only the parent layer gets displayed. Here's my code:
//display a green square:
CALayer *shipContainer = [CALayer layer];
shipContainer.bounds = CGRectMake(0,0,200,200);
shipContainer.position = CGPointMake(600,500);
shipContainer.borderColor = [UIColor greenColor].CGColor;
shipContainer.borderWidth = 3;
//display a red dot inside the square:
CALayer *ship1 = [CALayer layer];
ship1.bounds = CGRectMake(0,0,20,20);
ship1.position = CGPointMake(600,500);
ship1.cornerRadius = 10;
ship1.backgroundColor = [UIColor redColor].CGColor;
[shipContainer addSublayer:ship1];
I then call [self.view.layer addSublayer:shipContainer]; but only the green square is displayed. Any thoughts?
As per documentation :
Position
The position property is a CGPoint that specifies the position of the
layer relative to its superlayer, and is expressed in the superlayer's
coordinate system.
so you need to change
ship1.position = CGPointMake(600,500);
so that ship1 can come in visible area. As superlayer has 200,200 as its bounds you need to make position's x and y less then these values.
I'm trying to slide down an image in an UIImageView, but I don't know which combination of UIContentMode and animation property is the right one to make that happen.
The image should always have the same size and should not be streched... all I want is, that nothing is visible first and then the frame extends and reveals the image.
Maybe it's easier if you see what I mean:
So, it sounds rather easy, but what UIContentMode should I use for the UIImageView and what property should I animate? Thank you!
I took your lead and made a screencast as well. Was this what you had in mind?
I put the animation repeating indefinitely so it would be easier to capture with a video, but it can be started at the press of a button, as well as frozen in place, showing the popover and its contents, until reversed to be hidden again.
I used Core Animation for that, instead of animating a UIView, since I wanted to use the mask property of CALayer to hide the popover and reveal it with a sliding animation.
Here is the code I used (same as in the video):
- (void)viewDidLoad
{
// Declaring the popover layer
CALayer *popover = [CALayer layer];
CGFloat popoverHeight = 64.0f;
CGFloat popoverWidth = 200.0f;
popover.frame = CGRectMake(50.0f, 100.0f, popoverWidth, popoverHeight);
popover.contents = (id) [UIImage imageNamed:#"popover.png"].CGImage;
// Declaring the mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0, - popoverHeight, popoverWidth, popoverHeight);
maskLayer.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f].CGColor;
// Setting the animation (animates the mask layer)
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position.y"];
animation.byValue = [NSNumber numberWithFloat:popoverHeight];
animation.repeatCount = HUGE_VALF;
animation.duration = 2.0f;
[maskLayer addAnimation:animation forKey:#"position.y"];
//Assigning the animated maskLayer to the mask property of your popover
popover.mask = maskLayer;
[self.view.layer addSublayer:popover];
[super viewDidLoad];
}
NOTE: You have to import the QuartzCore framework into your project and write this line in your header file: #import <QuartzCore/QuartzCore.h>.
Tells if this works for you or if you need any more help setting this up.
Try this code.
Consider the UIImageView as imageView.
imageView.contentMode = UIViewContentModeScaleAspectFill;
CGRect imageRect = imageView.frame;
CGRect origImgRect = imageRect;
imageRect.size.height = 5;
imageView.frame = imageRect;
[UIView animateWithDuration:2.0
animations:^{imageView.rect = origImgRect;}
completion:^(BOOL finished){ }];
i am working on a game to spot the difference between 2 images.
I want to add an effect when you spot it. drawing out a circle would be much better than just showing the circle suddenly. but i've never done core animation or opengl before.
i don't think preparing 100 sprites and changing the sprite frame by frame is a good idea.
here is my code: (just add a circle image to both left and right image. )
-(void) show {
CCSprite* leftCircle = [CCSprite spriteWithFile:#"circle.png"];
CCSprite* rightCircle = [CCSprite spriteWithFile:#"circle.png"];
leftCircle.scaleX = size.width / [leftCircle boundingBox].size.width;
leftCircle.scaleY = size.height / [leftCircle boundingBox].size.height;
rightCircle.scaleX = size.width / [rightCircle boundingBox].size.width;
rightCircle.scaleY = size.height / [rightCircle boundingBox].size.height;
leftCircle.anchorPoint = ccp(0, 1);
rightCircle.anchorPoint = ccp(0, 1);
leftCircle.position = leftPosition;
rightCircle.position = rightPosition;
[[GameScene sharedScene] addChild:leftCircle z: 3];
[[GameScene sharedScene] addChild:rightCircle z: 3];
shown = YES;
}
So how can i implement it? It would be great if you can provide some source code.
As a simple way i can recommend you to create a circle and put it's scale to zero. Then create a CCScale action and run it. It will give you a growing circle. Here is the code:
CCSprite *sprite = [CCSprite spriteWithFile:#"mySprite.png"];
[sprite setScale:0.01];
id scale = [CCScale actionWithDuration:0.3 scale:1];
[sprite runAction:scale];
The other action can be used is CCFadeIn.You can make your sprite invisible after creation and make it fade in:
CCSprite *sprite = [CCSprite spriteWithFile:#"mySprite.png"];
[sprite setOpacity:0];
id fade = [CCFadeIn actionWithDuration:0.3];
[sprite runAction:fade];
Also you can combine this actions:
[sprite runAction:fade];
[sprite runAction:scale];
Also you can make it big (set scale 3 for example) and transparent. And make it fade in and scale down to highlight your image
To Draw a circle via drawing effect you can make it from small parts (arcs) and then build your circle from them. I think it also will be cool if you make don't make the part visible when adding, but make it fade in. I mean something like this:
-(void) init
{
NSMutableArray *parts = [[NSMutableArray array] retain]; //store it in your class variable
parts_ = parts;
localTime_ = 0; //float localTime_ - store the local time in your layer
//create all the parts here, make them invisible and add to the layer and parts
}
-(void) update: (CCTime) dt //add update method to your layer that will be called every simulation step
{
localTime_ += dt;
const float fadeTime = 0.1;
int currentPart = localTime_ / fadeTime;
int i = 0;
for (CCSprite *part in parts)
{
//setup the opacity of each part according to localTime
if (i < currentPart) [part setOpacity:255];
else if (i == currentPart)
{
float localLocalTime = localTime - i*fadeTime;
float alpha = localLocalTime / fadeTime;
[part setOpacity:alpha];
}
++i;
}
}
It is rather simple to create the effect you want.
You can realize it by setting the strokeEnd of a CAShapeLayer.
Here are the details:
create a path you then assign to a CAShapeLayer
UIBezierPath* circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100.0, 100.0) radius:80.0 startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(270.01) clockwise:NO];
circle = [CAShapeLayer layer];
circle.path = circlePath.CGPath;
circle.strokeColor = [[UIColor blackColor] CGColor];
circle.fillColor = [[UIColor whiteColor] CGColor];
circle.lineWidth = 6.0;
circle.strokeEnd = 0.0;
[self.view.layer addSublayer:circle];
set the strokeEnd to implicitly animate the drawing of the circle
circle.strokeEnd = 1.0;
That's it! The circle will be automatically drawn for you ;). Here is the code from above with a GestureRecognizer to trigger the animation. Copy this to an empty project of type "Single View Application" and paste it to the ViewController.
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIBezierPath* circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100.0, 100.0) radius:80.0 startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(270.01) clockwise:NO];
circle = [CAShapeLayer layer];
circle.path = circlePath.CGPath;
circle.strokeColor = [[UIColor blackColor] CGColor];
circle.fillColor = [[UIColor whiteColor] CGColor];
circle.lineWidth = 6.0;
circle.strokeEnd = 0.0;
[self.view.layer addSublayer:circle];
// add a tag gesture recognizer
// add a single tap gesture recognizer
UITapGestureRecognizer* tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapGesture:)];
[self.view addGestureRecognizer:tapGR];
}
#pragma mark - gesture recognizer methods
//
// GestureRecognizer to handle the tap on the view
//
- (void)handleTapGesture:(UIGestureRecognizer *)gestureRecognizer {
circle.strokeEnd = 1.0;
}