Shadow in NSScrollView in Mac OS X app - macos

I need two things similar to Pages app in my Mac OS X app. Please view the attached screen shot.
I need a shadow on NSScrollView as shown in Mac OS X Pages app.
I want my scroll bar to be like the one in Mac OS X Pages app.

A quick and easy way of getting the top shadow is to override the enclosing clip view. This is not ideal however, because it draws the shadow (actually a gradient) behind the controls. I find it good enough.
#import <Cocoa/Cocoa.h>
// DFInvertedClipView.h
IB_DESIGNABLE
#interface DFInvertedClipView : NSClipView
#property IBInspectable BOOL shouldDrawTopShadow;
#end
// DFInvertedClipView.m
#import "DFInvertedClipView.h"
#interface DFInvertedClipView ()
#property CGFloat startAlpha;
#end
#implementation DFInvertedClipView
- (BOOL) isFlipped {
return true;
}
-(void) drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
if (_shouldDrawTopShadow) {
NSGradient * gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.6 alpha:self.startAlpha]
endingColor:[NSColor colorWithCalibratedWhite:0.6 alpha:0.0]];
NSRect b = [self bounds];
NSRect topFade = NSMakeRect(NSMinX(b), NSMinY(b), NSWidth(b), 5.0);
[gradient drawInRect:topFade angle:90];
NSBezierPath *topLine = [NSBezierPath bezierPath];
[topLine moveToPoint:NSMakePoint(NSMinX(self.bounds), NSMinY(self.bounds))];
[topLine lineToPoint:NSMakePoint(NSMaxX(self.bounds), NSMinY(self.bounds))];
CGFloat lineWidth = [[NSScreen mainScreen] backingScaleFactor];
[topLine setLineWidth:lineWidth];
[[NSColor colorWithCalibratedWhite:0.5 alpha:self.startAlpha] setStroke];
[topLine stroke];
}
}
-(void) scrollToPoint:(NSPoint)newOrigin
{
[super scrollToPoint:newOrigin];
// Grade the shadow darkness based on the displacement from a flush fit
CGFloat displacementForFullShadow = 40.0; // pixels
if (newOrigin.y > 0) {
CGFloat alpha = 1.0/displacementForFullShadow * newOrigin.y; // e.g. linear grade function y = m*x + c (m = 1/displacementForFullShadow, c = 0.0)
if (alpha > 1.0) {
alpha = 1.0;
}
self.startAlpha = alpha;
} else {
self.startAlpha = 0.0;
}
}

The shadow of the view above is achieved by setting custom CALayer options of this view (you'll be probably interested in mask-property). Just check the View Effects Inspector from the IB (or you can do it programmatically too - read this)
To remove scroll view knobs' light style open NSScrollView attributes inspector in IB and select Scroller Knobs style to "Default Style" (second line).

Related

Subclass NSProgressIndicator

i like to subclass a NSProgressIndicator. I've used this code and i set the Subclass in the Interface Builder:
- (void)drawRect:(NSRect)dirtyRect {
NSRect rect = NSInsetRect([self bounds], 1.0, 1.0);
CGFloat radius = rect.size.height / 2;
NSBezierPath *bz = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:radius yRadius:radius];
[bz setLineWidth:2.0];
[[NSColor blackColor] set];
[bz stroke];
rect = NSInsetRect(rect, 2.0, 2.0);
radius = rect.size.height / 2;
bz = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:radius yRadius:radius];
[bz setLineWidth:1.0];
[bz addClip];
rect.size.width = floor(rect.size.width * ([self doubleValue] / [self maxValue]));
NSRectFill(rect);
When the app starts its looks like this:
But during the copy progress the old bar shows up.
Whats wrong?
It seems that the progress bar's progress is not drawn in drawRect:, so just overriding drawRect: is not enough. However, if you make the progress bar layer backed, you are responsible for doing all the drawing.
From the documentation:
The view class automatically creates a backing layer for you (using
makeBackingLayer if overridden), and you must use the view class’s
drawing mechanisms.
Check the "Core Animation Layer" in IB or add this to your sub class:
- (void)awakeFromNib {
[super awakeFromNib];
[self setWantsLayer:YES];
}
I followed the above advice (thanks!), but unfortunately discovered that when the NSProgressIndicator was resized, it disappeared, but only on the first viewing (it was inside a drawer).
Rather than try and understand what was happening, I realised you don't actually need the old control, as what I was doing is very simple (a strength indicator that changes color). Just create a subclass of NSView.
So this is it:
#interface SSStrengthIndicator : NSView
/// Set the indicator based upon a score from 0..4
#property (nonatomic) double strengthScore;
#end
#implementation SSStrengthIndicator
- (void)setStrengthScore:(double)strength
{
if (_strengthScore != strength) {
_strengthScore = strength;
[self setNeedsDisplay:YES];
}
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRect rect = NSInsetRect([self bounds], 1.0, 2.0);
double val = (_strengthScore + 1) * 20;
if (val <= 40)
[[NSColor redColor] set];
else if (val <= 60)
[[NSColor yellowColor] set];
else
[[NSColor greenColor] set];
rect.size.width = floor(rect.size.width * (val / 100.0));
[NSBezierPath fillRect:rect];
}
#end

