iOS icon jiggle algorithm

2019-01-21 03:21发布

I am writing an iPad app that presents user documents similar to the way Pages presents them (as large icons of the actual document). I also want to mimic the jiggling behavior when the user taps the edit button. This is the same jiggle pattern that icons make on the home screen when you tap and hold on them on both the iPhone and iPad.

I've searched the Internet and have found a few algorithms but they just cause the view to rock back and forth which is not at all like the Apple jiggle. It seems there is some randomness in there as each icon jiggles a little differently.

Does anyone have or know of some code that can re-create the same jiggle pattern (or something very close to it)? Thanks!!!

10条回答
何必那么认真
2楼-- · 2019-01-21 04:15

@mientus Original Apple Jiggle code in Swift 4, with optional parameters to adjust the duration (i.e. speed), displacement (i.e. position change) and degrees (i.e. rotation amount).

private func degreesToRadians(_ x: CGFloat) -> CGFloat {
    return .pi * x / 180.0
}

func startWiggle(
    duration: Double = 0.25,
    displacement: CGFloat = 1.0,
    degreesRotation: CGFloat = 2.0
    ) {
    let negativeDisplacement = -1.0 * displacement
    let position = CAKeyframeAnimation.init(keyPath: "position")
    position.beginTime = 0.8
    position.duration = duration
    position.values = [
        NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)),
        NSValue(cgPoint: CGPoint(x: 0, y: 0)),
        NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)),
        NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)),
        NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement))
    ]
    position.calculationMode = "linear"
    position.isRemovedOnCompletion = false
    position.repeatCount = Float.greatestFiniteMagnitude
    position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))
    position.isAdditive = true

    let transform = CAKeyframeAnimation.init(keyPath: "transform")
    transform.beginTime = 2.6
    transform.duration = duration
    transform.valueFunction = CAValueFunction(name: kCAValueFunctionRotateZ)
    transform.values = [
        degreesToRadians(-1.0 * degreesRotation),
        degreesToRadians(degreesRotation),
        degreesToRadians(-1.0 * degreesRotation)
    ]
    transform.calculationMode = "linear"
    transform.isRemovedOnCompletion = false
    transform.repeatCount = Float.greatestFiniteMagnitude
    transform.isAdditive = true
    transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))

    self.layer.add(position, forKey: nil)
    self.layer.add(transform, forKey: nil)
}
查看更多
狗以群分
3楼-- · 2019-01-21 04:20


@Vic320's answer is good but personally I don't like the translation. I've edited his code to provide a solution that I personally feel looks more like the springboard wobble effect. Mostly, it's achieved by adding a little randomness and focusing on rotation, without translation:

#define degreesToRadians(x) (M_PI * (x) / 180.0)
#define kAnimationRotateDeg 1.0

- (void)startJiggling {
    NSInteger randomInt = arc4random_uniform(500);
    float r = (randomInt/500.0)+0.5;

    CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( (kAnimationRotateDeg * -1.0) - r ));
    CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg + r ));

     self.transform = leftWobble;  // starting point

     [[self layer] setAnchorPoint:CGPointMake(0.5, 0.5)];

     [UIView animateWithDuration:0.1
                           delay:0
                         options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
                       animations:^{ 
                                 [UIView setAnimationRepeatCount:NSNotFound];
                                 self.transform = rightWobble; }
                      completion:nil];
}
- (void)stopJiggling {
    [self.layer removeAllAnimations];
    self.transform = CGAffineTransformIdentity;
}


Credit where credit's due though, @Vic320's answer provided the basis for this code so +1 for that.

查看更多
虎瘦雄心在
4楼-- · 2019-01-21 04:21

Check out the openspringboard project.

In particular, setIconAnimation:(BOOL)isAnimating in OpenSpringBoard.m. That should give you some ideas on how to do this.

查看更多
迷人小祖宗
5楼-- · 2019-01-21 04:21

I reverse engineered Apple Stringboard, and modified little bit their animation, and code below is really good stuff.

+ (CAAnimationGroup *)jiggleAnimation {
CAKeyframeAnimation *position = [CAKeyframeAnimation animation];
position.keyPath = @"position";
position.values = @[
                    [NSValue valueWithCGPoint:CGPointZero],
                    [NSValue valueWithCGPoint:CGPointMake(-1, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(1, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(-1, 1)],
                    [NSValue valueWithCGPoint:CGPointMake(1, -1)],
                    [NSValue valueWithCGPoint:CGPointZero]
                    ];
position.timingFunctions = @[
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
                             ];
position.additive = YES;

CAKeyframeAnimation *rotation = [CAKeyframeAnimation animation];
rotation.keyPath = @"transform.rotation";
rotation.values = @[
                    @0,
                    @0.03,
                    @0,
                    [NSNumber numberWithFloat:-0.02]
                    ];
rotation.timingFunctions = @[
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
                             ];

CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = @[ position, rotation ];
group.duration = 0.3;
group.repeatCount = HUGE_VALF;
group.beginTime = arc4random() % 30 / 100.f;
return group;
}

Original Apple jiggle animation:

CAKeyframeAnimation *position = [CAKeyframeAnimation animation];
position.beginTime = 0.8;
position.duration = 0.25;
position.values = @[[NSValue valueWithCGPoint:CGPointMake(-1, -1)],
                    [NSValue valueWithCGPoint:CGPointMake(0, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(-1, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(0, -1)],
                    [NSValue valueWithCGPoint:CGPointMake(-1, -1)]];
position.calculationMode = @"linear";
position.removedOnCompletion = NO;
position.repeatCount = CGFLOAT_MAX;
position.beginTime = arc4random() % 25 / 100.f;
position.additive = YES;
position.keyPath = @"position";

CAKeyframeAnimation *transform = [CAKeyframeAnimation animation];
transform.beginTime = 2.6;
transform.duration = 0.25;
transform.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
transform.values = @[@(-0.03525565),@(0.03525565),@(-0.03525565)];
transform.calculationMode = @"linear";
transform.removedOnCompletion = NO;
transform.repeatCount = CGFLOAT_MAX;
transform.additive = YES;
transform.beginTime = arc4random() % 25 / 100.f;
transform.keyPath = @"transform";

[self.dupa.layer addAnimation:position forKey:nil];
[self.dupa.layer addAnimation:transform forKey:nil];
查看更多
登录 后发表回答