Core Animation Layers not displaying under Snow Leopard - objective-c

I have recently converted one of the views in my OS X App to be layer-hosted and all is working well under Mountain Lion, however one of my testers is complaining that the layers aren't showing under Snow Leopard. I have written a small test app to perform further tests (source code here), and this test app also doesn't work under 10.6.
Here is the main body of code that sets-up the layers:
- (id)initWithFrame:(NSRect)frameRect
{
NSLog(#"initWithFrame");
self = [super initWithFrame:frameRect];
if (self != nil)
{
srand((unsigned)time(NULL));
_rootLayer = [[CALayer alloc] init];
_rootLayer.delegate = self;
_rootLayer.anchorPoint = CGPointMake(0.0, 0.0);
_rootLayer.frame = NSRectToCGRect([self bounds]);
_rootLayer.needsDisplayOnBoundsChange = NO;
_rootLayer.masksToBounds = YES;
self.layer = _rootLayer;
self.wantsLayer = YES;
_backgroundLayer = [[CALayer alloc] init];
_backgroundLayer.delegate = self;
_backgroundLayer.anchorPoint = CGPointMake(0.5, 0.5);
_backgroundLayer.frame = CGRectInset(NSRectToCGRect([self bounds]), BACKGROUND_INSET, BACKGROUND_INSET);
_backgroundLayer.cornerRadius = 5.0;
_backgroundLayer.needsDisplayOnBoundsChange = NO;
_backgroundLayer.masksToBounds = YES;
[_rootLayer addSublayer:_backgroundLayer];
_mouseLayer = [self _createOtherLayer];
_mouseLayer.opacity = 0.5;
for (unsigned i = 0; i < NUM_OTHER_LAYERS; i++)
_otherLayers[i] = [self _createOtherLayer];
[_backgroundLayer addSublayer:_mouseLayer];
[_rootLayer setNeedsDisplay];
[_backgroundLayer setNeedsDisplay];
[self _positionOtherLayersInRect:frameRect];
_trackingArea = nil;
[self updateTrackingAreas];
}
return self;
}
And here is the method that creates the other layers:
- (CALayer *)_createOtherLayer
{
CALayer *layer = [[CALayer alloc] init];
layer.delegate = self;
layer.anchorPoint = CGPointMake(0.5, 0.5);
layer.bounds = CGRectMake(0.0, 0.0, 64.0, 64.0);
layer.position = CGPointMake(0.0, 0.0);
layer.needsDisplayOnBoundsChange = NO;
layer.masksToBounds = YES;
layer.shadowColor = CGColorGetConstantColor(kCGColorBlack);
layer.shadowOffset = CGSizeMake(2.0, -2.0);
layer.shadowRadius = 2.0;
layer.shadowOpacity = 1.0;
[_backgroundLayer addSublayer:layer];
[layer setNeedsDisplay];
return layer;
}
Can anyone suggest why these layers don't work under 10.6?

Have you tried moving the code in initWithFrame: into awakeFromNib? It seems to be a common enough mistake that causes the layers to get screwed up. In this question the problem was that the layers were set up in initWithFrame, but since nibs are marked by default as not needing layers, they were wiped out immediately after. Move the code to awakeFromNib, and instead of using the passed frame use self.frame and see if that fixes the problem. At the very least it shouldn't be any worse (running on my Mac running Lion after moving the code to awakeFromNib and it still works fine, so it didn't break anything), and it may just be the solution you're looking for.
Hopefully this works, or you find another solution soon. Have a good day. :)

What happens if you change:
CALayer *layer = [[CALayer alloc] init];
to:
CALayer *layer = [CALayer layer];
Not sure why it would make a difference, but worth a shot maybe. Also have you tried to use insertSubLayer:atIndex: instead of addSubLayer?

Related

CGRectContainsPoint using shape opposed to frame

