Подтвердить что ты не робот

Иконный алгоритм iOS

Я пишу iPad-приложение, которое представляет пользовательские документы, похожие на то, как страницы их представляют (как большие значки фактического документа). Я также хочу подражать поведению, когда пользователь нажимает кнопку редактирования. Это тот же шаблон смешения, что и значки на главном экране, когда вы нажимаете и удерживаете их как на iPhone, так и на iPad.

Я обыскал Интернет и нашел несколько алгоритмов, но они просто заставляют взгляд качаться туда-сюда, что совсем не похоже на яблоко. Кажется, в нем есть какая-то случайность, так как каждый значок немного по-другому меняется.

Кто-нибудь знает или знает какой-то код, который может воссоздать тот же шаблон смешения (или что-то очень близкое ему)? Спасибо!!!

4b9b3361

Ответ 1

ОК, поэтому код openpringboard не совсем сделал это для меня, но я разрешил мне создать код, который, я думаю, немного лучше, но не префект, но лучше. Если у кого-то есть предложения, чтобы сделать это лучше, я бы с удовольствием их услышал... (добавьте это в подкласс вида (ов), который вы хотите поддразнивать)

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

- (void)startJiggling:(NSInteger)count {

    CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? +1 : -1 ) ));
    CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? -1 : +1 ) ));
    CGAffineTransform moveTransform = CGAffineTransformTranslate(rightWobble, -kAnimationTranslateX, -kAnimationTranslateY);
    CGAffineTransform conCatTransform = CGAffineTransformConcat(rightWobble, moveTransform);

    self.transform = leftWobble;  // starting point

    [UIView animateWithDuration:0.1
                          delay:(count * 0.08)
                        options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
                     animations:^{ self.transform = conCatTransform; }
                     completion:nil];
}

- (void)stopJiggling {
    [self.layer removeAllAnimations];
    self.transform = CGAffineTransformIdentity;  // Set it straight 
}

Ответ 2


@Vic320 ответ хороший, но лично мне не нравится перевод. Я отредактировал его код, чтобы предоставить решение, которое я лично чувствую, больше похоже на эффект качания плацдарма. В основном, это достигается путем добавления немного случайности и фокусировки на вращение, без перевода:

#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;
}


Кредит, в котором кредит, хотя, @Vic320 ответ, послужил основой для этого кода, поэтому +1 для этого.

Ответ 3

Для полноты, вот как я анимировал мой подкласс CALayer, вдохновленный другими ответами, используя явную анимацию.

-(void)stopJiggle 
{
    [self removeAnimationForKey:@"jiggle"];
}

-(void)startJiggle 
{
    const float amplitude = 1.0f; // degrees
    float r = ( rand() / (float)RAND_MAX ) - 0.5f;
    float angleInDegrees = amplitude * (1.0f + r * 0.1f);
    float animationRotate = angleInDegrees / 180. * M_PI; // Convert to radians

    NSTimeInterval duration = 0.1;
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    animation.duration = duration;
    animation.additive = YES;
    animation.autoreverses = YES;
    animation.repeatCount = FLT_MAX;
    animation.fromValue = @(-animationRotate);
    animation.toValue = @(animationRotate);
    animation.timeOffset = ( rand() / (float)RAND_MAX ) * duration;
    [self addAnimation:animation forKey:@"jiggle"];
}

Ответ 4

Просмотрите проект openspringboard.

В частности, setIconAnimation: (BOOL) isAnimating в OpenSpringBoard.m. Это должно дать вам несколько идей о том, как это сделать.

Ответ 5

В интересах других, которые приходят в будущем, я чувствовал, что джигг, предложенный @Vic320, был слишком роботизирован и сравнивал его с Keynote, он был слишком сильным, а не органическим (случайным?) достаточно. Итак, в духе совместного использования, вот код, который я встроил в свой подкласс UIView... мой контроллер представления хранит массив этих объектов, и когда пользователь нажимает кнопку "Редактировать", контроллер вида отправляет каждое сообщение для запуска, затем следуют сообщением stopJiggling, когда пользователь нажимает кнопку "Готово".

- (void)startJiggling
{
    // jiggling code based off the folks on stackoverflow.com:
    // http://stackoverflow.com/questions/6604356/ios-icon-jiggle-algorithm

#define degreesToRadians(x) (M_PI * (x) / 180.0)
#define kAnimationRotateDeg 0.1
    jiggling = YES;
    [self wobbleLeft];
}

- (void)wobbleLeft
{
    if (jiggling) {
        NSInteger randomInt = arc4random()%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

        [UIView animateWithDuration:0.1
                      delay:0
                    options:UIViewAnimationOptionAllowUserInteraction
                 animations:^{ self.transform = rightWobble; }
                 completion:^(BOOL finished) { [self wobbleRight]; }
          ];
    }
}

