NSBezierPath point animation in NSView - cocoa

I've built a custom control in Cocoa which is drawn using an NSBezierPath and I'd like it to change shape when it's state has changed (unused state = a pointed 'look here' edge, used state = standard control edge).
It feels like I've looked through every mention of "NSBezierPath" and "Animation" there is on the web but with no luck.
Before I crack out some NSTimers and write my own timing & path point controls, does anyone know if this is possible using Core Animation or similar?

You could use NSAnimation. Create your own NSAnimation subclass, and override setCurrentProgress method.
In that method, you can change your NSBezierPath size, shape or whatever you need, based on current animation progress. After that you can force a redraw in the view (via delegate, for instance) to re-display the path.

Related

can drawRect be triggered on a SCNView (macOS)?

I have two macOS apps that are very similar. One app renders an animation in 2-D, with Quartz calls, in a subclass of NSView, the other app, a 3-D animation in a subclass of SCNView (itself a subclass of NSView) using SceneKit geometries. In each case the "view" is owned by a view controller and that ownership is set in a storyboard. In each case I use a timer to dirty the view every second so its drawRect gets triggered to drive the animated movements. In each case I have used: self.view.needsDisplay = true
In the 2-D case, drawRect is called in the view instance, in the 3-D case it is not (even for the initial render).
I'm puzzled! Does SCNView suppress calls to drawRect? If so, how might I get around this? If not, what voodoo secret have I missed?
If this behavior is not what readers would expect, I will post a sample project which exhibits it.
I know that SceneKit can take advantage of Core Animation but I want to keep the same general timer mechanism in both apps because the animated content is, essentially, the same action, what was flat in 2-D is spherical in 3-D so using SceneKit rendering made sense.
Added an Xcode project to show different NSView and SCNView behaviors:
https://www.dropbox.com/s/qtymzitkqcqhfje/SCN.zip?dl=0
You're fighting the framework.
SceneKit has its own timers for rendering and animation. Hook into those to update your objects' properties (locations, colors, etc). Let SceneKit handle the draw calls.
The methods you need may be in unexpected places. Take a look at documentation for SCNSceneRenderer and SCNSceneRendererDelegate protocols. The renderer delegate documentation explains the render loop and shows you where to customize your app's animations and physics.

Click on NSBezierPath

