Search code examples
iosuikit-dynamicsuidynamicbehavior

Should I removeBehavior a UIPushBehavior -- I am adding many pushes


I have a small UIView that bounces in a box. I am adding an Instantaneous mode. push. In fact I am adding very many pushes -- 5 or more Hz.

Mystery:

(1) Do I have to removeBehavior???? if so .. when?!? "after" an "instant" ?

(2) Is it that UIPushBehaviorModeInstantaneous is a special case, and you do not have to (or cannot) remove those??

(3) When you addBehavior: does that .. retain?! the UIPushBehavior? Or?? WTF?!

(4) I couldn't seem to find these aspects documented anywhere!

-(void)pushMeRight:(CGFloat)mag
    {
    if ( self.bounceAnimator == nil ) return;
    NSLog(@"...........push me right %.2f", mag);

    UIPushBehavior *pushRight = [[UIPushBehavior alloc]
            initWithItems:@[self]
            mode:UIPushBehaviorModeInstantaneous];

    pushRight.magnitude = mag;
    pushRight.angle = radians(0);   // (NB, 0 is right, 90 is down)
    [self.bounceAnimator addBehavior:pushRight];
    }

{Note: I simply alloc the UIPushBehavior each time I need one. Note that if you try to use "just one" as a property, it does not work. In fact, Rob explains why below.}


Solution

After extremely extensive testing, we found that Rob's "second" solution, using the .action, is essentially perfect.

Again after vast testing, we would very strongly recommend that the following code, is, really, "the solution", the only way to code repetitive pushes.. Thank goodness for Rob :/

-(void)pushAllUp:(CGFloat)mag
    {
    if ( self.bounceAnimator == nil ) return;
    for ( UIView *pushme in self.subviews )
        {
        UIPushBehavior *pp =
        [[UIPushBehavior alloc]initWithItems:@[ pushme ]
             mode:UIPushBehaviorModeInstantaneous];

        pp.magnitude = mag;
        pp.angle = radians(270); // (NB, 0 is right, 90 is down)

        UIPushBehavior __weak *weakPP = pp;
        pp.action = ^{ if (!weakPP.active)
             [self.bounceAnimator removeBehavior:weakPP];};

        [self.bounceAnimator addBehavior:pp];
        }
    }

Solution

  • In answer to your questions:

    1. Yes, you are adding that UIPushBehavior correctly.

    2. You technically don't have to call removeBehavior for an instantaneous push because the behavior will have its active status turned off as soon as the instantaneous push takes place.

      Having said that, I'd be inclined to remove the behavior because you're otherwise taking up memory, with the animator maintaining a strong reference to these non-active instantaneous push behaviors. (This is easily verified by logging the animator's behaviors property, which is an array of all of the behaviors it is keeping track of.) There might eventually be a performance related issue for it to iterate through all of these non-active behaviors, too, though I suspect the memory concerns are probably of more significant interest.

      But unlike other behaviors (e.g. UISnapBehavior) which stay active, you don't have to worry about lingering instantaneous push behaviors continuing to affect the items to which it was added.

    3. They don't "expire", per se, but, yes, they quickly go to an active state of NO.

    4. Yes, when you add a behavior to the animator, the animator will maintain a strong reference to it until you remove the behavior.

    Personally, I'd be inclined to remove the behavior after adding it. Because it's instantaneous, the timing of its removal isn't terribly critical, and you could do something as simple as:

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [self.animator removeBehavior:push];
    });
    

    Or, you could set up an action that removed it for you.

    UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:self.items mode:UIPushBehaviorModeInstantaneous];
    push.pushDirection = ...
    
    UIPushBehavior __weak *weakPush = push;  // avoid strong reference cycle
    push.action = ^{
        if (!weakPush.active) {
            [self.animator removeBehavior:weakPush];
        }
    };
    
    [self.animator addBehavior:push];