NSWindow flip animation (easy and universal)

2019-02-28 08:51发布

How to make flip animation for OS X application windows without complex coding?

1条回答
等我变得足够好
2楼-- · 2019-02-28 09:39

Finally, I did it. I have created object that work with NSWindowController objects instead of NSWidows.

ALWindowFlipAnimator.h

#import <Foundation/Foundation.h>

//............................................................................................................
//      Shorten macroes:
#define FLIPANIMATOR  [ALWindowFlipAnimator sharedWindowFlipAnimator]

//............................................................................................................
//      Window flip direction:
typedef NS_ENUM(NSUInteger, ALFlipDirection)
{
  ALFlipDirectionLeft,
  ALFlipDirectionRight,
  ALFlipDirectionUp,
  ALFlipDirectionDown
};

@interface ALWindowFlipAnimator : NSObject

+(ALWindowFlipAnimator *)sharedWindowFlipAnimator;
-(void)flipToWindowNibName:(NSString *)nibName direction:(ALFlipDirection)direction;

@end

ALWindowFlipAnimator.m

#import "ALWindowFlipAnimator.h"
#import <QuartzCore/QuartzCore.h>

@implementation ALWindowFlipAnimator
{
  NSWindowController *_currentWindowController; // Current window controller
  NSWindowController *_nextWindowController;    // Next window controller to flip to
  NSWindow *_animationWindow;                   // Window where flip animation plays
}

//============================================================================================================
//      Initialize flip window controller
//============================================================================================================
-(id)init
{
  self = [super init];
  if (self)
  {
    _currentWindowController = nil;
    _nextWindowController = nil;
    _animationWindow = nil;
  }
  return self;
}

//============================================================================================================
//      Create shared flip window manager
//============================================================================================================
+(ALWindowFlipAnimator *)sharedWindowFlipAnimator
{
  static ALWindowFlipAnimator *wfa = nil;
  if (!wfa) wfa = [[ALWindowFlipAnimator alloc] init];
  return wfa;
}

//============================================================================================================
//      Flip to window with selected NIB file name from the current one
//============================================================================================================
-(void)flipToWindowNibName:(NSString *)nibName direction:(ALFlipDirection)direction
{
  if (!_currentWindowController || ![[_currentWindowController window] isVisible])
  {
    // No current window controller or window is closed
    _currentWindowController = [[NSClassFromString(nibName) alloc] initWithWindowNibName:nibName];
    [_currentWindowController showWindow:self];
  }
  else
  {
    if ([[_currentWindowController className] isEqualToString:nibName])
      // Bring current window to front
      [[_currentWindowController window] makeKeyAndOrderFront:self];
    else
    {
      // Flip to new window
      _nextWindowController = [[NSClassFromString(nibName) alloc] initWithWindowNibName:nibName];
      [self flipToNextWindowControllerDirection:direction];
    }
  }
}

#pragma mark - Flip animation

#define DEF_DURATION  2.0   // Animation duration
#define DEF_SCALE     1.2   // Scaling factor for animation window (_animationWindow)

//============================================================================================================
//      Start window flipping animation
//============================================================================================================
-(void)flipToNextWindowControllerDirection:(ALFlipDirection)direction
{
  NSWindow *currentWindow = [_currentWindowController window];
  NSWindow *nextWindow = [_nextWindowController window];
  NSView *currentWindowView = [currentWindow.contentView superview];
  NSView *nextWindowView = [nextWindow.contentView superview];

  // Create window for animation
  CGFloat maxWidth  = MAX(currentWindow.frame.size.width, nextWindow.frame.size.width);
  CGFloat maxHeight = MAX(currentWindow.frame.size.height, nextWindow.frame.size.height);
  CGFloat xscale = DEF_SCALE * 2.0;
  maxWidth += maxWidth * xscale;
  maxHeight += maxHeight * xscale;

  CGRect animationFrame = CGRectMake(NSMidX(currentWindow.frame) - (maxWidth / 2),
                                     NSMidY(currentWindow.frame) - (maxHeight / 2),
                                     maxWidth, maxHeight);
  _animationWindow =  [[NSWindow alloc] initWithContentRect:NSRectFromCGRect(animationFrame)
                                                  styleMask:NSBorderlessWindowMask
                                                    backing:NSBackingStoreBuffered
                                                      defer:NO];
  [_animationWindow setOpaque:NO];
  [_animationWindow setHasShadow:NO];
  [_animationWindow setBackgroundColor:[NSColor clearColor]];
  [_animationWindow.contentView setWantsLayer:YES];
  [_animationWindow setLevel:NSScreenSaverWindowLevel];

  // Move next window closer to the current one
  CGRect nextFrame = CGRectMake(NSMidX(currentWindow.frame) - (NSWidth(nextWindow.frame) / 2 ),
                                NSMaxY(currentWindow.frame) - NSHeight(nextWindow.frame),
                                NSWidth(nextWindow.frame), NSHeight(nextWindow.frame));
  [nextWindow setFrame:NSRectFromCGRect(nextFrame) display:NO];

  // Make snapshots of current and next windows
  [CATransaction begin];
  CALayer *currentWindowSnapshot = [self snapshotToImageLayerFromView:currentWindowView];
  CALayer *nextWindowSnapshot = [self snapshotToImageLayerFromView:nextWindowView];
  [CATransaction commit];

  currentWindowSnapshot.frame = [self rect:currentWindowView.frame
                                  fromView:currentWindowView
                                    toView:[_animationWindow contentView]];
  nextWindowSnapshot.frame = [self rect:nextWindowView.frame
                               fromView:nextWindowView
                                 toView:[_animationWindow contentView]];

  // Create 3D transform matrix to snapshots
  CATransform3D transform = CATransform3DIdentity;
  transform.m34 = -(1.0 / 1500.0);
  currentWindowSnapshot.transform = transform;
  nextWindowSnapshot.transform = transform;

  // Add snapshots to animation window
  [CATransaction begin];
  [[_animationWindow.contentView layer] addSublayer:currentWindowSnapshot];
  [[_animationWindow.contentView layer] addSublayer:nextWindowSnapshot];
  [CATransaction commit];
  [_animationWindow makeKeyAndOrderFront:nil];

  // Animation for snapshots
  [CATransaction begin];
  CAAnimation *currentSnapshotAnimation = [self animationWithDuration:(DEF_DURATION * 0.5) flip:YES direction:direction];
  CAAnimation *nextSnapshotAnimation = [self animationWithDuration:(DEF_DURATION * 0.5) flip:NO direction:direction];
  [CATransaction commit];

  // Start animation
  nextSnapshotAnimation.delegate = self;
  [currentWindow orderOut:nil];
  [CATransaction begin];
  [currentWindowSnapshot addAnimation:currentSnapshotAnimation forKey:@"flipAnimation"];
  [nextWindowSnapshot addAnimation:nextSnapshotAnimation forKey:@"flipAnimation"];
  [CATransaction commit];
}

