Core Animation

Пример Core Animation,

разворот Окон вокруг оси.

В данном примере с помощью технологии Core Animation будет произведен разворот окна вокруг оси Y и замена его другим окном, создавая эффект поворота двухстороннего окна.

Видео примера, с реализацией смены Окна другим окном.

Реализация на Swift

Реализация на Objective-C

Файл mbFlipWindow.h

#import <Foundation/Foundation.h>
@interface mbFlipWindow : NSObject
{
  BOOL flipRight;
  double duration;
  NSWindow *mAnimationWindow;// окна, создаваемые для анимации
  NSWindow *mTargetWindow;
}
// разворот activeWindow окна к targetWindow
- (void) flip:(NSWindow *)activeWindow to:(NSWindow *)targetWindow;

@property BOOL flipRight; // YES -поворот вправо
@property double duration; // время анимации, по умолчанию 2.0

@end

Файл mbFlipWindow.m

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

@interface mbFlipWindow () //скрытые методы
- (NSWindow *) windowForAnimation:(NSRect)aFrame;
- (CALayer *) layerFromView :(NSView*)view;
NSRect RectToScreen(NSRect aRect, NSView *aView);
NSRect RectFromScreen(NSRect aRect, NSView *aView);
NSRect RectFromViewToView(NSRect aRect, NSView *fromView, NSView *toView);
- (CAAnimation *) animationWithDuration:(CGFloat)time flip:(BOOL)bFlip right:(BOOL)rightFlip;
@end

@implementation mbFlipWindow

@synthesize flipRight;
@synthesize duration;

- (id)init
{
  self = [super init];
  if (self) {
    duration = 2.0;
    flipRight = YES;
    _scaleAnimation = 1.2;
  }
  return self;
}

// метод создает окно в котором будет производиться анимация
// с изображением окна
- (NSWindow *) windowForAnimation:(NSRect)aFrame {

NSWindow *wnd =  [[NSWindow alloc] initWithContentRect:aFrame 
  styleMask:NSBorderlessWindowMask 
  backing:NSBackingStoreBuffered 
  defer:NO];
  [wnd setOpaque:NO];
  [wnd setHasShadow:NO];
  [wnd setBackgroundColor:[NSColor clearColor]];
  [wnd.contentView setWantsLayer:YES];
  [wnd setLevel:NSScreenSaverWindowLevel];// уровень окна Screen Saver
  return wnd;
}

// создает слой для анимации с изображением вида
- (CALayer *) layerFromView :(NSView*)view {
  
  NSBitmapImageRep *image = [view bitmapImageRepForCachingDisplayInRect:view.bounds];
  [view cacheDisplayInRect:view.bounds toBitmapImageRep:image];
  
  CALayer *layer = [CALayer layer];
  layer.contents = (id)image.CGImage;
  layer.doubleSided = NO;

  // Тень окна, используемого в Mac OS X 10.6
  [layer setShadowOpacity:0.5f];
  [layer setShadowOffset:CGSizeMake(0,-10)];
  [layer setShadowRadius:15.0f];
  
  
  return layer;
}

// Следующие три функции преобразования координат

NSRect RectToScreen(NSRect aRect, NSView *aView) {
  aRect = [aView convertRect:aRect toView:nil];
  aRect = [aView.window convertRectToScreen:aRect];
  return aRect;
}

NSRect RectFromScreen(NSRect aRect, NSView *aView) {
  aRect = [aView.window convertRectFromScreen:aRect];
  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;
}

// Создание Core Animation отдаляющей и разворачивающей окно
- (CAAnimation *) animationWithDuration:(CGFloat)time flip:(BOOL)bFlip right:(BOOL)rightFlip{
  
  CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
  
  CGFloat startValue, endValue;
  
  if ( rightFlip ) {
    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 *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
  scaleAnimation.toValue = [NSNumber numberWithFloat:_scaleAnimation];
  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;
}

// метод, вызываемый после окончания анимации

- (void) animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
  
  if (flag) {
    [mTargetWindow makeKeyAndOrderFront:nil];
    [mAnimationWindow orderOut:nil];
    
    mTargetWindow = nil; // освобождаем память
    mAnimationWindow = nil;
  }
}

