Я пытаюсь создать класс Card, который дублирует поведение виджетов Dashboard в том, что вы можете размещать элементы управления, изображения или что-то еще на двух сторонах карты и переключаться между ними.
Представления с поддержкой слоев имеют свойство преобразования, но его изменение не дает того, что я ожидал бы от него (поворот слоя вокруг оси y сдвигает его влево).
Мне указали на некоторые недокументированные функции и файл .h с именем cgsprivate.h, но мне интересно, есть ли официальный способ сделать это? Это программное обеспечение должно быть отправлено, и я бы очень не хотел, чтобы он потерпел неудачу позже, потому что ребята из Apple вытащили его в 10.6.
Кто-нибудь знает, как это сделать? Для меня так странно, что в Core Animation так сложно реализовать простую вещь с виджетами.
Заранее спасибо!
Обновлено: я могу добиться этого с изображениями, которые находятся на слоях, но я не знаю, как получить более продвинутые элементы управления / представления / что-то еще на слоях. В примере карты используются изображения.





Это излишне для ваших целей (поскольку оно содержит в значительной степени законченное справочное приложение для настольных и карточных игр), но посмотрите этот образец из АЦП. Прилагаемые к нему карточные игры прекрасно справляются с этим эффектом переворачивания.
Карточки в этом образце представляют собой слои, в качестве содержимого которых добавлены изображения. Мне нужно разместить элементы управления (кнопки, текстовые поля, представления) на слоях, и я не могу найти хороший способ сделать это.
Если вы можете сделать это с изображениями, возможно, вы можете сохранить все свои элементы управления в объекте NSView (как обычно), а затем визуализировать NSView в растровое изображение с помощью cacheDisplayInRect:toBitmapImageRep: непосредственно перед выполнением эффекта переворота. Шаги будут такими:
NSView в растровое изображение
NSView и откройте слой изображения
Ага. Это именно то решение, которого я пытался избежать или, по крайней мере, не реализовывать его сам. Я подозреваю, что это именно то, что делают виджеты приборной панели, поскольку вы можете видеть, как они зависают и обновляются. Я просто надеялся, что для этого существует устоявшийся метод. Спасибо за методы NSView!
У Майка Ли есть реализация эффекта переворота, для которого он выпустил немного образец кода. (К сожалению, это больше не доступно онлайн, но Дрю МакКормак построил на этом в своей собственной реализации.) Похоже, что он захватывает слои для представлений «задний план» и «передний план», которые нужно поменять местами, использует CATransform3D для поворота двух представлений в анимации, а затем меняет местами представления после завершения анимации.
Используя слои из представлений, вам не нужно кэшировать в растровое изображение, поскольку слои в любом случае делают это. В любом случае, его контроллер представления выглядит хорошим решением для того, что вы хотите.
Ссылки, которые вы предоставили, не работают. Первый теперь ссылается на туристический сайт, а другой - на 404.
@RazorSharp - Похоже, что весь веб-сайт Майка отключился, поэтому ближайшей реализацией, которую я смог найти, был дизайн Дрю МакКормака чего-то подобного, которое он основал на исходном коде Майка.
Используя 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 есть пример проекта, который может помочь всем, кто все еще сталкивается с этим вопросом.
Вероятно, не тот случай, когда в 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 Действительно, немного смешно, поскольку тот, кто хочет сделать это на iOS, с такой же вероятностью найдет этот вопрос, как и тот, кто хочет сделать это на OSX, независимо от того, что OP использовал в 2008 году.
Я написал образец для iphone, демонстрирующий один из способов сделать это. github.com/samyzee/CardFlipperSample