1
votes

I'm trying to hide or show a view based on the value of a DCRoundSwitch object, but UIView animation blocks are not animating - the changes are instantly applied. I have also tried animating the view's layer with CATransaction, but those changes were instantly applied as well. If the same animation block is moved to another location (say, viewWillAppear:), animation works fine.

Any help determining why animations are not working is greatly appreciated. Thanks!

Here's the animation code:

- (void)switchValueChanged:(id)sender
{
    [UIView animateWithDuration:0.33
                     animations:^{
                         self.containerView.alpha = self.switchView.isOn ? 1 : 0;
                     }];
}

For reference, here's the code DCRoundSwitch uses to change the switch value. animated is YES and ignoreControlEvents is NO.

- (void)setOn:(BOOL)newOn animated:(BOOL)animated ignoreControlEvents:(BOOL)ignoreControlEvents
{
    BOOL previousOn = self.on;
    on = newOn;
    ignoreTap = YES;

    [CATransaction setAnimationDuration:0.014];
    knobLayer.gripped = YES;

    // setup by turning off the manual clipping of the toggleLayer and setting up a layer mask.
    [self useLayerMasking];
    [self positionLayersAndMask];

    [CATransaction setCompletionBlock:^{
        [CATransaction begin];
        if (!animated)
            [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
        else
            [CATransaction setValue:(id)kCFBooleanFalse forKey:kCATransactionDisableActions];

        CGFloat minToggleX = -toggleLayer.frame.size.width / 2.0 + toggleLayer.frame.size.height / 2.0;
        CGFloat maxToggleX = -1;


        if (self.on)
        {
            toggleLayer.frame = CGRectMake(maxToggleX,
                                           toggleLayer.frame.origin.y,
                                           toggleLayer.frame.size.width,
                                           toggleLayer.frame.size.height);
        }
        else
        {
            toggleLayer.frame = CGRectMake(minToggleX,
                                           toggleLayer.frame.origin.y,
                                           toggleLayer.frame.size.width,
                                           toggleLayer.frame.size.height);
        }

        if (!toggleLayer.mask)
        {
            [self useLayerMasking];
            [toggleLayer setNeedsDisplay];
        }

        [self positionLayersAndMask];

        knobLayer.gripped = NO;

        [CATransaction setCompletionBlock:^{
            [self removeLayerMask];
            ignoreTap = NO;

            // send the action here so it get's sent at the end of the animations
            if (previousOn != on && !ignoreControlEvents)
                [self sendActionsForControlEvents:UIControlEventValueChanged];
        }];

        [CATransaction commit];
    }];
}
3
Also: if I replace the DCRoundSwitch with a UISwitch, animations work, so it has to be something about the switch's animation block. Does anyone who knows about CATransaction see anything wrong with it?Austin
Before anyone asks, I can't use the UISwitch in the final product, because DCRoundSwitch provides customization options that UISwitch does not.Austin
I don't see were you call [CATransaction begin] on the initial transaction.. or commit. However... more questionable is the use of masks. Questionable in the sense that you are trying to animate an alpha value while simultaneously messing with a mask. Since I can't see exactly what you are doing with the mask I can't tell if it would have an impact. In general (from my experience): If you provide multiple changes to something that influences a property (such as alpha) directly or indirectly, within an animation block (or concurrently with another animation block in this case), ...Matt
(cont) the initial state of that property might be overwritten before the animation starts. For example, setting the frame of a view multiple times within an animation block might cause the initial frame for the animation to be one of the intermittent frames. In your case, you might be manipulating the visual alpha with your masks, or the actual alpha if it is implemented (internally) using layer masks. If you can, I would try combining the functionality of the alpha animation with the setOn animations in a way that there is no overlap of property change between animation blocks.Matt
The changes made in the initial transaction are added to an implicit transaction, which is why there is no call to begin and commit. The layers/mask are all part of DCRoundSwitch (github.com/domesticcatsoftware/DCRoundSwitch), which shares a parent view with the container. Changes to one view's mask should not affect changes to a sibling's alpha, should they?Austin

3 Answers

2
votes

Talking about the implicit transaction with @Matt got me thinking... UIView's animate methods wrap around CATransaction, and property changes are normally added to the implicit transaction. There is however, no transaction created for changes made in the completion block, which apparently affects [UIView animateWithDuration]. I changed

if (previousOn != on && !ignoreControlEvents)
    [self sendActionsForControlEvents:UIControlEventValueChanged];

to

if (previousOn != on && !ignoreControlEvents)
{
    [CATransaction begin];
    [CATransaction setDisableActions:NO];
    [self sendActionsForControlEvents:UIControlEventValueChanged];
    [CATransaction commit];
}

and it works. ([CATransaction setDisableActions:NO] was also necessary)

Thanks guys for helping.

1
votes

I dont know whats happening in the DCRoundSwitch code with CATransaction and how this works. But you could try this:

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.33];

self.containerView.alpha = self.switchView.isOn ? 1 : 0;

[UIView commitAnimations];
0
votes

I have used the above Austin solution. but unable to fix it.

i have used this below code. which works correctly.

Please follow up this thread

https://github.com/domesticcatsoftware/DCRoundSwitch/issues/12

U can just go to the Class which is using this DCRoundSwitch , on that class's dealloc method put this line.

 - (void)dealloc
 {
      [self.MyDCRoundSwitch removeTarget:nil
                         action:NULL
                         forControlEvents:UIControlEventAllEvents];
      [super dealloc];
 }