//------------------------

// Собственно сама функция, разворачивающая окно

- (void) flip:(NSWindow *)activeWindow to:(NSWindow *)targetWindow {
  
  CGFloat durat = duration * (activeWindow.currentEvent.modifierFlags & NSShiftKeyMask ? 10.0 : 1.0);
  CGFloat zDistance = 1500.0f;
  
  NSView *activeView = [activeWindow.contentView superview];
  NSView *targetView = [targetWindow.contentView superview];
  
  // Создаем окно для анимации
  CGFloat maxWidth  = MAX(NSWidth(activeWindow.frame), NSWidth(targetWindow.frame));
  CGFloat maxHeight = MAX(NSHeight(activeWindow.frame), NSHeight(targetWindow.frame));
  if(_scaleAnimation > 0.0){
    double xscale = _scaleAnimation * 2.0;
    maxWidth += maxWidth * xscale;
    maxHeight += maxHeight * xscale;
  }
  
  CGRect animationFrame = CGRectMake(NSMidX(activeWindow.frame) - (maxWidth / 2),
                                     NSMidY(activeWindow.frame) - (maxHeight / 2), 
                                     maxWidth, 
                                     maxHeight);
  
  mAnimationWindow = [self windowForAnimation:NSRectFromCGRect(animationFrame)];
  
  // Добавляем перспективу
  CATransform3D transform = CATransform3DIdentity; 
  transform.m34 = -1.0 / zDistance;
  
  // Перемещение target window к 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 = [self layerFromView: activeView];
  CALayer *targetWindowLayer = [self layerFromView:targetView];
  [CATransaction commit];
  
  [mAnimationWindow makeKeyAndOrderFront:nil];
  
  activeWindowLayer.frame = NSRectToCGRect(RectFromViewToView(activeView.frame, activeView,
                     [mAnimationWindow contentView]));
  targetWindowLayer.frame = NSRectToCGRect(RectFromViewToView(targetView.frame, targetView,
                    [mAnimationWindow contentView]));
  
  activeWindowLayer.transform = transform;
  targetWindowLayer.transform = transform;
  
  
  [CATransaction begin];
  [[mAnimationWindow.contentView layer] addSublayer:activeWindowLayer];
  [CATransaction commit];
  
  
  
  [CATransaction begin];
  [[mAnimationWindow.contentView layer] addSublayer:targetWindowLayer];
  [CATransaction commit];
  
  // Animate our new layers
  [CATransaction begin];
  CAAnimation *activeAnim = [self animationWithDuration:(durat * 0.5) flip:YES right:flipRight];
  CAAnimation *targetAnim = [self animationWithDuration:(durat * 0.5) flip:NO  right:flipRight];
  [CATransaction commit];
  
  targetAnim.delegate = self;
  [activeWindow orderOut:nil];
  
  [CATransaction begin];
  [activeWindowLayer addAnimation:activeAnim forKey:@"flipWnd"];
  [targetWindowLayer addAnimation:targetAnim forKey:@"flipWnd"];
  [CATransaction commit];
}
@end

Выше приведенный код имеет один открытый метод - (void) flip:(NSWindow *)activeWindow to:(NSWindow *)targetWindow, которому передаются в качестве аргументов активное окно и созданное, но скрытое окно, которое используется в качестве тыльного. Также данный пример использует включенную опцию Use Automatic Reference Counting, т.е. компилятор автоматически создает код подсчета ссылок

Свойство flipRight устанавливается в YES для поворота вправо и в NO для поворота влево.

Помним, что для использования Core Animation необходимо подключить к проекту библиотеку QuartzCore.framework, иначе линкер выдаст ошибку.

Для использования данного кода просто вставьте в проект два вышеприведенных файла, создайте экземпляр mbFlipWindow и вызывайте его метод flip: to: с аргументами окон для смены, установив перед этим свойство flipRight для указания направления анимации. Ниже приведен пример использования:

// Допустим есть два экземпляра NSWindow, которые мы будем сменять:
// window1 и window2, а также создан экземпляр mbFlipWindow* flipWnd;

flipWnd.flipRight = YES; // поворот вправо
[flipWnd flip:window1 to:window2];

 

Реализация на Swift