- (void)wobbleRight
{
    if (jiggling) {

        NSInteger randomInt = arc4random()%500;
        float r = (randomInt/500.0)+0.5;

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

        self.transform = rightWobble;  // starting point

        [UIView animateWithDuration:0.1
                      delay:0
                    options:UIViewAnimationOptionAllowUserInteraction
                 animations:^{ self.transform = leftWobble; }
                 completion:^(BOOL finished) { [self wobbleLeft]; }
         ];
    }

}
- (void)stopJiggling
{
    jiggling = NO;
    [self.layer removeAllAnimations];
    [self setTransform:CGAffineTransformIdentity];
    [self.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
}

Ответ 6

@mientus Исходный код Apple Jiggle в Swift 4 с дополнительными параметрами для регулировки продолжительности (то есть скорости), смещения (то есть изменения положения) и градусов (т.е. количества вращения).

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)
}

Ответ 7

Итак, я уверен, что я закричу за то, что написал грязный код (возможно, есть более простые способы сделать это, о чем я не знаю, потому что я полуновичок), но это более случайная версия алгоритма Vic320, который изменяет количество вращения и перевода. Он также решает случайным образом, какое направление он будет колебаться первым, что дает более сильный более случайный взгляд, если у вас есть несколько вещей, качающихся одновременно. Если эффективность для вас большая проблема, не используйте. Это именно то, что я придумал, чтобы я знал, как это сделать.

Для кого-то интересно, что вам нужно #import <QuartzCore/QuartzCore.h> и добавить его в связанные библиотеки.

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

- (void)startJiggling:(NSInteger)count {
    double kAnimationRotateDeg = (double)(arc4random()%5 + 5) / 10;
    double kAnimationTranslateX = (arc4random()%4);
    double kAnimationTranslateY = (arc4random()%4);

    CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? +1 : -1 ) ));
    CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? -1 : +1 ) ));
    int leftOrRight = (arc4random()%2);
    if (leftOrRight == 0){
        CGAffineTransform moveTransform = CGAffineTransformTranslate(rightWobble, -kAnimationTranslateX, -kAnimationTranslateY);
        CGAffineTransform conCatTransform = CGAffineTransformConcat(rightWobble, moveTransform);
        self.transform = leftWobble;  // starting point

        [UIView animateWithDuration:0.1
                              delay:(count * 0.08)
                            options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
                         animations:^{ self.transform = conCatTransform; }
                         completion:nil];
    } else if (leftOrRight == 1) {
        CGAffineTransform moveTransform = CGAffineTransformTranslate(leftWobble, -kAnimationTranslateX, -kAnimationTranslateY);
        CGAffineTransform conCatTransform = CGAffineTransformConcat(leftWobble, moveTransform);
        self.transform = rightWobble;  // starting point

        [UIView animateWithDuration:0.1
                              delay:(count * 0.08)
                            options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
                         animations:^{ self.transform = conCatTransform; }
                         completion:nil];
    }
}

- (void)stopJiggling {
    [self.layer removeAllAnimations];
    self.transform = CGAffineTransformIdentity;  // Set it straight 
}

Ответ 8

Я переделал встроенную Apple Stringboard и немного изменил их анимацию, а код ниже - действительно хороший материал.

+ (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;
}

Оригинальная анимация Apple jiggle:

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];

Ответ 9

Пол Попил дал превосходный ответ на это выше, и я навсегда обязан ему за это. Есть одна небольшая проблема, которую я обнаружил с его кодом, и то, что он не работает хорошо, если эта процедура вызывается несколько раз - анимации слоя иногда теряются или деактивируются.

Зачем называть это более одного раза? Я реализую это через UICollectionView, и, поскольку ячейки отключены или перемещены, мне нужно восстановить покачивание. С оригинальным кодом Пола мои ячейки часто перестали бы покачиваться, если бы они прокручивались за пределы экрана, несмотря на мои попытки восстановить покачивание в пределах очереди и обратный вызов willDisplay. Однако, давая две анимации с именами ключей, он всегда работает надежно, даже если вызывается дважды в ячейке.

Это почти весь код Пола с небольшим исправлением, описанным выше, плюс я создал его как расширение UIView и добавил Swift 4-совместимый stopWiggle.

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

extension UIView {
    func startWiggle() {
        let duration: Double = 0.25
        let displacement: CGFloat = 1.0
        let 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: "bounce")
        self.layer.add(transform, forKey: "wiggle")
    }

    func stopWiggle() {
        self.layer.removeAllAnimations()
        self.transform = .identity
    }
}

В случае, если это сэкономит кому-либо еще время для реализации этого в UICollectionView, вам понадобится несколько других мест, чтобы убедиться, что покачивание остается во время перемещений и прокрутки. Во-первых, процедура, которая начинает шевелить все ячейки, которые вызывались с самого начала:

func wiggleAllVisibleCells() {
    if let visible = collectionView?.indexPathsForVisibleItems {
        for ip in visible {
            if let cell = collectionView!.cellForItem(at: ip) {
                cell.startWiggle()
            }
        }
    }
}

И так как отображаются новые ячейки (от движения или прокрутки), я восстанавливаю покачивание:

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    // Make sure cells are all still wiggling
    if isReordering {
        cell.startWiggle()
    }
}