NSAnimation removes button background colour - macos

I am working on a Mac app. I am trying to do a simple animation which makes an NSButton move down. The animation works really nicely, but when i do it, the background colour of my NSButton disappears for some reason. Here is my code:
// Tell the view to create a backing layer.
additionButton.wantsLayer = YES;
// Set the layer redraw policy. This would be better done in
// the initialization method of a NSView subclass instead of here.
additionButton.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
context.duration = 1.0f;
additionButton.animator.frame = CGRectOffset(additionButton.frame, 0.0, -20.0);
//additionButton.frame = CGRectOffset(additionButton.frame, 0.0, -20.0);
} completionHandler:nil];
Button move down animation:
Button after move down animation:
Update 1
Just to make it clear, I am not using a background image in my buttons. I am using a background NSColor which I set in the viewDidLoad method like so:
[[additionButton cell] setBackgroundColor:[NSColor colorWithRed:(100/255.0) green:(43/255.0) blue:(22/255.0) alpha:1.0]];

I presume this is an AppKit bug. There are a couple of ways you can work around it.
Workaround 1:
Don't use layers. The button you're animating seems to be small, and you might be able to get away with using a non layer-backed animation and still have it look decent. The button will redraw during each step of the animation, but it will animate correctly. That means this is really all you have to do:
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
additionButton.animator.frame = CGRectOffset(additionButton.frame, 0, -20);
} completionHandler:nil];
Workaround 2:
Set the background color on the layer.
additionButton.wantsLayer = YES;
additionButton.layer.backgroundColor = NSColor.redColor.CGColor;
additionButton.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
additionButton.animator.frame = CGRectOffset(additionButton.frame, 0, -20);
} completionHandler:nil];
Workaround 3:
Subclass NSButtonCell, and implement -drawBezelWithFrame:inView:, drawing your background color there. Keep in mind that the parent view containing the button should be layer-backed, otherwise the button will still redraw on every step.

Related

NSView won't draw through bezierpath