import Cocoa
import QuartzCore

class mbFlipWindow: NSObject {
  
  var duration : Double = 2.0// время анимации
  var _scaleAnimation : Double = 1.2// масштабирование при анимации
  var flipRight : Bool = true
  
  // внутренние переменные
  var mAnimationWindow : NSWindow?
  var mTargetWindow : NSWindow?
  
  func windowForAnimation(aFrame : NSRect) -> NSWindow{
    var wnd : NSWindow = NSWindow(contentRect : aFrame, styleMask : NSBorderlessWindowMask,
          backing : NSBackingStoreType.Buffered, defer : false);
    wnd.opaque = false
    wnd.hasShadow = false
    wnd.backgroundColor = NSColor.clearColor()
    (wnd.contentView as NSView).wantsLayer = true
    wnd.level = 9999
    return wnd
  }
  
  // создает слой для анимации с изображением вида
  func layerFromView(view : NSView) -> CALayer{
    var image = view.bitmapImageRepForCachingDisplayInRect(view.bounds)
    view.cacheDisplayInRect(view.bounds, toBitmapImageRep:image)
    var layer : CALayer = CALayer();
    layer.contents = image.CGImage;
    layer.doubleSided = false;
    // Тень окна, используемого в Mac OS X 10.6
    layer.shadowOpacity = 0.5
    layer.shadowOffset = CGSizeMake(0,-10)
    layer.shadowRadius = 15.0
    
    return layer
  }
  
  // Следующие три функции преобразования координат
  
  func RectToScreen(var aRect:NSRect,aView:NSView) -> NSRect {
    aRect = aView.convertRect(aRect, toView:nil)
    aRect.origin = aView.window.convertBaseToScreen(aRect.origin)
    return aRect;
  }
  
  func RectFromScreen(var aRect:NSRect,aView:NSView) -> NSRect {
    aRect.origin = aView.window.convertScreenToBase(aRect.origin)
    aRect = aView.convertRect(aRect, fromView:nil)
    return aRect;
  }
  
  func RectFromViewToView(var aRect: NSRect, fromView : NSView, toView : NSView) -> NSRect{
    aRect = RectToScreen(aRect, aView: fromView)
    aRect = RectFromScreen(aRect, aView: toView)
  
    return aRect;
  }
  
  // Создание Core Animation отдаляющей и разворачивающей окно
  func animationWithDuration(time:CGFloat, flip bFlip:Bool, right rightFlip:Bool) -> CAAnimation{
  
    var flipAnimation : CABasicAnimation = CABasicAnimation(keyPath :"transform.rotation.y")
  
    var startValue: Double = 0.0; var endValue: Double = 0.0;
    
    if rightFlip {
    startValue = bFlip ? 0.0 : -M_PI
    endValue = bFlip ? M_PI : 0.0
    } else {
    startValue = bFlip ? 0.0 : M_PI
    endValue = bFlip ? -M_PI : 0.0
    }
    
    flipAnimation.fromValue = NSNumber.numberWithDouble(startValue)
    flipAnimation.toValue = NSNumber.numberWithDouble(endValue)
    
    var scaleAnimation : CABasicAnimation  = CABasicAnimation(keyPath:"transform.scale")
    scaleAnimation.toValue = NSNumber.numberWithDouble(_scaleAnimation)
    scaleAnimation.duration = time * 0.5;
    scaleAnimation.autoreverses = true;
    
    var animationGroup : CAAnimationGroup = CAAnimationGroup()
    animationGroup.animations = [flipAnimation, scaleAnimation]
    animationGroup.timingFunction = CAMediaTimingFunction(name : kCAMediaTimingFunctionEaseInEaseOut)
    animationGroup.duration = time;
    animationGroup.fillMode = kCAFillModeForwards;
    animationGroup.removedOnCompletion = false;
    
    return animationGroup;
  }
  
  // метод, вызываемый после окончания анимации
  
   override func animationDidStop(animation : CAAnimation, finished flag : Bool) {
  
    if flag {
    (mTargetWindow as NSWindow).makeKeyAndOrderFront(nil)
    (mAnimationWindow as NSWindow).orderOut(nil)
    
    mTargetWindow = nil; // освобождаем память
    mAnimationWindow = nil;
    }
  }
  