I'm drawing a NSBezierPath line on my NSImageView. I'm creating NSBezierPath object, setting moveToPoint, setting lineToPoint, setting setLineWidth: and after that in drawRect of my NSImageView subclass I'm calling [myNSBezierPath stroke]. It all works just like I want, but I can't seem to use containsPoint: method... I tried implementing
if([myNSBezierPath containsPoint:[theEvent locationInWindow]]{
//do something
}
in -(void)mouseUp:(NSEvent*)theEvent of my NSImageView subclass but it's never reacting and I'm sure I'm hitting that line... Am I doing something wrong? I just need to detect if NSBezierPath is being clicked.
Cheers.
Make sure to transform the mouse click location into the coordinate system of your image view subclass, not into the one of the window (unless they are the same). Your bezier path doesn't know about the offset it's drawn into, so you have to take that into account when performing a hit test.
Also, from the containsPoint: documentation:
This method checks the point against the path itself and the area it
encloses. When determining hits in the enclosed area, this method uses
the non-zero winding rule (NSNonZeroWindingRule). It does not take
into account the line width used to stroke the path.
Emphasis mine.

Changing selection state of IKImageBrowserView

Hey guys, I've just migrated my image selector from NSCollectionView to IKImageBrowserView. I've got almost everything set up the way I want it, except for the selection ring. I don't like the greyed out background that IKImageBrowserView defaults to, and I wanted to do a yellow stroke around the edge of my selected image to indicate it's selection (like in iPhoto). Is is possible to override the draw state of IKImageBrowserCell? I haven't been able to find any way to do it yet. It doesn't have the simple drawRect methods that I'm used to. Any help would be appreciated. I'm assuming I have to use CALayers?
I overrode - (CALayer *)layerForType:(NSString *)type and tried just as a test, setting the layer corner radius to 0, but it didn't seem to change anything. The method is being called because if I throw a breakpoint in it, it stops there. However, even if I return nil from that method, it still draws the images like usual.
Thanks!
That is the right method for customizing the IKImageBrowserCell.
Using CALayers and configuring different attributes, you can control many facets of how the images are presented.,
A layer of type = IKImageBrowserCellSelectionLayer is what you will want to change to have the display behave and present as you wish.
Here's a link to Apple's sample code project that will get you started

Animating setHidden: on NSView via Cocoa bindings

I'm currently putting the final touches on a project.
A lot (if not all) of the UI logic currently relies on Cocoa Bindings.
Some of the user interface elements (labels, buttons, etc.) have their "Hidden" bindings defined. When certain events are triggered, these elements visibility is toggled.
I'm trying to animate the visibility change (by animating the opacity and maybe even the scale). This could easily be accomplished in a number of ways, either by setting the relevant layer properties, adding the animations to the layer, etc. However, since I'm trying to totally rely on the bindings behavior I "can't" really do this directly.
I tried an implementation using Layer actions, by defining actions for the keys kCAOnOrderIn and kCAOnOrderOut on the relevant elements, but it really didn't work, as the setHidden: is most likely being triggered on the NSView instead of the CALayer -- which makes sense.
So, my question is: how would you animate setHidden: on a NSView, when setHidden: is being invoked by the Cocoa Bindings.
Thank you.
This will fade out an NSView...
[[someView animator] setAlphaValue:0.0f];
Animating setHidden will have no visual effect since it's either on or off. If you want to animate visibility, use setAlpha (or setOpacity on the layer) instead. These take a value between 0.0 and 1.0. If you need the hidden flag to get set for the sake of state information, call -performSelector:withObject:afterDelay passing it a selector that sets the hidden value to whatever you need it to be after the animation has completed. Alternatively you can set up a delegate for explicit animation to be called back when the animation finishes and call setHidden then.
I would suggest taking a look at NSViewAnimation. It takes any NSView and can animate the frame, size or visibility.

Core animation code structure/conventions

In learning Core Animation, I learned very quickly that if you don't do it right, you get really weird undefined behavior. To that end, I have a few questions that will help me conceptually understand it better.
My NSView subclass declares the following in it's init. This view is a subview of normal layer backed view.
[self setLayer:[CALayer layer]];
[self setWantsLayer:NO];
After this, when and in what situations should I refer to self as opposed to [self layer]? I have been ONLY manipulating the layer with explicit and implicit animations, staying away from [self setFrame:] etc. and using [[self layer] setPosition] etc.
The problem with this approach is that the actual frame of the view stays in one spot throughout any and all animations applied. What if my view is supposed to recieve mouse events? For example, I have a view that uses core animation and it is dragged around by the mouse. Is there a way I can somehow keep the view frame synced with the current state of the presentation layer so I can receive proper mouse events?
About the presentation layer, apparently it's only available when an actual animation is in progress. Is there any sort of property of the layer that can tell me where it's ACTUALLY visually at even when an animation's not in progress?
I think you need to re-phrase your question a little. It seems there is some underlying misunderstanding, but you're not really expressing it very clearly. You're question title suggests you're looking to understand something more theoretical, but your actual question suggests you're looking for something more concrete. Let me see if I can clarify a few things.
The presentationLayer provides information about the layer's current state while "in-flight".
When there is no animation occurring, the presentationLayer and the layer information will be identical. Query the layer's bounds, frame, or position to find out where it is currently in its parents coordinate space.
NSViews must have layer backing enabled to be able to perform animations.
Make sure you're not just animating with an explicit animation and not actually setting the layer value that you're animating. Animations don't automatically change the properties of the layers they're animating. You have to change the property to the ending value yourself or it will just "snap back" to the starting value.
If you want to animate the view, as opposed to a layer, you can use the animator proxy, like [[view animator] setFrame:newFrame];
Wrap calls to the animator in a CATrasaction to alter things like animation duration.
Let me know if you need some clarification by updating your question. Providing some pertinent code would really help identify the problems you're having trouble solving.
Firstly, you want to use [self setWantsLayer: YES]. Also, it's only important to call -setLayer: before -setWantsLayer: if you want to provide a specific CALayer subclass (such as a CAScrollLayer); if you just want a regular CALayer you just call -setWantsLayer: and it'll be created for you. Even better, just check the 'wants layer' option in Interface Builder.
Secondly, the entire point of using a layer-backed view is that you can continue to use the regular NSView methods and get the free CoreAnimation 'tweening' effects. If you want to use CoreAnimation as your only means of moving items around, then the correct way to do so is to create a layer backed view which contains your pure-CALayer presentation hierarchy.
I've not looked at any freely-available CoreAnimation tutorials, but I can definitely recommend the Pragmatic Programmers' book on the subject. They also have a screencast available by the book's author.

Resources