Search code examples
objective-ccocoansviewtransformationcgaffinetransform

How to apply a view transformation that works for a UIView to a NSView?


I have a view transform to make any UIView object follow a cocos2d node. Works fine on iOS, in this example the UIButton follows its Box2D body:

enter image description here

Now for Mac I first verified that I have the overlay NSWindow setup correctly in order to draw NSView objects on top of the cocos2d OpenGL view. That works, as in this case with a NSButton:

enter image description here

But then I learned that there is no [NSView setTransform:..] method for NSViews. I'm not sure if this is how it works on Mac, but here's what I did:

// sometime after initializing the view
[nsView setWantsLayer:YES];

// every time I update transform:
nsView.layer.affineTransform = transform;

I verified that layer is non-nil. I also re-verified that the NSView is drawn after I enabled setWantsLayer. The result is disappointing, the button is nowhere to be seen on the entire window, it doesn't even seem to be slightly offset or mirrored, it's just not there anymore:

enter image description here

This brings me to the transform code. As I said, this is the same code as for iOS (except for assigning the transform at the end). It works perfectly on iOS (in any orientation too) but not on Mac OS X with NSView objects, where I don't see the NSView object at all anymore.

I'm not sure what could be wrong here, perhaps the CALayer works differently, perhaps I simply made a really stupid and obvious mistake. I just can't find anything.

KKAppDelegate* ad = (KKAppDelegate*)[KTApplication sharedApplication].delegate;
float windowHeight = ad.window.frame.size.height;

float x = _position.x - _contentSize.width * _anchorPoint.x;
float y = windowHeight - (_position.y + _contentSize.height * _anchorPoint.y);

if (_ignoreAnchorPointForPosition)
{
    x += _anchorPointInPoints.x;
    y += _anchorPointInPoints.y;
}

float cx = 1, sx = 0, cy = 1, sy = 0;
if (_rotationX || _rotationY)
{
    float radiansX = CC_DEGREES_TO_RADIANS(_rotationX);
    float radiansY = CC_DEGREES_TO_RADIANS(_rotationY);
    cx = cosf(radiansX);
    sx = sinf(radiansX);
    cy = cosf(radiansY);
    sy = sinf(radiansY);
}

CGAffineTransform transform = 
    CGAffineTransformMake(cy * _scaleX, sy * _scaleX,
                          -sx * _scaleY, cx * _scaleY, x, y);

#if KK_PLATFORM_IOS
    _cocoaView.transform = transform;
#elif KK_PLATFORM_MAC
    _cocoaView.layer.affineTransform = transform;
#endif

I verified that the code runs and the x/y values are what could be expected (ie no negative position or crazy values). Rotation isn't even performed in the very first step, yet I still don't see the NSView after the first frame's transform & draw.

I'm a total n00b to CALayer and applying transformations to NSView. I'd appreciate if not the solution then some tips on how to debug this. Thanks!

UPDATE:

I started playing around with changing the layer's transform by small values to see what happens.

CGAffineTransform transform = _cocoaView.layer.affineTransform;
transform = CGAffineTransformTranslate(transform, 0.1, 0.1);
_cocoaView.layer.affineTransform = transform;

The effect of that was a slowly moving NSButton. It kept moving up and to the right, but it vanishes at the view frame's bounds rect:

enter image description here

So I believe my problem here is that the view isn't transformed, just the layer - and when I transform the layer it will quickly draw the view's content outside of the bounds of the layer and therefore nothing is displayed anymore.

Am I right to assume that I'll have to change the view's properties directly instead? The documentation also stresses this point. I just don't know at this point how I can apply the transformation to the view (ie I'm back to square one). I tried setting rotation and frame origin manually, but that way I can't align the center of rotation with the cocos2d node's center (anchorPoint).


Solution

  • There is an important difference between UIButton and NSButton. Even if NSButton is at the end a subclass of NSView, the rendering of an NSButton is done by its cell, NSButtonCell. Cells were created as a "lighter" version of NSView. As Apple's documentation states:

    The NSCell class provides a mechanism for displaying text or images in an NSView object without the overhead of a full NSView subclass. It’s used heavily by most of the NSControl classes to implement their internal workings.

    While this was useful in the early Mac OS X days, now it is no longer the case and hopefully Apple will change this in AppKit at some stage.

    What is happening to you is that the cell gets rotated, but then it is clipped by the view, in your case the button itself.

    To solve the issue, make the NSButton's superview layer-backed too, and I believe that the clipping will go away:

    [nsView.superview setWantsLayer:YES];