Dear fellow Cocoa programmers,
What I'd like to accomplish:
I have a checkbox, a popUpButton(which is hidden) and a NSView on my canvas.
If myCheckbox is checked -> show the popUpButton and draw a line through bezierPath on the NSView.
if myCheckbox is UNchecked -> Hide the popUpButton again and "undraw" the path
The code:
- (IBAction)isChecked:(id)sender {
//if myChekcbox is checked, show the pop up button
if ([sender state]==NSOnState) {
NSLog(#"Checked");
[myPopUp setHidden:NO];
}
else
{
//if the checkbox is unchecked, hide the popupbutton
[myPopUp setHidden:YES];
NSLog(#"Unchecked");
}
//reload my drawrect method (reload the view)
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect
{
//if the checkedbutton is checked, draw the line
if ([myCheckbox state]==NSOnState)
{
NSBezierPath *myPath = [NSBezierPath bezierPath];
[myPath moveToPoint:NSMakePoint(10, 20)];
[myPath lineToPoint:NSMakePoint(50, 20)];
[myPath setLineWidth:2];
[myPath stroke];
}
}
The problem:
if checked state = NSOnState the popUpButton is visible but the line just won't draw and I wonder why... I personally think it's a connection(s) problem.
I uploaded the project file (it's rather small-35kb) here:Drawing.zip
Globally:
I've read the NSView documentation and it's saying there is only one way to draw to a view and it's through the drawRect method. Is this actually true? Also is this a descent way to draw to a view? (if function in the view and setNeedsDisplay:YES in the method)
thanks in advance,
Ben
You will need to get an NSColor instance and then call setStroke on it to set a current stroke color. It does not know which color to use to stroke the path at the start of drawRect:, so you have to tell it.

How to force redraw of a CALayer during an animation that changes its bounds

I have a layer-hosting NSView. Within that I have a CALayer which includes a drawInContext method. The needsDisplayOnBoundsChange parameter is set to true, and and if I resize the view then the layer is indeed resized and redrawn during correctly during the animation.
However, I'd like to animate the size of the layer independently of the view. I can set up an animation to do this, however the layer isn't redrawn during the animation. Rather, is seems that a snap shot is taken for the start and end frames and one is faded out as the other is faded in. Worse, both images are distorted into the resizing-frame as the animation progresses.
How can I force the CALayer to redraw itself during the resize animation?
Thanks,
Tim
This is probably too old but the solution shouldn't be too hard. I don't know if there is a better way but you can just use a CADisplayLink which sets your layer to need redrawing.
- (void)updateContinuously
{
if(!self.displayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(updateLayer)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
- (void)stopUpdatingContinuously
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)update
{
// This will fire every time when the display is redrawn
[self setNeedsDisplay];
}
- (void)updateContinuouslyForTime:(NSTimeInterval)seconds
{
// Create an NSTimer that will remove the displayLink when the time is over
NSTimer *stopTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:#selector(stopUpdatingContinuously) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:stopTimer forMode:NSRunLoopCommonModes];
}

Cocos2d how to scale a sprite without scaling layer? Or, How to scale and crop sprite/layer?

iPad App setup: SceneA contains layerA - 1024x768. Push a button in layerA, layerB drops down over top using a CCMoveTo action. LayerB is only 800x600 so you can see layerA behind it (think of an overlayed pause screen type effect). LayerB contains an 800x600 sprite that the user can zoom in on by pressing a button. The zoom effect is simply a combination of CCScaleTo and CCMoveTo to keep it centered on the part it's zooming in on. However, when the sprite scales, so does layerB overtop of layerA. Is there a way to scale the sprite within a contained window?
LayerB should use the GL_SCISSOR_TEST to trim the outside of itself. You can easily google for more information about it, it basically defines a rect and then uses glScissoron it to remove the outside. I have a class I extend when I need to do this, that goes as follows:
//
// CCNodeClip.h
//
// Created by Ignacio Orlandoni on 7/29/11.
//
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface CCNodeClip : CCLayer {
}
-(void)preVisit;
-(void)postVisit;
#end
-
//
// CCNodeClip.m
//
// Created by Ignacio Orlandoni on 7/29/11.
//
#import "CCNodeClip.h"
#implementation CCNodeClip
-(void)visit {
[self preVisit];
[super visit];
[self postVisit];
}
-(void)preVisit {
if (!self.visible)
return;
glEnable(GL_SCISSOR_TEST);
CGPoint position = [self position];
//I don't remember if this rect really serves for both orientations, so you may need to change the order of the values here.
CGRect scissorRect = CGRectMake(position.x, position.y, [self contentSize].width, [self contentSize].height);
// CCLOG(#"Scrissor Rect: X: %02f, Y:%02f, W: %02f, H: %02f", scissorRect.origin.x, scissorRect.origin.y, scissorRect.size.width, scissorRect.size.height);
// Handle Retina
scissorRect = CC_RECT_POINTS_TO_PIXELS(scissorRect);
glScissor((GLint) scissorRect.origin.x, (GLint) scissorRect.origin.y,
(GLint) scissorRect.size.width, (GLint) scissorRect.size.height);
}
-(void)postVisit {
glDisable(GL_SCISSOR_TEST);
}
#end
With that imported into LayerB, you can now define it as a CCNodeClip instead of CCLayer.
Some links...
glScissor << cocos2d Forum
Circle shape clipping with opengl-es in cocos2d << StackOverflow
Cocos2d iPhone - Sprite cliping/mask/frame << StackOverflow
Another Cocos2D gem: ClippingNode << Learn-Cocos2d.com
As a side note...
CCScaleTo + CCMoveTo can be avoided if the anchor point for the sprite is centered, so the image stays centered in the container as it scales. (.anchorPoint = ccp(0.5, 0.5);)

Lion compound animations for full screen

I'm transitioning a window to full screen mode (the new Lion kind of full screen mode). While I do the transition, I'd like to also slide one of the views in my NSWindow to a new position.
So, in my NSWindowDelegate, I've tried returning the window and implementing the custom animation:
- (NSArray *)customWindowsToEnterFullScreenForWindow:(NSWindow *)window
{
return [NSArray arrayWithObject: window];
}
- (void)window:(NSWindow *)_window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration
{
// book is NSView *ivar
[[book animator] setFrame: NSMakeRect(/*computed rect*/)];
}
But this completely kills the default animation of going to full-screen mode and my window suddenly doesn't paint correctly.
Is there some way to compound these while still using the default animation? I'm pretty new to core animation beyond [view animator] level stuff, So I'm sure I'm screwing up something quite simple.
You have to write something like this in order to have the two animations in sync:
- (void)window:(NSWindow *)_window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration
{
// book is NSView *ivar
[[NSAnimationContext currentContext] setDuration:duration];
[[book animator] setFrame: NSMakeRect(/*computed rect*/)];
}

NSButton with variable size width, rounded corners

What is the best way to create an NSButton with a custom background image, that is able to have variable width, without making the corner bezel look stretched? I know there are convenience methods to do this with UIButton: http://jainmarket.blogspot.com/2009/04/create-uibuttonbutton-with-images.html but I haven't seen anything similar in NSButton.
I needed to have a custom button background, here's how I did it. I made an NSButton subclass and overrode drawrect method:
- (void)drawRect:(NSRect)dirtyRect
{
// My buttons don't have a variable height, so I make sure that the height is fixed
if (self.frame.size.height != 22) {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width,
22.0f);
}
//
switch (self.state) {
// Onstate graphics
case NSOnState:
NSDrawThreePartImage(self.bounds,
[NSImage imageNamed:#"btnmain_lb_h.png"], [NSImage imageNamed:#"btnmain_bg_h.png"], [NSImage imageNamed:#"btnmain_rb_h.png"],
NO, NSCompositeSourceAtop, 1.0, NO);
// Offstate graphics
default:
case NSOffState:
NSDrawThreePartImage(self.bounds,
[NSImage imageNamed:#"btnmain_lb.png"], [NSImage imageNamed:#"btnmain_bg.png"], [NSImage imageNamed:#"btnmain_rb.png"],
NO, NSCompositeSourceAtop, 1.0, NO);
break;
}
[super drawRect:dirtyRect];
}
Then I could put the buttons using Interface Builder, and to get the custom graphics I just have to change the class to my new subclass.
this worked perfectly fine for me:
[self.addBuddyCommitButton.cell setBezelStyle:NSRoundedBezelStyle];
NSButton doesn't have the same convenience methods for background images as UIButton (which is odd and here's to hoping Apple bridges that gap). You'll need to create a custom button my subclassing NSView and handling the variable width and corners yourself. I don't think it will be easy, but I don't think it would be terribly difficult either.

Resources