Иконный алгоритм iOS
Я пишу iPad-приложение, которое представляет пользовательские документы, похожие на то, как страницы их представляют (как большие значки фактического документа). Я также хочу подражать поведению, когда пользователь нажимает кнопку редактирования. Это тот же шаблон смешения, что и значки на главном экране, когда вы нажимаете и удерживаете их как на iPhone, так и на iPad.
Я обыскал Интернет и нашел несколько алгоритмов, но они просто заставляют взгляд качаться туда-сюда, что совсем не похоже на яблоко. Кажется, в нем есть какая-то случайность, так как каждый значок немного по-другому меняется.
Кто-нибудь знает или знает какой-то код, который может воссоздать тот же шаблон смешения (или что-то очень близкое ему)? Спасибо!!!
Ответы
Ответ 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()
}
}