Поведение "переворота" виджета в Core Animation / Cocoa

Я пытаюсь создать класс Card, который дублирует поведение виджетов Dashboard в том, что вы можете размещать элементы управления, изображения или что-то еще на двух сторонах карты и переключаться между ними.

Представления с поддержкой слоев имеют свойство преобразования, но его изменение не дает того, что я ожидал бы от него (поворот слоя вокруг оси y сдвигает его влево).

Мне указали на некоторые недокументированные функции и файл .h с именем cgsprivate.h, но мне интересно, есть ли официальный способ сделать это? Это программное обеспечение должно быть отправлено, и я бы очень не хотел, чтобы он потерпел неудачу позже, потому что ребята из Apple вытащили его в 10.6.

Кто-нибудь знает, как это сделать? Для меня так странно, что в Core Animation так сложно реализовать простую вещь с виджетами.

Заранее спасибо!

Обновлено: я могу добиться этого с изображениями, которые находятся на слоях, но я не знаю, как получить более продвинутые элементы управления / представления / что-то еще на слоях. В примере карты используются изображения.

Я написал образец для iphone, демонстрирующий один из способов сделать это. github.com/samyzee/CardFlipperSample

user283455 30.08.2011 20:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
1
13 895
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Это излишне для ваших целей (поскольку оно содержит в значительной степени законченное справочное приложение для настольных и карточных игр), но посмотрите этот образец из АЦП. Прилагаемые к нему карточные игры прекрасно справляются с этим эффектом переворачивания.

Карточки в этом образце представляют собой слои, в качестве содержимого которых добавлены изображения. Мне нужно разместить элементы управления (кнопки, текстовые поля, представления) на слоях, и я не могу найти хороший способ сделать это.

user46747 16.12.2008 20:43

Если вы можете сделать это с изображениями, возможно, вы можете сохранить все свои элементы управления в объекте NSView (как обычно), а затем визуализировать NSView в растровое изображение с помощью cacheDisplayInRect:toBitmapImageRep: непосредственно перед выполнением эффекта переворота. Шаги будут такими:

  1. Рендеринг NSView в растровое изображение
  2. Отобразите это растровое изображение на слое, подходящем для эффекта отражения.
  3. Скройте NSView и откройте слой изображения
  4. Выполните эффект переворота

Ага. Это именно то решение, которого я пытался избежать или, по крайней мере, не реализовывать его сам. Я подозреваю, что это именно то, что делают виджеты приборной панели, поскольку вы можете видеть, как они зависают и обновляются. Я просто надеялся, что для этого существует устоявшийся метод. Спасибо за методы NSView!

user46747 16.12.2008 22:10
Ответ принят как подходящий

У Майка Ли есть реализация эффекта переворота, для которого он выпустил немного образец кода. (К сожалению, это больше не доступно онлайн, но Дрю МакКормак построил на этом в своей собственной реализации.) Похоже, что он захватывает слои для представлений «задний план» и «передний план», которые нужно поменять местами, использует CATransform3D для поворота двух представлений в анимации, а затем меняет местами представления после завершения анимации.

Используя слои из представлений, вам не нужно кэшировать в растровое изображение, поскольку слои в любом случае делают это. В любом случае, его контроллер представления выглядит хорошим решением для того, что вы хотите.

Ссылки, которые вы предоставили, не работают. Первый теперь ссылается на туристический сайт, а другой - на 404.

Sam Spencer 25.05.2013 03:18

@RazorSharp - Похоже, что весь веб-сайт Майка отключился, поэтому ближайшей реализацией, которую я смог найти, был дизайн Дрю МакКормака чего-то подобного, которое он основал на исходном коде Майка.

Brad Larson 25.05.2013 21:26

Используя Core Animation, как обрисовал e.James ... Обратите внимание, здесь используется сборка мусора и размещенный слой:

#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

Заголовочный файл:

@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

Обновлено: это будет анимировать переход от одного окна к другому. Вы можете применить к представлению те же принципы.

Это полная реализация с открытым исходным кодом, созданная ребятами из Mizage.

Вы можете проверить это здесь: https://github.com/mizage/Flip-Animation

Я знаю, что это поздно, но у Apple есть пример проекта, который может помочь всем, кто все еще сталкивается с этим вопросом.

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

Вероятно, не тот случай, когда в 2008 году был задан этот вопрос, но в наши дни это довольно просто:

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

Примечание. По всей видимости, это работает только на iOS.

Вопрос касается MacOS, а не iOS

Borzh 03.07.2015 17:10

@Borzh Действительно, немного смешно, поскольку тот, кто хочет сделать это на iOS, с такой же вероятностью найдет этот вопрос, как и тот, кто хочет сделать это на OSX, независимо от того, что OP использовал в 2008 году.

aepryus 04.07.2015 04:18

Другие вопросы по теме