So we have our detection here
-(void)checkInFOVWithPlayer:(Player *)player andEnemy:(Player *)enemy {
SKNode *fovNode = [player childNodeWithName:player.playersFOVName];
SKNode *node = [self childNodeWithName:#"enemy"];
CGPoint newPosition = [self convertPoint:node.position toNode:fovNode.parent];
if (CGRectContainsPoint(fovNode.frame, newPosition)) {
[self playerAimAtEnemy:enemy withPlayer:player];
}
}
And our implementation for the field of vision
SKShapeNode *fov = [SKShapeNode node];
UIBezierPath *fovPath = [[UIBezierPath alloc] init];
[fovPath moveToPoint:CGPointMake(0, 0)];
[fovPath addLineToPoint:CGPointMake(fovOpposite *-1, fovDistance)];
[fovPath addLineToPoint:CGPointMake(fovOpposite, fovDistance)];
[fovPath addLineToPoint:CGPointMake(0, 0)];
fov.path = fovPath.CGPath;
fov.lineWidth = 1.0;
fov.strokeColor = [UIColor clearColor];
fov.antialiased = NO;
fov.fillColor = [UIColor greenColor];
fov.alpha = 0.2;
fov.name = #"playerFOV";
[_playerImage addChild:fov];
Now, this works. However, the detection range for the "field of vision" is not actually the boundaries of the BezierPath, it's in fact the CGRect that creates the image.
So, the detection will run, even if it's outside of the visual field of vision.
I'm curious as to whether there's an easy fix for this, as I don't really want to go down physics body paths if I don't need to.
Finally I figured out what was required.
I had to redraw the path and optimise it for each frame, after that;
if (CGPathContainsPoint(fovPath.CGPath, nil, newPosition, NO)) {}
Ended my problems.

AVVideoCompositionCoreAnimationTool not adding all CALayers

Okay, this one has me completed stumped. I'm happy to post other code if you need it but I think this is enough. I cannot for the life of me figure out why things are going wrong. I'm adding CALayers, which contain images, to a composition using AVVideoCompositionCoreAnimationTool. I create an NSArray of all the annotations (see interface below) I want to add and then add them to the animation layer with an enumerator. No matter how many, as far as I can tell, annotations are in the array, the only ones that end up in the outputted video are the ones added by the last loop. Can someone spot what I'm missing?
Here's the interface for the annotations
#interface Annotation : NSObject// <NSCoding>
#property float time;
#property AnnotationType type;
#property CALayer *startLayer;
#property CALayer *typeLayer;
#property CALayer *detailLayer;
+ (Annotation *)annotationAtTime:(float)time ofType:(AnnotationType)type;
- (NSString *)annotationString;
#end
And here's the message that creates the video composition with the animation.
- (AVMutableVideoComposition *)createCompositionForMovie:(AVAsset *)movie fromAnnotations:(NSArray *)annotations {
AVMutableVideoComposition *videoComposition = nil;
if (annotations){
//CALayer *imageLayer = [self layerOfImageNamed:#"Ring.png"];
//imageLayer.opacity = 0.0;
//[imageLayer setMasksToBounds:YES];
Annotation *ann;
NSEnumerator *enumerator = [annotations objectEnumerator];
CALayer *animationLayer = [CALayer layer];
animationLayer.frame = CGRectMake(0, 0, movie.naturalSize.width, movie.naturalSize.height);
CALayer *videoLayer = [CALayer layer];
videoLayer.frame = CGRectMake(0, 0, movie.naturalSize.width, movie.naturalSize.height);
[animationLayer addSublayer:videoLayer];
// TODO: Consider amalgamating this message and scaleVideoTrackTime:fromAnnotations
// Single loop instead of two and sharing of othe offset variables
while (ann = (Annotation *)[enumerator nextObject]) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.duration = 3; // TODO: Three seconds is the currently hard-coded display length for an annotation, should this be a configurable option after the demo?
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.fromValue = [NSNumber numberWithFloat:1.0];
animation.toValue = [NSNumber numberWithFloat:1.0];
animation.beginTime = time;
// animation.beginTime = AVCoreAnimationBeginTimeAtZero;
ann.startLayer.opacity = 0.0;
ann.startLayer.masksToBounds = YES;
[ann.startLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.startLayer];
ann.typeLayer.opacity = 0.0;
ann.typeLayer.masksToBounds = YES;
[ann.typeLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.typeLayer];
ann.detailLayer.opacity = 0.0;
ann.detailLayer.masksToBounds = YES;
[ann.detailLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.detailLayer];
}
videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:movie];
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:animationLayer];
}
return videoComposition;
}
I want to stress that the video outputs correctly and I do get the layers appearing at the right time, just not ALL the layers. Very confused and would greatly appreciate your help.
So I was fiddling around trying to figure out what might have caused this and it turns out that it was caused by the layers hidden property being set to YES. By setting it to NO, all the layers appear but then they never went away. So I had to change the animation's autoreverses property to YES and halve the duration.
So I changed the code to this:
while (ann = (Annotation *)[enumerator nextObject]){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.duration = 1.5; // TODO: Three seconds is the currently hard-coded display length for an annotation, should this be a configurable option after the demo?
animation.repeatCount = 0;
animation.autoreverses = YES; // This causes the animation to run forwards then backwards, thus doubling the duration, that's why a 3-second period is using 1.5 as duration
animation.removedOnCompletion = NO;
animation.fromValue = [NSNumber numberWithFloat:1.0];
animation.toValue = [NSNumber numberWithFloat:1.0];
animation.beginTime = time;
// animation.beginTime = AVCoreAnimationBeginTimeAtZero;
ann.startLayer.hidden = NO;
ann.startLayer.opacity = 0.0;
ann.startLayer.masksToBounds = YES;
[ann.startLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.startLayer];
ann.typeLayer.hidden = NO;
ann.typeLayer.opacity = 0.0;
ann.typeLayer.masksToBounds = YES;
[ann.typeLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.typeLayer];
ann.detailLayer.hidden = NO;
ann.detailLayer.opacity = 0.0;
ann.detailLayer.masksToBounds = YES;
[ann.detailLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.detailLayer];
}

