Hofi
Hofi

Reputation: 966

Why NSAnimationContext completionHandler does not work (sometimes)?


    // wc here is an NSWindowController

    [NSAnimationContext beginGrouping];
    [[NSAnimationContext currentContext] setDuration:0.5f];

    if (duplication) {
        NSPoint origin = initialSize.origin;
        origin.y += initialSize.size.height;
        origin = [wc.window cascadeTopLeftFromPoint:origin];
        origin.y -= initialSize.size.height;
        //[[wc.window animator] setFrameOrigin:origin];   // Why setFrameOrigin and cascadeTopLeftFromPoint are not animated?
        initialSize.origin = origin;
        [[wc.window animator] setFrame:initialSize display:YES];
    }

    // This block should be invoked when all of the animations started above have completed or been cancelled.
    // For not to show the edit window till the duplication animation not finished
    [NSAnimationContext currentContext].completionHandler = ^{
        if (edit)
            [wc editDocument:self];
        else
            if (fullScreen)
                [wc.window toggleFullScreen:self];
    };

    [NSAnimationContext endGrouping];

In this case the completion block executed but unfortunately does not wait for the window reposition be finished, instead it opens the window's edit sheet immediately and moves them together.

The most strange thing is that a few lines above in the same source file the same type of completition block works fine :-O

What am I missing here?

Upvotes: 3

Views: 2118

Answers (3)

Mecki
Mecki

Reputation: 132919

Check the documentation of completionHandler:

If set to a non-nil value, a context’s completionHandler is guaranteed to be called on the main thread as soon as all animations subsequently added to the current NSAnimationContext grouping have completed or been cancelled.

Source: https://developer.apple.com/documentation/appkit/nsanimationcontext/1531132-completionhandler?language=objc

The completion handler only affects animations added after the completion handler has been set.

At the end it also says:

If no animations are added before the current grouping is ended—or the completionHandler is set to a different value—the handler will be invoked immediately.

In your case, no animations are added between setting a completion handler and the end of the current grouping, thus your completion handler is called immediately.

The correct code would be:

// wc here is an NSWindowController

[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.5f];

// This block should be invoked when all of the animations started above have completed or been cancelled.
// For not to show the edit window till the duplication animation not finished
[NSAnimationContext currentContext].completionHandler = ^{
    if (edit)
        [wc editDocument:self];
    else
        if (fullScreen)
            [wc.window toggleFullScreen:self];
};

if (duplication) {
    NSPoint origin = initialSize.origin;
    origin.y += initialSize.size.height;
    origin = [wc.window cascadeTopLeftFromPoint:origin];
    origin.y -= initialSize.size.height;
    //[[wc.window animator] setFrameOrigin:origin];   // Why setFrameOrigin and cascadeTopLeftFromPoint are not animated?
    initialSize.origin = origin;
    [[wc.window animator] setFrame:initialSize display:YES];
}

[NSAnimationContext endGrouping];

Upvotes: 2

Mattie
Mattie

Reputation: 3028

This actually isn't a bug, but it has tripped me up a ton of times. You must set the completion handler before you invoke any animations.

Upvotes: 6

Hofi
Hofi

Reputation: 966

OK, it is a BUG and I file a bug report on it. The next version works perfectly

__block NSRect newPosition(initialSize);
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
    [context setDuration:0.5f];

    if (duplication) {
        NSPoint origin = newPosition.origin;
        origin.y += newPosition.size.height;
        origin = [wc.window cascadeTopLeftFromPoint:origin];
        origin.y -= newPosition.size.height;
        //[[wc.window animator] setFrameOrigin:origin];   // Why setFrameOrigin and cascadeTopLeftFromPoint are not animated?
        newPosition.origin = origin;
        [[wc.window animator] setFrame:newPosition display:YES];
    }
} completionHandler:^{
    // This block will be invoked when all of the animations
    // started above have completed or been cancelled.
    if (edit)
        [wc editDocument:self];
    else
        if (fullScreen)
            [wc.window toggleFullScreen:self];
}];

Upvotes: 0

Related Questions