Comportamiento “flip” de widgets en Core Animation / Cocoa

Intento crear una clase de Tarjeta que duplique el comportamiento de los widgets del Tablero en el que puede poner controles o imágenes o lo que sea en los dos lados de la tarjeta y alternar entre ellos.

Las vistas respaldadas por capas tienen una propiedad de transformación, pero alterar eso no hace lo que esperaría que hiciera (al girar la capa alrededor del eje y se pliega hacia el lado izquierdo).

Me señalaron algunas características no documentadas y un archivo .h llamado cgsprivate.h, pero me pregunto si hay una forma oficial de hacerlo. Este software debería ser enviado y no me gustaría verlo fallar más tarde porque los chicos de Apple lo sacan en 10.6.

Alguien tiene alguna idea de cómo hacer esto? Es tan extraño para mí que un widget simple sería tan difícil de hacer en Core Animation.

¡Gracias por adelantado!

EDITAR: Puedo lograr este comportamiento con imágenes que están en capas, pero no sé cómo obtener controles / vistas / lo que sea más avanzado en las capas. El ejemplo de la tarjeta usa imágenes.

    Mike Lee tiene una implementación del efecto de volteo para el cual ha lanzado un código de muestra . (Desafortunadamente, esto ya no está disponible en línea, pero Drew McCormack se basó en eso en su propia implementación .) Parece que toma las capas para intercambiar las vistas de “fondo” y “primer plano”, usa un CATransform3D para rotar el dos vistas en la animación, y luego intercambia las vistas una vez que la animación se ha completado.

    Al utilizar las capas de las vistas, evita tener que almacenar en caché un bitmap, ya que eso es lo que hacen las capas de todos modos. En cualquier caso, su controlador de vista parece ser una buena solución para lo que quiere.

    Usando Core Animation como e.James delineado … Nota, esto está usando la recolección de basura y una capa alojada:

    #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 

    El archivo de encabezado:

     @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 

    EDITAR: se animará para pasar de una ventana a otra ventana. Puede aplicar los mismos principios a una vista.

    Es excesivo para tus propósitos (ya que contiene una aplicación de referencia de juegos de mesa y cartas en gran parte completa), pero echa un vistazo a esta muestra de ADC . Los juegos de cartas incluidos hacen ese efecto de volteo bastante bien.

    Si puede hacer esto con imágenes, quizás pueda mantener todos sus controles en un objeto NSView (como siempre), y luego renderizar el NSView en una imagen de bitmap usando cacheDisplayInRect:toBitmapImageRep: justo antes de ejecutar el efecto de volteo. Los pasos serían:

    1. NSView el NSView a un bitmap
    2. Muestra ese bitmap en una capa adecuada para el efecto de volteo
    3. Ocultar el NSView y exponer la capa de la imagen
    4. Realice el efecto de volteo

    Sé que esto es tarde, pero Apple tiene un proyecto de ejemplo aquí que puede ser de ayuda para cualquiera que todavía se tropiece con esta pregunta.

    https://developer.apple.com/library/mac/#samplecode/ImageTransition/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010277

    Hay una implementación completa de código abierto de esto por los chicos de Mizage.

    Puede consultarlo aquí: https://github.com/mizage/Flip-Animation

    Probablemente no sea el caso en 2008 cuando se hizo esta pregunta, pero esto es bastante fácil en estos días:

     [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 */ }]; 

    Nota: Aparentemente, esto solo funciona en iOS.