CALayer shadow while scrolling UITableView

I have added a shadow to a UITableView (which covers a third of the screen sfrom the bottom - see attached screenshot) using the following in a UIView Category:
- (void) addShadow {
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
self.layer.masksToBounds = NO;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 1;
self.layer.shadowOffset = CGSizeMake(-5,-5);
self.layer.shadowRadius = 20;
self.layer.shadowPath = path.CGPath;
self.layer.shouldRasterize = YES;
}
It appears as expected, but when I scroll it up, the shadow scrolls up too. Also, the table scrolls beyond its upper bound. Can you suggest what is wrong here? if I comment self.layer.masksToBounds = NO;, the shadow disappears, but the table scrolling is as expected. Hence, the problem lies somewhere around masksToBounds perhaps.
I solved it by putting an identical view underneath, just for the shadow. Not a clean solution ... hence I am still open to answers. My code is as follows:
- (UIView*) addShadow {
UIView* backView = [[UIView alloc] initWithFrame:self.frame];
UIBezierPath *path = [UIBezierPath bezierPathWithRect:backView.bounds];
backView.layer.masksToBounds = NO;
backView.layer.shadowColor = [UIColor blackColor].CGColor;
backView.layer.shadowOpacity = 1;
backView.layer.shadowOffset = CGSizeMake(-5,-5);
backView.layer.shadowRadius = 20;
backView.layer.shadowPath = path.CGPath;
backView.layer.shouldRasterize = YES;
[self.superview addSubview:backView];
[self.superview bringSubviewToFront:self];
return backView;
}
(void) removeShadow {
self.layer.masksToBounds = YES;
self.layer.shadowColor = nil;
self.layer.shadowOpacity = 0;
self.layer.shadowOffset = CGSizeMake(0,0);
self.layer.shadowRadius = 0;
}

NSMutableArray not holding UIImages properly?

