NSView with gradient background AND shadow - cocoa

I want my NSView to have both gradient background and shadow, so I subclassed NSView and overrode the - (void)awakeFromNib method like this:
[self setWantsLayer:YES]; // view's backing store is using a Core Animation Layer
CAGradientLayer *backgroundGradient = [[CAGradientLayer alloc] init];
backgroundGradient.colors = #[
(__bridge id)CGColorCreateGenericGray(0.85, 1.0),
(__bridge id)CGColorCreateGenericGray(0.94, 1.0)
];
backgroundGradient.masksToBounds = NO;
CALayer *shadowLayer = [CALayer layer];
shadowLayer.shadowColor = CGColorCreateGenericGray(0.5, 1.0);
shadowLayer.shadowOffset = NSMakeSize(0, 0.0);
shadowLayer.shadowRadius = 10.0;
shadowLayer.shadowOpacity = 1.0;
shadowLayer.masksToBounds = NO;
[backgroundGradient addSublayer:shadowLayer];
[self setLayer:backgroundGradient];
However, only the gradient was shown. In addition, if I try [self setLayer:shadowLayer] then the shadow is shown (but of course, no gradient).
How to show both the gradient and the shadow? Thanks a lot!

Related

CALayer mask Layer animating

I want to mask CALayer with another layer whose frame I want to keep changing in every draw call thereby animating. Is it possible to do change the frame of mask layer in every draw call rather than making a new mask layer?
Try the following code. This code will run on Cocoa App for macOS. Kindly change the classes accordingly if you are going for Cocoa Touch for iOS. I hope this works for you.
// ViewController.h
import
#interface ViewController : NSViewController{
CALayer * maskLayer;
CALayer * Layer;
}
// ViewController.m
#implementation ViewController
-(void)viewDidLoad{
[super viewDidLoad];
maskLayer = [CALayer layer];
Layer = [CALayer layer];
Layer.backgroundColor = [[NSColor blueColor] CGColor];
Layer.frame = CGRectMake(20, 20, 720, 900);
Layer.contents = [NSImage imageNamed:#"bg1"];
maskLayer.frame = CGRectMake(20, 20, 720, 900);
maskLayer.opacity = 0.5;
[maskLayer mask];
maskLayer.contents = [NSImage imageNamed:#"bg"];
[self.view.layer addSublayer:Layer];
[self.view.layer addSublayer:maskLayer];
[self maskLayerAnimation];
}
-(void)maskLayerAnimation{
[CATransaction begin];
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 5.0;
[CATransaction setCompletionBlock:^{
NSLog(#"animation completed");
maskLayer.frame = CGRectMake(20, 20, 720, 700);
maskLayer.contents = [NSImage imageNamed:#"bg2"];
}];;
[maskLayer addAnimation:fadeAnim forKey:#"opacity"];
[CATransaction commit];
}

Why is my CALayer being clipped by my NSView?

The Core Animation Programming Guide led me to believe that a sublayer could, by default, extend outside the bounds of a host view without being clipped. But that's not what's happening for me.
Here's how I initialize my layer-hosting custom view:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self != nil) {
mBaseImage = [[NSImage imageNamed: #"Button.png"] retain];
mSliderImage = [[NSImage imageNamed: #"Track.png"] retain];
mRootLayer = [CALayer layer];
mRootLayer.masksToBounds = NO;
[self setLayer: mRootLayer ];
[self setWantsLayer: YES];
mRootLayer.contents = mBaseImage;
mSliderLayer = [CALayer layer];
[mRootLayer addSublayer: mSliderLayer];
NSSize imSize = [mSliderImage size];
NSRect sliderBounds = NSMakeRect( 0.0f, 0.0f, imSize.width, imSize.height );
mSliderLayer.bounds = sliderBounds;
mSliderLayer.contents = mSliderImage;
mSliderLayer.position = NSMakePoint( 31.0f, 31.0f );
}
return self;
}
The host view, and the image used as its contents, are 62x62, while the sublayer image is 90x10. But the whole thing gets clipped to the 62x62 bounds. What am I missing?
The trick seems to be that the view must be contained in another view (maybe the content view of the window) that has a layer. (Is that documented?)

Cocos2D: openGL-es becoming transparent over UIView

For reasons outside the capabilities of Cocos2D, I needed to make it transparent and show UIView's behind it.
This works fine except under a certain circumstance. When a sprite's opacity becomes more transparent, it makes everything else in Cocos2D drawn under it become transparent for some reason which makes my UIViews show through.
Shown here on the first image is water tiles, drawn in Cocos2D with a UIView of the mountain and the sky showing under it. The second image is the same thing except in Cocos2D there's a black sprite covering the whole screen at half opacity, which for some reason, makes the UIView under it show through. Where this is most visible in my example is the mountain showing through the water tiles.
My delegate's applicationDidFinishLaunching code is as follows:
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
// Init the window
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Try to use CADisplayLink director
// if it fails (SDK < 3.1) use the default director
if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
[CCDirector setDirectorType:kCCDirectorTypeDefault];
CCDirector *director = [CCDirector sharedDirector];
// Init the View Controller
viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
viewController.wantsFullScreenLayout = YES;
EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGBA8 // kEAGLColorFormatRGBA8, kEAGLColorFormatRGB565
depthFormat:0 // GL_DEPTH_COMPONENT16_OES
];
// attach the openglView to the director
[director setOpenGLView:glView];
glView.opaque = NO;
#if GAME_AUTOROTATION == kGameAutorotationUIViewController
[director setDeviceOrientation:kCCDeviceOrientationPortrait];
#else
[director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];
#endif
[director setAnimationInterval:1.0/60];
[director setDisplayFPS:YES];
//color/gradient BG
bgLayer = [ColorBGView createGradientWithName:#"darkCave"];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
bgLayer.frame = CGRectMake(0, 0, screenHeight, screenWidth);
[viewController.view.layer insertSublayer:bgLayer atIndex:0];
// add custom parallax view
parallaxView = [[ParallaxView alloc] init];
parallaxView.frame = CGRectMake(0, 0, 1024, 768);
[viewController.view insertSubview:parallaxView atIndex:1];
[parallaxView release];
// make the OpenGLView a child of the view controller
[viewController.view insertSubview:glView atIndex:2];
viewController.view.opaque = YES;
viewController.view.backgroundColor = [UIColor blackColor];
// make the View Controllers childs of the main window
window.rootViewController = viewController;
foregroundLabelView = [[ForegroundLabelView alloc] init];
foregroundLabelView.frame = CGRectMake(0, 0, 1024, 768);
[viewController.view insertSubview:foregroundLabelView atIndex:3];
foregroundLabelView.hidden = YES;
[foregroundLabelView release];
[window makeKeyAndVisible];
// Default texture format for PNG/BMP/TIFF/JPEG/GIF images
// It can be RGBA8888, RGBA4444, RGB5_A1, RGB565
// You can change anytime.
[CCTexture2D setDefaultAlphaPixelFormat:kTexture2DPixelFormat_RGBA8888]; //
[glView setMultipleTouchEnabled:YES];
// Removes the startup flicker
[self removeStartupFlicker];
// Run the intro Scene
[[CCDirector sharedDirector] runWithScene: [BlankScene scene]];
}
Please let me know if you need anything else. Thank you.

How to draw a default image in imageview in the center of imageView?

How to draw a default image in imageview in the center of imageView?using - (void)drawRect:(NSRect)rect overridden method of NSImageView
Yes. That was one way. I've used the following code.
// Drawing
- (void)drawRect:(NSRect)rect
{
if([self image])
{
[[NSColor grayColor] set];
NSRectFill(rect);
//ImageView Bounds and Size
NSRect vBounds = [self bounds];
NSSize vSize = vBounds.size;
//Get the size and origin of default image set to imageView
NSRect imageRect;
imageRect.size = [[self image] size];
imageRect.origin = NSZeroPoint;
//Create a preview image
NSSize previewSize = NSMakeSize( [self image].width / 4.0, [self image].height / 4.0 );
NSImage *previewImage = [[NSImage alloc] initWithSize:previewSize];
//Get the point where the preview image needs to be draw
NSRect newRect;
newRect.origin.x = vSize.width/2-previewSize.width/2;
newRect.origin.y = vSize.height/2-previewSize.height/2;
newRect.size = [previewImage size];
//Draw preview image in imageView
[[self image] drawInRect:newRect fromRect:imageRect operation:NSCompositeSourceOver fraction:1.0];
[previewImage release];
}
}
Why not just set the image view's image initially to the default image, then change it later?

Widget "flip" behavior in Core Animation/Cocoa

I'm trying to make a Card class that duplicates the behavior of Dashboard widgets in that you can put controls or images or whatever on two sides of the card and flip between them.
Layer backed views have a transform property, but altering that doesn't do what I would expect it to do (rotating the layer around the y axis folds it off to the left side).
I was pointed to some undocumented features and an .h file named cgsprivate.h, but I'm wondering if there is an official way to do this? This software would have to be shipped and I'd hate to see it fail later because the Apple guys pull it in 10.6.
Anyone have any idea how to do this? It's so weird to me that a simple widget thing would be so hard to do in Core Animation.
Thanks in advance!
EDIT: I can accomplish this behavior with images that are on layers, but I don't know how to get more advanced controls/views/whatever on the layers. The card example uses images.
Mike Lee has an implementation of the flip effect for which he has released some sample code. (Unfortunately, this is no longer available online, but Drew McCormack built off of that in his own implementation.) It appears that he grabs the layers for the "background" and "foreground" views to be swapped, uses a CATransform3D to rotate the two views in the animation, and then swaps the views once the animation has completed.
By using the layers from the views, you avoid needing to cache into a bitmap, since that's what the layers are doing anyways. In any case, his view controller looks to be a good drop-in solution for what you want.
Using Core Animation like e.James outlined...Note, this is using garbage collection and a hosted layer:
#import "AnimationWindows.h"
#interface AnimationFlipWindow (PrivateMethods)
NSRect RectToScreen(NSRect aRect, NSView *aView);
NSRect RectFromScreen(NSRect aRect, NSView *aView);
NSRect RectFromViewToView(NSRect aRect, NSView *fromView, NSView *toView);
#end
#pragma mark -
#implementation AnimationFlipWindow
#synthesize flipForward = _flipForward;
- (id) init {
if ( self = [super init] ) {
_flipForward = YES;
}
return self;
}
- (void) finalize {
// Hint to GC for cleanup
[[NSGarbageCollector defaultCollector] collectIfNeeded];
[super finalize];
}
- (void) flip:(NSWindow *)activeWindow
toBack:(NSWindow *)targetWindow {
CGFloat duration = 1.0f * (activeWindow.currentEvent.modifierFlags & NSShiftKeyMask ? 10.0 : 1.0);
CGFloat zDistance = 1500.0f;
NSView *activeView = [activeWindow.contentView superview];
NSView *targetView = [targetWindow.contentView superview];
// Create an animation window
CGFloat maxWidth = MAX(NSWidth(activeWindow.frame), NSWidth(targetWindow.frame)) + 500;
CGFloat maxHeight = MAX(NSHeight(activeWindow.frame), NSHeight(targetWindow.frame)) + 500;
CGRect animationFrame = CGRectMake(NSMidX(activeWindow.frame) - (maxWidth / 2),
NSMidY(activeWindow.frame) - (maxHeight / 2),
maxWidth,
maxHeight);
mAnimationWindow = [NSWindow initForAnimation:NSRectFromCGRect(animationFrame)];
// Add a touch of perspective
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / zDistance;
[mAnimationWindow.contentView layer].sublayerTransform = transform;
// Relocate target window near active window
CGRect targetFrame = CGRectMake(NSMidX(activeWindow.frame) - (NSWidth(targetWindow.frame) / 2 ),
NSMaxY(activeWindow.frame) - NSHeight(targetWindow.frame),
NSWidth(targetWindow.frame),
NSHeight(targetWindow.frame));
[targetWindow setFrame:NSRectFromCGRect(targetFrame) display:NO];
mTargetWindow = targetWindow;
// New Active/Target Layers
[CATransaction begin];
CALayer *activeWindowLayer = [activeView layerFromWindow];
CALayer *targetWindowLayer = [targetView layerFromWindow];
[CATransaction commit];
activeWindowLayer.frame = NSRectToCGRect(RectFromViewToView(activeView.frame, activeView, [mAnimationWindow contentView]));
targetWindowLayer.frame = NSRectToCGRect(RectFromViewToView(targetView.frame, targetView, [mAnimationWindow contentView]));
[CATransaction begin];
[[mAnimationWindow.contentView layer] addSublayer:activeWindowLayer];
[CATransaction commit];
[mAnimationWindow orderFront:nil];
[CATransaction begin];
[[mAnimationWindow.contentView layer] addSublayer:targetWindowLayer];
[CATransaction commit];
// Animate our new layers
[CATransaction begin];
CAAnimation *activeAnim = [CAAnimation animationWithDuration:(duration * 0.5) flip:YES forward:_flipForward];
CAAnimation *targetAnim = [CAAnimation animationWithDuration:(duration * 0.5) flip:NO forward:_flipForward];
[CATransaction commit];
targetAnim.delegate = self;
[activeWindow orderOut:nil];
[CATransaction begin];
[activeWindowLayer addAnimation:activeAnim forKey:#"flip"];
[targetWindowLayer addAnimation:targetAnim forKey:#"flip"];
[CATransaction commit];
}
- (void) animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
if (flag) {
[mTargetWindow makeKeyAndOrderFront:nil];
[mAnimationWindow orderOut:nil];
mTargetWindow = nil;
mAnimationWindow = nil;
}
}
#pragma mark PrivateMethods:
NSRect RectToScreen(NSRect aRect, NSView *aView) {
aRect = [aView convertRect:aRect toView:nil];
aRect.origin = [aView.window convertBaseToScreen:aRect.origin];
return aRect;
}
NSRect RectFromScreen(NSRect aRect, NSView *aView) {
aRect.origin = [aView.window convertScreenToBase:aRect.origin];
aRect = [aView convertRect:aRect fromView:nil];
return aRect;
}
NSRect RectFromViewToView(NSRect aRect, NSView *fromView, NSView *toView) {
aRect = RectToScreen(aRect, fromView);
aRect = RectFromScreen(aRect, toView);
return aRect;
}
#end
#pragma mark -
#pragma mark CategoryMethods:
#implementation CAAnimation (AnimationFlipWindow)
+ (CAAnimation *) animationWithDuration:(CGFloat)time flip:(BOOL)bFlip forward:(BOOL)forwardFlip{
CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue, endValue;
if ( forwardFlip ) {
startValue = bFlip ? 0.0f : -M_PI;
endValue = bFlip ? M_PI : 0.0f;
} else {
startValue = bFlip ? 0.0f : M_PI;
endValue = bFlip ? -M_PI : 0.0f;
}
flipAnimation.fromValue = [NSNumber numberWithDouble:startValue];
flipAnimation.toValue = [NSNumber numberWithDouble:endValue];
CABasicAnimation *shrinkAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
shrinkAnimation.toValue = [NSNumber numberWithFloat:1.3f];
shrinkAnimation.duration = time * 0.5;
shrinkAnimation.autoreverses = YES;
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = [NSArray arrayWithObjects:flipAnimation, shrinkAnimation, nil];
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animationGroup.duration = time;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
return animationGroup;
}
#end
#pragma mark -
#implementation NSWindow (AnimationFlipWindow)
+ (NSWindow *) initForAnimation:(NSRect)aFrame {
NSWindow *window = [[NSWindow alloc] initWithContentRect:aFrame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[window setOpaque:NO];
[window setHasShadow:NO];
[window setBackgroundColor:[NSColor clearColor]];
[window.contentView setWantsLayer:YES];
return window;
}
#end
#pragma mark -
#implementation NSView (AnimationFlipWindow)
- (CALayer *) layerFromWindow {
NSBitmapImageRep *image = [self bitmapImageRepForCachingDisplayInRect:self.bounds];
[self cacheDisplayInRect:self.bounds toBitmapImageRep:image];
CALayer *layer = [CALayer layer];
layer.contents = (id)image.CGImage;
layer.doubleSided = NO;
// Shadow settings based upon Mac OS X 10.6
[layer setShadowOpacity:0.5f];
[layer setShadowOffset:CGSizeMake(0,-10)];
[layer setShadowRadius:15.0f];
return layer;
}
#end
The header file:
#interface AnimationFlipWindow : NSObject {
BOOL _flipForward;
NSWindow *mAnimationWindow;
NSWindow *mTargetWindow;
}
// Direction of flip animation (property)
#property (readwrite, getter=isFlipForward) BOOL flipForward;
- (void) flip:(NSWindow *)activeWindow
toBack:(NSWindow *)targetWindow;
#end
#pragma mark -
#pragma mark CategoryMethods:
#interface CAAnimation (AnimationFlipWindow)
+ (CAAnimation *) animationWithDuration:(CGFloat)time
flip:(BOOL)bFlip // Flip for each side
forward:(BOOL)forwardFlip; // Direction of flip
#end
#interface NSWindow (AnimationFlipWindow)
+ (NSWindow *) initForAnimation:(NSRect)aFrame;
#end
#interface NSView (AnimationFlipWindow)
- (CALayer *) layerFromWindow;
#end
EDIT: This will animate to flip from one window to another window. You can apply the same principals to a view.
It's overkill for your purposes (as it contains a largely-complete board and card game reference app), but check out this sample from ADC. The card games included with it do that flip effect quite nicely.
If you are able to do this with images, perhaps you can keep all of your controls in an NSView object (as usual), and then render the NSView into a bitmap image using cacheDisplayInRect:toBitmapImageRep: just prior to executing the flip effect. The steps would be:
Render the NSView to a bitmap
Display that bitmap in a layer suitable for the flip effect
Hide the NSView and expose the image layer
Perform the flip effect
I know this is late but Apple has an example project here that may be of help to anyone still stumbling upon this question.
https://developer.apple.com/library/mac/#samplecode/ImageTransition/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010277
There's a complete open source implementation of this by the guys at Mizage.
You can check it out here: https://github.com/mizage/Flip-Animation
Probably not the case in 2008 when this question was asked, but this is pretty easy these days:
[UIView animateWithDuration:0.5 animations:^{
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.iconView cache:YES];
/* changes to the view made here will be reflected on the flipped to side */
}];
Note: Apparently, this only works on iOS.

Resources