Creating an animatable translucent overlay with Core Animation layers

I'm creating a spotlight that moves over content in my app, like so:
In the sample app (shown above), the background layer is blue, and I have a layer over it that darkens all of it, except a circle that shows it normally. I've got this working (you can see how in the code below). In my real app, there is actual content in other CALayers, rather than just blue.
Here's my problem: it doesn't animate. I'm using CGContext drawing to create the circle (which is an empty spot in an otherwise black layer). When you click the button in my sample app, I draw the circle at a different size in a different location.
I would like that to smoothly translate and scale, instead of jumping, as it currently does. It may require a different method of creating the spotlight effect, or there might be a way I don't know of to implicitly animate the -drawLayer:inContext: call.
It's easy to create the sample app:
Make a new Cocoa app (using ARC)
Add the Quartz framework
Drop a custom view and a button onto the XIB
Link the custom view to a new class (SpotlightView), with code provided below
Delete SpotlightView.h, since I included its contents in SpotlightView.m
Set the button's outlet to the -moveSpotlight: action
Update (the mask property)
I like David Rönnqvist's suggestion in comments to use the mask property of the darkened layer to cut out a hole, which I could then move independently. The problem is that for some reason, the mask property works the opposite of how I expect a mask to work. When I specify a circular mask, all that shows up is the circle. I expected the mask to work in the opposite manner, masking out the area with 0 alpha.
Masking feels like the right way to go about this, but if I have to fill in the entire layer and cut out a hole, then I may as well do it the way I originally posted. Does anyone know how to invert the -[CALayer mask] property, so that the area drawn in gets cut out from the layer's image?
/Update
Here's the code for SpotlightView:
//
// SpotlightView.m
//
#import <Quartz/Quartz.h>
#interface SpotlightView : NSView
- (IBAction)moveSpotlight:(id)sender;
#end
#interface SpotlightView ()
#property (strong) CALayer *spotlightLayer;
#property (assign) CGRect highlightRect;
#end
#implementation SpotlightView
#synthesize spotlightLayer;
#synthesize highlightRect;
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
self.wantsLayer = YES;
self.highlightRect = CGRectNull;
self.spotlightLayer = [CALayer layer];
self.spotlightLayer.frame = CGRectInset(self.layer.bounds, -50, -50);
self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
self.spotlightLayer.opacity = 0.60;
self.spotlightLayer.delegate = self;
CIFilter *blurFilter = [CIFilter filterWithName:#"CIGaussianBlur"];
[blurFilter setValue:[NSNumber numberWithFloat:5.0]
forKey:#"inputRadius"];
self.spotlightLayer.filters = [NSArray arrayWithObject:blurFilter];
[self.layer addSublayer:self.spotlightLayer];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {}
- (void)moveSpotlight:(id)sender {
[self.spotlightLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
if (layer == self.spotlightLayer) {
CGContextSaveGState(ctx);
CGColorRef blackColor = CGColorCreateGenericGray(0.0, 1.0);
CGContextSetFillColorWithColor(ctx, blackColor);
CGColorRelease(blackColor);
CGContextClearRect(ctx, layer.bounds);
CGContextFillRect(ctx, layer.bounds);
// Causes the toggling
if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) {
self.highlightRect = CGRectMake(25, 25, 100, 100);
} else {
self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50,
NSMaxY(self.layer.bounds) - 50,
25, 25);
}
CGRect drawnRect = [layer convertRect:self.highlightRect
fromLayer:self.layer];
CGMutablePathRef highlightPath = CGPathCreateMutable();
CGPathAddEllipseInRect(highlightPath, NULL, drawnRect);
CGContextAddPath(ctx, highlightPath);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGContextFillPath(ctx);
CGPathRelease(highlightPath);
CGContextRestoreGState(ctx);
}
else {
CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0);
CGContextSetFillColorWithColor(ctx, blueColor);
CGContextFillRect(ctx, layer.bounds);
CGColorRelease(blueColor);
}
}
#end
I finally got it. What prodded me to the answer was hearing of the CAShapeLayer class. At first, I thought it would be a simpler way to draw the layer's contents, rather than drawing and clearing the contents of a standard CALayer. But I read the documentation of the path property of CAShapeLayer, which stated it could be animated, but not implicitly.
While a layer mask might have been more intuitive and elegant, it doesn't seem to be possible to use the mask to hide a portion of the owner's layer, rather than showing a portion, and so I couldn't use it. I'm happy with this solution, as it's pretty clear what's going on. I wish it used implicit animation, but the animation code is only a few lines.
Below, I've modified the sample code from the question to add smooth animation. (I removed the CIFilter code, because it was extraneous. The solution does still work with filters.)
//
// SpotlightView.m
//
#import <Quartz/Quartz.h>
#interface SpotlightView : NSView
- (IBAction)moveSpotlight:(id)sender;
#end
#interface SpotlightView ()
#property (strong) CAShapeLayer *spotlightLayer;
#property (assign) CGRect highlightRect;
#end
#implementation SpotlightView
#synthesize spotlightLayer;
#synthesize highlightRect;
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
self.wantsLayer = YES;
self.highlightRect = CGRectNull;
self.spotlightLayer = [CAShapeLayer layer];
self.spotlightLayer.frame = self.layer.bounds;
self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
self.spotlightLayer.fillRule = kCAFillRuleEvenOdd;
CGColorRef blackoutColor = CGColorCreateGenericGray(0.0, 0.60);
self.spotlightLayer.fillColor = blackoutColor;
CGColorRelease(blackoutColor);
[self.layer addSublayer:self.spotlightLayer];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {}
- (CGPathRef)newSpotlightPathInRect:(CGRect)containerRect
withHighlight:(CGRect)spotlightRect {
CGMutablePathRef shape = CGPathCreateMutable();
CGPathAddRect(shape, NULL, containerRect);
if (!CGRectIsNull(spotlightRect)) {
CGPathAddEllipseInRect(shape, NULL, spotlightRect);
}
return shape;
}
- (void)moveSpotlight {
CGPathRef toShape = [self newSpotlightPathInRect:self.spotlightLayer.bounds
withHighlight:self.highlightRect];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"path"];
pathAnimation.fromValue = (__bridge id)self.spotlightLayer.path;
pathAnimation.toValue = (__bridge id)toShape;
[self.spotlightLayer addAnimation:pathAnimation forKey:#"path"];
self.spotlightLayer.path = toShape;
CGPathRelease(toShape);
}
- (void)moveSpotlight:(id)sender {
if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) {
self.highlightRect = CGRectMake(25, 25, 100, 100);
} else {
self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50,
NSMaxY(self.layer.bounds) - 50,
25, 25);
}
[self moveSpotlight];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0);
CGContextSetFillColorWithColor(ctx, blueColor);
CGContextFillRect(ctx, layer.bounds);
CGColorRelease(blueColor);
}
#end

