NSView in cocoa refuses to redraw, whatever I try - xcode

I'm writing an application on mac to measure colors on screen. For that I have a ColorView that fills a bezierpath with a specific color. When measuring it goes well for most of the patches, but at a given moment the redraw staggers and the view does not get updated anymore. I have been looking for a while now, tried many proposed solutions. None of them are waterproof.
My actual code:
[colorView setColor:[colorsForMeasuring objectAtIndex:index]];
[colorView setNeedsDisplay:YES];
[colorView setNeedsLayout:YES];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue,^{
[colorView setNeedsDisplay:YES];
[colorView setNeedsLayout:YES];
[colorView updateLayer];
[colorView displayIfNeeded];
[colorView display];
});
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
[NSApp runModalSession:aSession];
The drawcode of my colorView is as follows:
- (void)display
{
CALayer *layer = self.layer;
[layer setNeedsDisplay];
[layer displayIfNeeded];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
[self internalDrawWithRect:self.bounds];
}
- (void)internalDrawWithRect:(CGRect)rect
{
NSRect bounds = [self bounds];
[color set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRect:(CGRect)rect {
[self internalDrawWithRect:rect];
}
Any help would be very welcome! My original code was much simpler, but I kept on adding things that might have helped.

As I said, the original code was rather straightfoward and simple:
The measuring loop:
for ( uint32 index = 0; index < numberOfColors && !stopMeasuring; index++ )
{
#ifndef NDEBUG
NSLog( #"Color on screen:%#", [colorsForMeasuring objectAtIndex:index] );
#endif
[colorView setColor:[colorsForMeasuring objectAtIndex:index]];
[colorView setNeedsDisplay:YES];
[colorView displayIfNeeded];
// Measure the displayed color in XYZ values
CGFloat myRed, myGreen, myBlue, myAlpha;
[[colorsForMeasuring objectAtIndex:index] getRed:&myRed green:&myGreen blue:&myBlue alpha:&myAlpha];
float theR = (float) myRed;
float theG = (float) myGreen;
float theB = (float) myBlue;
xyzData = [measDeviceController measureOnceXYZforRed:255.0f*theR Green:255.0f*theG Blue:255.0f*theB];
if ( [xyzData count] > 0 )
{
measuringResults[index*3] = [[xyzData objectAtIndex:0] floatValue];
measuringResults[(index*3) + 1] = [[xyzData objectAtIndex:1] floatValue];
measuringResults[(index*3) + 2] = [[xyzData objectAtIndex:2] floatValue];
#ifndef NDEBUG
printf("Measured value X=%f, Y=%f, Z=%f\n", measuringResults[index*3], measuringResults[(index*3) + 1], measuringResults[(index*3) + 2]);
#endif
} }
The drawing of the MCTD_ColorView which just is inherited from NSView:
- (void)setColor:(NSColor *)aColor;
{
[aColor retain];
[color release];
color = aColor;
}
- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[color set];
[NSBezierPath fillRect:bounds];
}
- (void)dealloc
{
[color release];
[super dealloc];
}
When running my measurement loop and after putting breakpoints in "setColor" and "drawRect", debugging always stops in setColor but never in drawRect. Without debugging, the view remains white while I should see all different colors appearing.
As can be seen in my first post, I have tried many things to get it drawing, but they all failed.

Related

NSTextField (multiline) grow horizontally after filling all lines

I have multiline NSTexField, that I use to show static text, that may have different length on each showing of window. This NSTextField has a height constrain, that limits it to two lines.
I want to setup behaviour, that NSTextField will grow horizontally only just so much, that it will fill those two horizontal lines.
Now when I set up horizontal compression resistance higher, text field width grows so much to display all the text on one line. Is this possible to achieve only by using autolayout constrains or do I have to calculate nstextfield width somehow?
I tried overloading "layout" method in superview. But its not working as I expect.
- (void)layout
{
[self solveLayoutForView:self];
}
- (void)solveLayoutForView:(NSView*)view
{
for (NSView* subView in [view subviews])
{
if (subView.subviews.count)
[self solveLayoutForView:subView];
else
{
NSLayoutConstraint* height = [subView constraintForAttribute:NSLayoutAttributeHeight];
if (height && height.constant > 17 && [subView isKindOfClass:[NSTextField class]]) // seems like multiline nstextfield
{
NSTextField* textField = (NSTextField*)subView;
textField.preferredMaxLayoutWidth = 0;
[super layout];
textField.preferredMaxLayoutWidth = NSWidth([textField alignmentRectForFrame:textField.frame]);
[super layout];
}
}
}
}
I ended up calculating it by myself.
I overload setStringValue in subclass of NSTextField.
"PreferGrowingHorizontally" is a subclass property if I ever need to skip this calculation (e.g.: in case I would like it to grow horizontally)
- (void)setStringValue:(NSString *)stringValue
{
BOOL changed = ![self.stringValue isEqualToString:stringValue] ? YES : NO;
[super setStringValue:stringValue];
if (stringValue.length && changed)
{
if (self.frame.size.height > 17 && self.preferGrowingHorizontally)
[self compensateWidth];
}
}
- (void)compensateWidth
{
float requiredHeight = FLT_MAX;
int widthCompensation = 0;
while (requiredHeight > self.frame.size.height)
requiredHeight = [self heightForStringDrawing:self.stringValue Font:self.font Width:self.frame.size.width + widthCompensation++];
if (--widthCompensation)
{
NSLayoutConstraint* width = [self constraintForAttribute:NSLayoutAttributeWidth];
if (width)
width.constant = self.frame.size.width + widthCompensation;
NSLog(#"requiredHeight: %f width compensation: %d for textField: %#", requiredHeight, widthCompensation, self.stringValue);
}
}
- (float)heightForStringDrawing:(NSString*)myString Font:(NSFont*)myFont Width:(float)myWidth
{
NSTextStorage *textStorage = [[[NSTextStorage alloc] initWithString:myString] autorelease];
NSTextContainer *textContainer = [[[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)] autorelease];
NSLayoutManager *layoutManager = [[[NSLayoutManager alloc] init] autorelease];
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
[textStorage addAttribute:NSFontAttributeName value:myFont
range:NSMakeRange(0, [textStorage length])];
[textContainer setLineFragmentPadding:0.0];
textContainer.lineBreakMode = NSLineBreakByWordWrapping;
(void) [layoutManager glyphRangeForTextContainer:textContainer];
return [layoutManager usedRectForTextContainer:textContainer].size.height;
}

setNeedsDisplay not resulting in dirtyRect filling entire view

I am having a confusing issue. I try to draw a graph, and call setNeedsDisplay each time new data is added to the graph.
The graph receives new data via addData:(NSNotification*)theNote, which then tries several ways to call setNeedsDisplay to redraw the view. The data does not get redrawn properly, however, at this time. If I drag the window to another screen, or if I click on the graph and call setNeedsDisplay from mouseDown then the view is redrawn correctly. By monitoring the size of dirtyRect using NSLog, I have tracked the problem to a too small dirtyRect. But I cannot even guess how to correct this. Any ideas out there?
//
// graphView.m
// miniMRTOF
//
// Created by シューリ ピーター on 8/1/16.
// Copyright © 2016 シューリ ピーター. All rights reserved.
//
#import "graphView.h"
#implementation graphView
- (id)initWithFrame:(NSRect)frame{
self = [super initWithFrame:frame];
if (self) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(addData:) name:AddDataNotification object:nil];
theData = [[NSMutableArray alloc] init];
NSLog(#"graphView initialized");
}
return self;
}
-(void)addData:(NSNotification*)theNote{
NSLog(#"graphView has gotten the note");
NSMutableArray *theThermalArray = [[NSMutableArray alloc] init];
theThermalArray = [[theNote userInfo] objectForKey:#"theThermals"];
float ave = 0;
int n = 0;
for (int i=0; i<16; i++){
float f_i = [[theThermalArray objectAtIndex:i] floatValue];
ave += f_i;
if(f_i != 0) n++;
}
ave /= n;
[theData addObject:[NSNumber numberWithFloat:ave]];
NSLog(#"graphView has added %0.3f and now has %lu components", ave, (unsigned long)[theData count]);
[self setNeedsDisplay:YES];
[[self.window contentView] setNeedsDisplay:YES];
[self performSelectorOnMainThread:#selector(redraw) withObject:nil waitUntilDone:YES];
}
-(void)redraw{
[self setNeedsDisplay:YES];
[[self.window contentView] setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"dirtyRect: x:%0.2f, y:%0.2f, w:%0.2f, h:%0.2f", dirtyRect.origin.x, dirtyRect.origin.y, dirtyRect.size.width, dirtyRect.size.height);
float xScale = ([self bounds].size.width)/((float)[theData count]+1e-3);//(maxX - minX);
float yScale = [self bounds].size.height/(maxT - minT);
[[NSColor whiteColor] set];
NSRect fillArea = [self bounds];
[NSBezierPath fillRect:fillArea];
if(dirtyRect.size.height < 100){
NSLog(#"small dirtyRect");
[[NSColor grayColor] set];
[NSBezierPath fillRect:dirtyRect];
}
dirtyRect = [self bounds];
int dataCount = (int)[theData count];
NSBezierPath *pathForFrame = [[NSBezierPath alloc] init];
NSPoint P0 = {1,1};
NSPoint P1 = {1, [self bounds].size.height};
NSPoint P2 = {[self bounds].size.width, 1};
[pathForFrame moveToPoint:P0];
[pathForFrame lineToPoint:P1];
[pathForFrame moveToPoint:P0];
[pathForFrame lineToPoint:P2];
[[NSColor redColor] set];
[pathForFrame stroke];
NSLog(#"drawing %i points", dataCount);
NSBezierPath *pathForPlot = [[NSBezierPath alloc] init];
if(dataCount<maxRawPlotData){
if(dataCount>1){
NSPoint p1;
p1.y = [[theData objectAtIndex:0] floatValue];
p1.x = (0-minX)*xScale;
p1.y = (p1.y-minT)*yScale;
[pathForPlot moveToPoint:p1];
NSLog(#"point: %0.1f, %0.1f", p1.x, p1.y);
}
for(int i=1; i<dataCount; i++){
NSPoint p;
p.y = [[theData objectAtIndex:i] floatValue];
p.x = (i-minX)*xScale;
p.y = (p.y-minT)*yScale;
[pathForPlot lineToPoint:p];
NSLog(#"point: %0.1f, %0.1f", p.x, p.y);
}
}
[[NSColor blackColor] set];
[pathForPlot stroke];
[txtMaxX setStringValue:[NSString stringWithFormat:#"%0.0f",maxX]];
}
-(void)controlTextDidEndEditing:(NSNotification *)obj{
NSLog(#"controlTextDidEndEditing");
if([[obj object] isEqualTo:txtMaxT]){
maxT = [txtMaxT floatValue];
[txtMidT setStringValue:[NSString stringWithFormat:#"%0.1f",0.5*(maxT+minT)]];
}
else if([[obj object] isEqualTo:txtMinT]){
minT = [txtMinT floatValue];
[txtMidT setStringValue:[NSString stringWithFormat:#"%0.1f",0.5*(maxT+minT)]];
}
else if([[obj object] isEqualTo:txtMaxX]){
maxX = [txtMaxX floatValue];
}
else if([[obj object] isEqualTo:txtMinX]){
minX = [txtMinX floatValue];
}
else NSLog(#"How'd we get here?");
[[[obj object] window] makeFirstResponder:[[obj object] window].contentView];
[self setNeedsDisplay:YES];
[self display];
}
-(void)mouseDown:(NSEvent *)theEvent{
NSLog(#"mousedown");
[self setNeedsDisplay:YES];
}
#end

Transparent NSWindow + custom NSView - don't want custom drawn objects to cast shadows?

So I have a borderless window with it's background color set to clear. Inside it, I have an NSView with some custom drawing. It looks like this:
https://www.dropbox.com/s/16dh9ez3z04eic7/Screenshot%202016-02-10%2012.15.35.png?dl=0
The problem is, the custom drawing is casting a shadow, which you can see if the view is hidden:
https://www.dropbox.com/s/04ymp0b2yqi5egu/Screenshot%202016-02-10%2012.19.04.png?dl=0
I only want the shadow around the frame of the window! How can I achieve this?
NOTE: strangely enough, giving the window a title bar provides the behavior I want: https://www.dropbox.com/s/1vv9iwb5403tufe/Screenshot%202016-02-10%2012.25.29.png?dl=0 - Unfortunately, I don't want a title bar.
The code:
#implementation MyWindow
/*
In Interface Builder, the class for the window is set to this subclass. Overriding the initializer
provides a mechanism for controlling how objects of this class are created.
*/
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)aStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)flag {
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:NO];
if (self != nil) {
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
[self makeKeyAndOrderFront:NSApp];
[self setMovable:YES];
[self setShowsResizeIndicator:YES];
[self setResizeIncrements:NSMakeSize(2, 2)];
[self setLevel:TOP_LEVEL];
}
return self;
}
/*
Custom windows that use the NSBorderlessWindowMask can't become key by default. Override this method
so that controls in this window will be enabled.
*/
- (BOOL)canBecomeKeyWindow {
return YES;
}
#end
And the ContentView:
#implementation WindowContentView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// we need to redraw the whole frame
dirtyRect = self.bounds;
// background
NSBezierPath *framePath = [NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:0.0f yRadius:0.0f];
[[NSColor colorWithCalibratedRed:0.0f/255.0f green:255.0f/255.0f blue:153.0f/255.0f alpha:0.3f] setFill];
[framePath fill];
// border
[[NSColor colorWithCalibratedRed:0.0f/255.0f green:255.0f/255.0f blue:153.0f/255.0f alpha:1.0f] setStroke];
framePath.lineWidth = 1.0f;
[framePath stroke];
// grid
[self drawGridInRect:dirtyRect];
[super drawRect:dirtyRect];
}
- (void)drawGridInRect:(NSRect)rect {
int rows = 5;
int columns = 5;
float gridWidth = CGRectGetWidth(rect)/columns;
float gridHeight = CGRectGetHeight(rect)/rows;
[[NSColor colorWithCalibratedRed:0.0f/255.0f green:255.0f/255.0f blue:153.0f/255.0f alpha:0.2f] setStroke];
[NSBezierPath setDefaultLineWidth:1.0f];
for(int i=1; i<rows; i++) {
[NSBezierPath strokeLineFromPoint:NSMakePoint(gridWidth*i, CGRectGetMaxY(rect))
toPoint:NSMakePoint(gridWidth*i, CGRectGetMinY(rect))];
}
for(int i=1; i<columns; i++) {
[NSBezierPath strokeLineFromPoint:NSMakePoint(CGRectGetMaxX(rect), gridHeight*i)
toPoint:NSMakePoint(CGRectGetMinX(rect), gridHeight*i)];
}
}
#end

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