  //------------------------
  
  // Собственно сама функция, разворачивающая окно
  
  func flip(activeWindow : NSWindow, to targetWindow : NSWindow)  {
  
    var durat : CGFloat = duration
    var zDistance : CGFloat = 1500.0
    
    var activeView : NSView = (activeWindow.contentView as NSView).superview
    var targetView : NSView  = (targetWindow.contentView as NSView).superview
    
    // Создаем окно для анимации
    var maxWidth  = max(NSWidth(activeWindow.frame), NSWidth(targetWindow.frame))
    var maxHeight = max(NSHeight(activeWindow.frame), NSHeight(targetWindow.frame))
   
    let xscale = _scaleAnimation * 2.0;
    maxWidth += maxWidth * xscale;
    maxHeight += maxHeight * xscale;
    
    
    var animationFrame = CGRectMake(NSMidX(activeWindow.frame) - (maxWidth / 2),
    NSMidY(activeWindow.frame) - (maxHeight / 2),
    maxWidth,
    maxHeight);
    
    mAnimationWindow = windowForAnimation(NSRectFromCGRect(animationFrame))
    
    // Добавляем перспективу
    var transform = CATransform3DIdentity
    transform.m34 = -1.0 / zDistance;
    
    // Перемещение target window к active window
    var 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:false)
    
    mTargetWindow = targetWindow;
    
    // New Active/Target Layers
    CATransaction.begin()
    var activeWindowLayer = layerFromView(activeView)
    var targetWindowLayer = layerFromView(targetView)
    CATransaction.commit()
    
    (mAnimationWindow as NSWindow).makeKeyAndOrderFront(self)
    
    activeWindowLayer.frame = NSRectToCGRect(RectFromViewToView(activeView.frame, fromView: activeView,
               toView: (mAnimationWindow as NSWindow).contentView as NSView));
    targetWindowLayer.frame = NSRectToCGRect(RectFromViewToView(targetView.frame, fromView: targetView,
               toView: (mAnimationWindow as NSWindow).contentView as NSView));
    
    activeWindowLayer.transform = transform;
    targetWindowLayer.transform = transform;
    
    
    CATransaction.begin()
    var layerMW : CALayer = ((mAnimationWindow as NSWindow).contentView as NSView).layer as CALayer
    layerMW.addSublayer(activeWindowLayer)
    CATransaction.commit()
    
    
    
    CATransaction.begin()
    ((mAnimationWindow as NSWindow).contentView.layer as CALayer).addSublayer(targetWindowLayer)
    CATransaction.commit()
    
    // Animate our new layers
    CATransaction.begin()
    var activeAnim = self.animationWithDuration((durat * 0.5), flip:true, right:flipRight)
    var targetAnim = self.animationWithDuration((durat * 0.5), flip:false,  right:flipRight)
    CATransaction.commit()
    
    targetAnim.delegate = self;
    activeWindow.orderOut(nil)
    
    CATransaction.begin()
    activeWindowLayer.addAnimation(activeAnim, forKey:"flipWnd")
    targetWindowLayer.addAnimation(targetAnim, forKey:"flipWnd")
    CATransaction.commit()
  }

}

Пример реализации и вызова в Swift проекте:

import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate {
                            
  @IBOutlet var window: NSWindow
  @IBOutlet var window2 : NSWindow

  var fliper : mbFlipWindow = mbFlipWindow() // Создание объекта при инициализации

  func applicationDidFinishLaunching(aNotification: NSNotification?) {
    // Insert code here to initialize your application
    
  }

  func applicationWillTerminate(aNotification: NSNotification?) {
    // Insert code here to tear down your application
  }
  
  // Обработчики событий элементов пользовательского интерфейса, разворачивающие окна
  
  @IBAction func flipRight(sender : AnyObject) {
    fliper.flipRight = true
    fliper.flip(window, to: window2)
  }
  @IBAction func flipLeft(sender : AnyObject) {
    fliper.flipRight = false
    fliper.flip(window2, to: window)
  }
}

См. также:

Core Animation, разворот пользовательских видов
 
 
homeЗаметили ошибкукарта сайта  EN
   Made on a Mac