Problems faced while customizing NSTextFieldCell

I am trying to make cells in an outline view just like we have for users in Skype's message window.
For this I created a custom class:
IconNameCell.h
#interface IconNameCell : NSTextFieldCell {
//#private
NSImage *userImage; // size (17,17)
NSImage *statusIcon; // size (14,14)
NSString *cellText;
}
#property (readwrite, retain) NSImage *userImage;
#property (readwrite, retain) NSImage *statusIcon;
#property (readwrite, retain) NSString *cellText;
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
#end
IconNameCell.m
#implementation IconNameCell
#synthesize userImage;
#synthesize statusIcon;
#synthesize cellText;
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
#try{
// Inset the cell frame to give everything a little horizontal padding
NSRect anInsetRect = NSInsetRect(cellFrame,2.0,0);
//FIXME: flip coordinates and size can be set in accessor methods
// setting userImage and statusIcon in flipped coordinate
[userImage setFlipped:YES];
[statusIcon setFlipped:YES];
// setting size of image and icon
[userImage setSize:NSMakeSize(25.0, 25.0)];
[statusIcon setSize:NSMakeSize(15.0, 17.0)];
// setting attributes of cell text
NSMutableParagraphStyle *dParagraphStyle = [[NSMutableParagraphStyle alloc] init];
[dParagraphStyle setAlignment:NSLeftTextAlignment];
NSColor *colorOfText;
if ([self isHighlighted]) {
colorOfText = [NSColor whiteColor];
}
else {
colorOfText = [NSColor blackColor];
}
NSMutableDictionary * dTitleAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
colorOfText,NSForegroundColorAttributeName,
[NSFont systemFontOfSize:11.0],NSFontAttributeName,
dParagraphStyle, NSParagraphStyleAttributeName,
nil];
// getting sizes
NSSize cellTextSize = [cellText sizeWithAttributes:dTitleAttributes];
NSSize userImageSize = [userImage size];
NSSize statusIconSize = [statusIcon size];
// making layout boxes for all elements
// vertical padding between the lines of text
float dVerticalPadding = 2.0;
// horizontal padding between two images
float padBtwnImgs = 4.0;
// horizontal padding between image and text
float padBtwnImgText = 6.0;
NSString *userImageName = [userImage name];
NSLog(#"userImageName - %# / cellText- %#",userImageName,cellText); // getting null for userImageName
//if ([userImageName isEqualToString:#"current_D.png"]) {
//FIXME: this is juggad and should be corrected
NSRange rangeOfComma = [cellText rangeOfString:#","];
if (rangeOfComma.length !=0 ) {
//<#statements#>
// userImage box: center the userImage vertically inside of the inset rect
NSRect cellTitleBox = NSMakeRect(anInsetRect.origin.x,
anInsetRect.origin.y + anInsetRect.size.height*.5 - cellTextSize.height*.5,
cellTextSize.width,
cellTextSize.height);
// drawing cell text
[cellText drawInRect:cellTitleBox withAttributes:dTitleAttributes];
}
else {
// userImage box: center the userImage vertically inside of the inset rect
NSRect userImageBox = NSMakeRect(anInsetRect.origin.x,
anInsetRect.origin.y + anInsetRect.size.height*.5 - userImageSize.height*.5,
userImageSize.width,
userImageSize.height);
// statusIcon box: center the statusIcon vertically inside of the inset rect
NSRect statusIconBox = NSMakeRect(userImageBox.origin.x + userImageBox.size.width + padBtwnImgs,
anInsetRect.origin.y + anInsetRect.size.height*.5 - statusIconSize.height*.5,
statusIconSize.width,
statusIconSize.height);
// cellTitleBox: vertically aligning text
NSRect cellTitleBox = NSMakeRect(statusIconBox.origin.x + statusIconBox.size.width + padBtwnImgText,
anInsetRect.origin.y + anInsetRect.size.height*.5 - cellTextSize.height*.5,
cellTextSize.width,
cellTextSize.height);
// drawing user image
[userImage drawInRect:userImageBox fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
// drawing user status
[statusIcon drawInRect:statusIconBox fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
// drawing cell text
[cellText drawInRect:cellTitleBox withAttributes:dTitleAttributes];
}
}
#catch (NSException *e) {
NSLog(#"IconNameCell -%#",e);
}
}
#end
2nd, I assigned text field cell for outline view the class: IconNameCell in IB
3rd, I used this code in delegate to set image, icon and name of user in custom cell-
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item{
if ([[tableColumn identifier] isEqualToString:#"userInfo"]) {
// some relevant code
if( [[item onlineStatus] intValue] == 0 ){
[cell setStatusIcon:[NSImage imageNamed:#"offline.png"]];
}
else{
[cell setStatusIcon:[NSImage imageNamed:#"online.png"]];
}
//similarly, setting attribute for userImage and cellText
}
I am facing two problems with it: 1. Application is frequently crashing when I am selecting one or the other row in outline view. Earlier when I had not taken the customized cell but three different columns for - user image, user status and user name it was working fine! 2. For the log: NSLog(#"userImageName - %# / cellText- %#",userImageName,cellText); I am getting (null) as userImageName, although I should get some string value for it.
Can anyone suggest me some solution for it??
Thanks,
Miraaj
Application is frequently crashing when I am selecting one or the other row in outline view.
Please edit your question to include the crash log.
2. For the log: NSLog(#"userImageName - %# / cellText- %#",userImageName,cellText); I am getting (null) as userImageName, although I should get some string value for it.
Have you set the image's name with setName: or obtained it from imageNamed:? Otherwise, it doesn't have one, and this output is correct.

How do I manage to draw a few images in Custom View and Drag&Drop them

I'm writting a Cocoa app. I've wrote a code that can Drag&Drop one Image. But now I need to draw several Images by double click and D&D them. Each double click- new image, each click on existing image- starts D&D. Problem is in realization. I can't imagine a simple way of realization. Can anybody propose a solution?
Thanks.
#import "DotView.h"
#implementation DotView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
center.x = center.y = 100.0;
}
return self;
}
- (void)drawRect:(NSRect)rect {
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
NSRectFill(bounds);
[[NSGraphicsContext currentContext]
setImageInterpolation: NSImageInterpolationHigh];
NSSize viewSize = [self bounds].size;
NSSize imageSize = { 50, 40 };
NSPoint imageOrigin = center;
imageOrigin.x -= imageSize.width * 0.50;
imageOrigin.y -= imageSize.height * 0.50;
NSRect destRect;
destRect.origin = imageOrigin;
destRect.size = imageSize;
NSString * file = #"/Users/classuser/Desktop/ded.jpg";
NSImage * image = [[NSImage alloc] initWithContentsOfFile:file];
[image setFlipped:NO];
[image drawInRect: destRect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];
NSBezierPath * path = [NSBezierPath bezierPathWithRect:destRect];
}
-(void)mouseDown:(NSEvent*)event
{
NSPoint point = [event locationInWindow];
if(center.x<point.x+25 && center.x>point.x-25)
if(center.y<point.y+20 && center.y>point.y-20)
center = [self convertPoint:point fromView:nil];
}
- (void) mouseDragged:(NSEvent*)event {
[self mouseDown:event];
[self setNeedsDisplay:YES];
}
#end
Read:
Cocoa Drawing Guide
and
View Programming Guide for Cocoa
and
Drag and Drop Programming Topics for Cocoa
Once you've read these, ask more targeted questions - this is a bit too broad to answer simply.

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