I have enabled my Cocoa Touch app to be navigable by swiping left or right to alter positions in history. The animation is kind of done like Android's "card" style. Where swiping to the left (<--) just moves the current screen image out of the way, while showing the previous view beneath it.
This works fine, but when I want to swipe the other way (-->), to go back, I need to get the previous image, and move that over the current view. Now, I had this working if I only store the previous image, but what if I go <-- a few times, then I will not have enough images.
So, the solution is obvious, use an NSMutableArray and just throw the latest image at the front of the array, and when you swipe the other way, just use the first image in the array. However, the image never shows when I start the animation. It just shows nothing. Here's the required methods you should see:
-(void)animateBack {
CGRect screenRect = [self.view bounds];
UIImage *screen = [self captureScreen];
[imageArray insertObject:screen atIndex:0]; //Insert the screenshot at the first index
imgView = [[UIImageView alloc] initWithFrame:screenRect];
imgView.image = screen;
[imgView setHidden:NO];
NSLog(#"Center of imgView is x = %f, y = %f", imgView.center.x, imgView.center.y);
CGFloat startPointX = imgView.center.x;
CGFloat width = screenRect.size.width;
NSLog(#"Width = %f", width);
imgView.center = CGPointMake(imgView.center.x, imgView.center.y);
[self.view addSubview: imgView];
[self navigateBackInHistory];
[UIView animateWithDuration:.3 animations:^{
isSwiping = 1;
imgView.center = CGPointMake(startPointX - width, imgView.center.y);
} completion:^(BOOL finished){
// Your animation is finished
[self clearImage];
isSwiping = 0;
}];
}
-(void)animateForward {
CGRect screenRect = [self.view bounds];
//UIImage *screen = [self captureScreen];
UIImage *screen = [imageArray objectAtIndex:0]; //Get the latest image
imgView = [[UIImageView alloc] initWithFrame:screenRect];
imgView.image = screen;
[imgView setHidden:NO];
NSLog(#"Center of imgView is x = %f, y = %f", imgView.center.x, imgView.center.y);
CGFloat startPointX = imgView.center.x;
CGFloat width = screenRect.size.width;
NSLog(#"Width = %f", width);
imgView.center = CGPointMake(imgView.center.x - width, imgView.center.y);
[self.view addSubview: imgView];
[UIView animateWithDuration:.3 animations:^{
isSwiping = 1;
imgView.center = CGPointMake(startPointX, imgView.center.y);
} completion:^(BOOL finished){
// Your animation is finished
[self navigateForwardInHistory];
[self clearImage];
isSwiping = 0;
}];
}
-(void)clearImage {
[imgView setHidden:YES];
imgView.image = nil;
}
-(void)navigateBackInHistory {
[self saveItems:self];
[self alterIndexBack];
item = [[[LEItemStore sharedStore] allItems] objectAtIndex:currentHistoryIndex];
[self loadItems:self];
}
-(void)navigateForwardInHistory {
[imageArray removeObjectAtIndex:0]; //Remove the latest image, since we just finished swiping this way.
[self saveItems:self];
[self alterIndexForward];
item = [[[LEItemStore sharedStore] allItems] objectAtIndex:currentHistoryIndex];
[self loadItems:self];
}
Note that imgView is a class-level UIImageView and imageArray is a class level array.
Any ideas? Thanks.
Edit
Here's the code at the top of my .m to initalize it. Still does not work.
.....
NSMutableArray *imageArray;
- (void)viewDidLoad
{
[super viewDidLoad];
imageArray = [imageArray initWithObjects:nil];
It looks like you forgot to create the array. Something like this at the appropriate time would do (assuming ARC):
imageArray = [NSMutableArray array];
Glad that worked out.

add an animation to bar plot does not take effect visually

I am a newer to core plot. And recently I decide to add bar growth animation to my plot. so I followed the instruction searched here to implement the animation. Now I come up with the problem: the animation seemed not to take effect.The delegate: didStopAnimate:(BOOL) returns no matter how long I set the duration of the animation with flag false.
One more thing to state: in the graph view, I put a scrollview under the real graph hosting view. does that affect Core animation to work?
the plot appeared after a navigation from the table view to itself. here is some of my codes:
if ([plot_type isEqualToString:#"bar"]) {
CPTBarPlot *plot = [[CPTBarPlot alloc] init];
//id
plot.identifier = [d objectForKey:#"title"];
plot.title = [d objectForKey:#"title"];
plot.lineStyle = barLineStyle;
plot.barCornerRadius = 1.2;
//set color
CPTGradient *gradient = [CPTGradient gradientWithBeginningColor:[CPTColor colorWithCGColor:color_begin] endingColor:[CPTColor colorWithCGColor:color_end]];
gradient.angle = 90.0f;
CPTFill *fill = [CPTFill fillWithGradient:gradient];
plot.fill = fill;
//bar width
double width = [[d objectForKey:#"width"] doubleValue]==0.0?0.6:[[d objectForKey:#"width"] doubleValue];
plot.barWidth = CPTDecimalFromFloat(width);
plot.barsAreHorizontal = NO;
//need manually stacked
if (need_stack && !is_first) {
plot.barBasesVary = YES;
} else {
plot.barBasesVary = NO;
}
//data source
plot.dataSource = self;
//delegate
plot.delegate = self;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
[anim setDuration:2.0f];
anim.toValue = [NSNumber numberWithFloat:1];
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.removedOnCompletion = NO;
anim.delegate = self;
anim.fillMode = kCAFillModeForwards;
[plot addAnimation:anim forKey:(NSString *)plot.identifier];
[graph addPlot:plot toPlotSpace:plotSpace];
}
Thanks for your helps in advance.
The transform property of a CALayer is CATransform3D struct, not a scalar value. You should wrap it using the NSValue method +valueWithCATransform3D:.