//============================================================================================================
//      Convert rectangle from one view coordinates to another
//============================================================================================================
-(CGRect)rect:(NSRect)rect fromView:(NSView *)fromView toView:(NSView *)toView
{
  rect = [fromView convertRect:rect toView:nil];
  rect = [fromView.window convertRectToScreen:rect];
  rect = [toView.window convertRectFromScreen:rect];
  rect = [toView convertRect:rect fromView:nil];
  return NSRectToCGRect(rect);
}

//============================================================================================================
//      Get snapshot of selected view as layer with bitmap image
//============================================================================================================
-(CALayer *)snapshotToImageLayerFromView:(NSView*)view
{
  // Make view snapshot
  NSBitmapImageRep *snapshot = [view bitmapImageRepForCachingDisplayInRect:view.bounds];
  [view cacheDisplayInRect:view.bounds toBitmapImageRep:snapshot];
  // Convert snapshot to layer
  CALayer *layer = [CALayer layer];
  layer.contents = (id)snapshot.CGImage;
  layer.doubleSided = NO;
  // Add shadow of window to snapshot
  [layer setShadowOpacity:0.5];
  [layer setShadowOffset:CGSizeMake(0.0, -10.0)];
  [layer setShadowRadius:15.0];
  return layer;
}

//============================================================================================================
//      Create animation
//============================================================================================================
-(CAAnimation *)animationWithDuration:(CGFloat)time flip:(BOOL)flip direction:(ALFlipDirection)direction
{
  // Set flip direction
  NSString *keyPath = @"transform.rotation.y";
  if (direction == ALFlipDirectionUp || direction == ALFlipDirectionDown) keyPath = @"transform.rotation.x";
  CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:keyPath];
  CGFloat startValue = flip ? 0.0 : -M_PI;
  CGFloat endValue = flip ? M_PI : 0.0;
  if (direction == ALFlipDirectionLeft || direction == ALFlipDirectionUp)
  {
    startValue = flip ? 0.0 : M_PI;
    endValue = flip ? -M_PI : 0.0;
  }

  flipAnimation.fromValue = [NSNumber numberWithDouble:startValue];
  flipAnimation.toValue = [NSNumber numberWithDouble:endValue];

  CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
  scaleAnimation.toValue = [NSNumber numberWithFloat:DEF_SCALE];
  scaleAnimation.duration = time * 0.5;
  scaleAnimation.autoreverses = YES;

  CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
  animationGroup.animations = [NSArray arrayWithObjects:flipAnimation, scaleAnimation, nil];
  animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  animationGroup.duration = time;
  animationGroup.fillMode = kCAFillModeForwards;
  animationGroup.removedOnCompletion = NO;

  return animationGroup;
}

//============================================================================================================
//      Flip animation did finish
//============================================================================================================
-(void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
  [[_nextWindowController window] makeKeyAndOrderFront:nil];
  [_animationWindow orderOut:nil];
  _animationWindow = nil;
  _currentWindowController = _nextWindowController;
}

@end

How to use:

  1. Create some new NSWindowController objects with NIBs in your project
  2. Call [FLIPANIMATOR flipToWindowNibName:@"SecondWindowController" direction:ALFlipDirectionRight]; to flip to second window, or [FLIPANIMATOR flipToWindowNibName:@"FirstWindowController" direction:ALFlipDirectionLeft]; to return back
  3. Link QuarzCore.framework to your project
  4. That's all!
查看更多
登录 后发表回答