macOS animation works once, then not again


Steve Mills
 

I'm probably missing something obvious here. I have an NSScrollView. Its document view is an NSImageView. At some point, I add a custom NSView subclass as a subview of the image view. This custom view draws a bezier path over the image. All good so far.

At certain points, I need to animate a zoom/scroll to focus on a particular area of the image, then show the bezier view, and finally fade out the bezier view (a simple "look here" signal to the user). This works the first time (the bezier draws and fades out), but I can't get the bezier view to show itself again.

I've tried many different forms of animation: NSAnimationContext's runAnimationGroup; adding a CABasicAnimation to the bezier view's layer; NSAnimationContext beginGrouping/endGrouping; possibly others. The property I'm changing is bezView.animator.alphaValue or bezView.layer.opacity.

I've tried forcing the bezier view to be layer-based (as well as replacing the host layer with a CAShapeLayer) or just drawing the bezier in the drawRect method. Here's the code that works just peachy on iOS:

[UIView animateWithDuration:1 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:1 options:0 animations:^{
if(!CGRectIsEmpty(boxToZoomTo))
[self.scrollView zoomToRect:boxToZoomTo animated:NO];
} completion:^(BOOL finished) {
self.bezView.alpha = 1;

[UIView animateWithDuration:1 animations:^{
self.bezView.alpha = 0;
}];
}];

And here's one attempt that's similar on macOS (setting allowsImplicitAnimation was something else I tried):

[NSAnimationContext runAnimationGroup:^(NSAnimationContext* _Nonnull context) {
context.duration = 1;
context.allowsImplicitAnimation = YES;

if(!CGRectIsEmpty(boxToZoomTo))
[self.scrollView.animator magnifyToFitRect:boxToZoomTo];
} completionHandler:^{
self.bezView.alphaValue = 1;

[NSAnimationContext runAnimationGroup:^(NSAnimationContext* _Nonnull context) {
context.duration = 1;
context.allowsImplicitAnimation = YES;
self.bezView.animator.alphaValue = 0;
} completionHandler:^{
}];
}];

Shouldn't that just work? Why isn't it redrawing the bez view at alphaValue 1 before fading to 0?

--
Steve Mills
Drummer, Mac geek


Quincey Morris
 

On Jan 30, 2018, at 09:52 , Steve Mills <sjmills@...> wrote:

Shouldn't that just work? Why isn't it redrawing the bez view at alphaValue 1 before fading to 0?

I don’t know, but your code made me wonder what happens (if you haven’t tried it already) if you remove this line from your completion handler:

self.bezView.alphaValue = 1;

and add this variation to the initiating animation block:

self.bezView.animator.alphaValue = 1;

(Not the exact animation you were looking for, but the question is whether it works at all.)

Or, going at the opposite end of that same thought, is it in fact meaningful to use the animator proxy:

self.bezView.animator.alphaValue = 0;

inside an explicit animation block? Maybe you can’t mix the two mechanisms.

(I looked back at some code I wrote years ago that used explicit animations to fade views in and out, but it was not much help. I used NSViewAnimation objects, and I had commented it out anyway, because at some macOS update it stopped working reliably and I had no idea why.)




Alex Zavatone
 

Can you dump the properties of the object you are trying to animate and the view before and after each attempt?

What thread is this happening on? Anything else besides thread 1?

A year back, I noticed that animations simply weren’t happening in my iOS app, but only on the iPad. At one point, I gave up in frustration and went off to grab a coffee. When I came back, the animation was completed.

What was happening was that the animation was not happening on the main thread and between 15 and 26 seconds later, it executed.

If that’s not it, are you properly starting and ending the animationContext?

What if you dispatch the animation on the main thread as async with GCD?

Alex Zavatone

On Jan 30, 2018, at 11:52 AM, Steve Mills <sjmills@mac.com> wrote:

I'm probably missing something obvious here. I have an NSScrollView. Its document view is an NSImageView. At some point, I add a custom NSView subclass as a subview of the image view. This custom view draws a bezier path over the image. All good so far.

At certain points, I need to animate a zoom/scroll to focus on a particular area of the image, then show the bezier view, and finally fade out the bezier view (a simple "look here" signal to the user). This works the first time (the bezier draws and fades out), but I can't get the bezier view to show itself again.

I've tried many different forms of animation: NSAnimationContext's runAnimationGroup; adding a CABasicAnimation to the bezier view's layer; NSAnimationContext beginGrouping/endGrouping; possibly others. The property I'm changing is bezView.animator.alphaValue or bezView.layer.opacity.

I've tried forcing the bezier view to be layer-based (as well as replacing the host layer with a CAShapeLayer) or just drawing the bezier in the drawRect method. Here's the code that works just peachy on iOS:

[UIView animateWithDuration:1 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:1 options:0 animations:^{
if(!CGRectIsEmpty(boxToZoomTo))
[self.scrollView zoomToRect:boxToZoomTo animated:NO];
} completion:^(BOOL finished) {
self.bezView.alpha = 1;

[UIView animateWithDuration:1 animations:^{
self.bezView.alpha = 0;
}];
}];

And here's one attempt that's similar on macOS (setting allowsImplicitAnimation was something else I tried):

[NSAnimationContext runAnimationGroup:^(NSAnimationContext* _Nonnull context) {
context.duration = 1;
context.allowsImplicitAnimation = YES;

if(!CGRectIsEmpty(boxToZoomTo))
[self.scrollView.animator magnifyToFitRect:boxToZoomTo];
} completionHandler:^{
self.bezView.alphaValue = 1;

[NSAnimationContext runAnimationGroup:^(NSAnimationContext* _Nonnull context) {
context.duration = 1;
context.allowsImplicitAnimation = YES;
self.bezView.animator.alphaValue = 0;
} completionHandler:^{
}];
}];

Shouldn't that just work? Why isn't it redrawing the bez view at alphaValue 1 before fading to 0?

--
Steve Mills
Drummer, Mac geek




Steve Mills
 

On Jan 30, 2018, at 14:40:29, Quincey Morris <quinceymorris@rivergatesoftware.com> wrote:

On Jan 30, 2018, at 09:52 , Steve Mills <sjmills@mac.com> wrote:

I don’t know, but your code made me wonder what happens (if you haven’t tried it already) if you remove this line from your completion handler:

self.bezView.alphaValue = 1;
and add this variation to the initiating animation block:

self.bezView.animator.alphaValue = 1;
Doing that did indeed make it work. I ended up with 3 animate/completion blocks: 1. Do the zoom/scroll. 2. Fade the bez in very quickly (0.001). 3. Fade the bez out.

I appreciate that Apple has continually added new ways to animate the UI, each one supposedly easier to use than the previous, but damn, there are so many gotchas with each one. One might suck at smooth resizing unless you set the layerContentsRedrawPolicy to a certain value. Another might be very easy to use, but doesn't do a whole bunch of properties at all. One clearly doesn't look at the current value of the property it's animating and start the animation there. Bleah!

Thanks for the suggestion!

--
Steve Mills
Drummer, Mac geek