Анимации в iOS для начинающих. Модели, классы от Core Animation, блоки

Думаю, всем разработчикам хочется в той или иной степени украсить свои приложения различными эффектами. Android-отдел компании Live Typing уже выпустил две статьи на эту тему: про тип классов Animator и собственную библиотеку CannyViewAnimation, заменяющую им несовершенный ViewAnimator. Я представляю отдел iOS-разработки нашей компании и тоже хочу высказаться на тему, которая важнее, чем кажется.

Эта статья — введение в мир анимаций для iOS-приложений. Рекомендуется тем, кто никода не работал с анимациями, либо не понимает некоторые моменты в стандартных iOS-анимациях.

И iOS-разработчики, и дизайнеры сходятся во мнении, что анимация является одним из ключевых элементов пользовательских интерфейсов. Она нужна, чтобы:

  • привлечь внимание пользователя к конкретному объекту;
  • показать ему, как и что нужно сделать в том или ином случае;
  • показать логику перехода или иерархичность экранов, что помогает ориентироваться в приложении;
  • убедить пользователя в том, что совершённое действие действительно совершено;
  • разнообразить приложение, ведь без анимаций оно будет выглядеть очень сухо и пользоваться им будет неинтересно.

Другими словами, анимация — это способ придать динамику конкретному элементу в рамках интерфейса, либо интерфейсу в целом.

Основные понятия


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

Core Animation — фреймворк для работы с базовыми классами анимации: CABasicAnimation, CAKeyFrameAnimation, CATransition, CAAnimationGroup. Использование Core Animation полностью автоматизировано: не нужно создавать циклов и таймеров, чтобы сделать анимацию.

CALayer — набор классов, управляющих анимацией корневого слоя объекта. Классы получают доступ к слою и применяют к нему одно из свойств. Среди таких свойств — размер и положение слоя, фоновый цвет слоя, тень, скруглённые углы и тому подобное.

Чтобы при создании анимации указать путь к свойствам CALayer, используется метод animationWithKeyPath или свойство keyPath. Последние назначаются строковым видом @«название_ключа». Вот несколько примеров:

CALayer *layer = [CALayer layer].opacity
CALayer *layer = [CALayer layer].position
CALayer *layer = [CALayer layer].shadowRadius

С использованием animationWithKeyPath мы познакомимся получше в разделе «Примеры явных анимаций». А все свойства можно посмотреть здесь.

Модели анимации


Существует две модели анимации: неявная и явная.

Неявная анимация


Неявная модель анимации Core Animation предполагает, что все изменения в анимируемых свойствах слоя должны быть постепенными и асинхронными. Анимация будет происходить без эффектов, переходя от одного значения к другому.

Предположим, что текущее положение слоя в (theLayer.position.x, theLayer.position.y)

Objective-c:

theLayer.position=CGPointMake(100.0,100.0);

Swift:
theLayer.position = CGPoint.init(x: 100.0, y: 100.0)

f6faf2d6925446fa98a8cf8160d05eb9.gif

Явная анимация


Явная модель анимации требует создания объекта анимации и постановки начальных и конечных значений и будет протекать плавно от одного значения к другому. Анимация не начнётся, пока не будет добавлена к слою.

Набор классов анимаций, унаследованных от Core Animation:

CABasicAnimation. Обеспечивает простую интерполяцию между значениями для слоя. Например, с этим классом мы можем перемещать слой из одной точки в другую, менять значение прозрачности от одного к другому и т.п. С помощью класса можно сделать анимации для привлечения внимания пользователя к определённому объекту на экране или показать обучающую информацию в виде анимации.

Objective-c:

CABasicAnimation *theAnimation;

theAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
theAnimation.duration = 3.0;
theAnimation.repeatCount = 2;
theAnimation.autoreverses = NO (YES);
theAnimation.fromValue= [NSValue valueWithCGPoint:CGPointMake(screenWidth/2, _animationButton.frame.origin.y)];
theAnimation.toValue= [NSValue valueWithCGPoint:CGPointMake(100, 100)];
[theLayer addAnimation:theAnimation forKey:@"animatePosition"];

Swift:
let theAnimation = CABasicAnimation(keyPath: "position");

theAnimation.fromValue = [NSValue(CGPoint: CGPointMake(screenWidth/2, self.animationButton.frame.origin.y))]
theAnimation.toValue = [NSValue(cgPoint: CGPoint.init(x: 100.0, y: 100.0))]
theAnimation.duration = 3.0;
theAnimation.autoreverses = false //true - возвращает в исходное значение либо плавно, либо нет
theAnimation.repeatCount = 2
theLayer.addAnimation(theAnimation, forKey: "animatePosition");

В правой анимации параметр autoreverses = NO, в левой — YES. То есть позиция либо возвращается к первоначальному значению плавно, либо нет.

fd8d591a6dd140c18e069ae2ae5a2579.gife7d4c18a3a924cd7957d56e2f559de88.gif

CAKeyframeAnimation. Обеспечивает изменение значений свойства слоя по величинам, которые задаются в массиве. Для инициализации используется метод animationWithKeyPath с указанием свойства, которое нужно изменить.Также указываем массив значений, которые будут представляться на каждом этапе анимации. Мы можем задать несколько значений, в которые будет перемещаться слой — получается гораздо интереснее простого изменения позиции из одной точки в другую, как было в примерах выше.

Objective-c:

NSArray * pathArray = @[ [NSValue valueWithCGPoint:CGPointMake(10., 10.)], [NSValue valueWithCGPoint:CGPointMake(100., 10.)], [NSValue valueWithCGPoint:CGPointMake(10., 100.)], [NSValue valueWithCGPoint:CGPointMake(10., 10.)], ]; 

CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 
pathAnimation.values = pathArray; 
pathAnimation.duration = 5.0; 
[self.label.layer addAnimation:pathAnimation forKey:@"position"];

Swift:
let animation = CAKeyframeAnimation()

let pathArray = [[NSValue(cgPoint: CGPoint.init(x: 10.0, y: 10.0))],  [NSValue(cgPoint: CGPoint.init(x: 100.0, y: 100.0))], [NSValue(cgPoint: CGPoint.init(x: 10.0, y: 100.0))], [NSValue(cgPoint: CGPoint.init(x: 10.0, y: 10.0))], ]

animation.keyPath = "position"
animation.values = pathArray
animation.duration = 5.0
self.label.layer.add(animation, forKey: "position")

557a34e71d0d46bc8dbd4d6a1e1566de.gif

CATransition. Обеспечивает эффект перехода, который влияет на контент всего слоя. Он исчезает, толкает или раскрывает содержимое слоя при анимации. CATransition можно использовать для перехода между UIView или для изменения переходов между UIViewController: плавное появление, появление с разных сторон, появление поверх текущего контента, выталкивание текущего контента.

Для того, чтобы реализовать кастомный переход между экранами, нужно в методе pushViewController: animated: указать NO (false) для параметра animated.

Objective-c:

CATransition *transition = [CATransition animation];
transition.duration = 2.35
transition.timingFunction = [CAMediaTimingFunction      functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
 [[self navigationController] pushViewController:[NewViewController new] animated:NO];

Swift:
let transition = CATransition()

transition.duration = 2.35
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
self.navigationController?.pushViewController(ViewController(), animated: false)

56e00ea54d2342d0a0a79ea83ab24108.gif

Типы для transition.type:

  • kCATransitionFade. Содержимое слоя исчезает, как только он становится видимым или невидимым;
  • kCATransitionMoveIn. Содержимое слоя скользит поверх текущего контента. С этим типом используются простые подтипы transition.subtype;
  • kCATransitionPush. Содержимое слоя выталкивает существующий контент. С этим типом используются простые подтипы transition.subtype;
  • kCATransitionReveal. Содержание слоя раскрывается постепенно в направлении, указанном подтипом для перехода. С этим типом используются простые подтипы transition.subtype.

Подтипы для transition.subtype:
  • kCATransitionFromRight. Представление начинается справа;
  • kCATransitionFromLeft. Представление начинается слева;
  • kCATransitionFromTop. Представление начинается сверху;
  • kCATransitionFromBottom. Представление начинается снизу;

Весь список типов и подтипов CATransition можно посмотреть в официальной документации Apple для разработчиков.

Типы:
Swift
Objective-c

Подтипы:
Swift
Objective-c

CAAnimationGroup. Позволяет создать массив анимированных объектов, которые сгруппируются вместе и будут работать одновременно.

Objective-c:

CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    [posAnimation setFromValue:[NSValue valueWithCGPoint:CGPointMake(_animationButton.frame.origin.x, _animationButton.frame.origin.y)]];
    [posAnimation setToValue:[NSValue valueWithCGPoint:CGPointMake(100, 100)]];
    posAnimation.autoreverses = YES;

//Важная часть, в которой задаётся скорость анимации и время, через которое анимация начнёт проигрываться.

    [posAnimation setDuration:10.0];
    [posAnimation setBeginTime:0.0];

CABasicAnimation *heightAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
heightAnimation.autoreverses = YES;
[heightAnimation setFromValue: [NSValue valueWithCGSize:CGSizeMake(_animationButton.frame.size.width,_animationButton.frame.size.height)]];
[heightAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(_animationButton.frame.size.width, 200)]];
[heightAnimation setDuration:10.0];
[heightAnimation setBeginTime:5.0];

CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration:10.0];
[group setAnimations:[NSArray arrayWithObjects:posAnimation, heightAnimation, nil]];
[_animationButton.layer addAnimation:group forKey:nil];

Swift:
var animations = [CABasicAnimation]()

var posAnimation = CABasicAnimation(keyPath: "position")
posAnimation.duration = 1.0
posAnimation.autoreverses = true
posAnimation.fromValue = [NSValue(cgPoint: CGPoint.init(x: self.animationButton.frame.origin.x, y: self.animationButton.frame.origin.y))]
posAnimation.toValue = [NSValue(cgPoint: CGPoint.init(x: 100.0, y: 100.0))]
animations.append(posAnimation)
        
var heightAnimation = CABasicAnimation(keyPath: "bounds.size")

heightAnimation.autoreverses = true
heightAnimation.duration = 10.0
heightAnimation.fromValue =  [NSValue(cgSize: CGSize.init(width: self.animationButton.frame.size.width, height: self.animationButton.frame.size.height))]
heightAnimation.toValue = [NSValue(cgSize: CGSize.init(width: self.animationButton.frame.size.width, height: 200.0))]
heightAnimation.beginTime = 5.0
animations.append(heightAnimation)
        
let group = CAAnimationGroup()

group.duration = 10.0 
group.animations = animations
self.animationButton.layer.addAnimation(group, forKey: nil)

Здесь параметр BeginTime указывает, через какой промежуток после старта запустится анимация.

Анимация изменения позиции начинается сразу после старта, а анимация изменения высоты кнопки — с пятой секунды.

8410bd258c374b6292029e7cef61e288.gif

Удаление анимаций


Мы можем удалить как конкретную анимацию у layer, так и все анимации.

Удаляем конкретную анимацию


При создании анимации мы указывали ключ @«animateOpacity», по которому сможем получить доступ к ней. Чтобы удалить эту анимацию, делаем следующее:

Objective-c: removeAnimationForKey:@«animateOpacity»

Swift: removeAnimationForKey («animateOpacity»)

Удаляем все анимации


Чтобы удалить все анимации у layer, нужно отправить сообщение removeAllAnimations:

Objective-c: [theLayer removeAllAnimations];

Swift: theLayer.removeAllAnimations ()

Блоки анимации


Есть заранее заготовленные блоки, в которых можно проигрывать нужную анимацию (изменение прозрачности, позиции, размеров). Таких блоков два: animations и completion. Определим их назначение:
  • Блок animations — блок, в котором код будет выполняться анимационно
  • Блок completion — блок, в котором код выполняется после того, как выполнится блок animations

Блоки анимаций нужно использовать при смене одного значения слоя (объекта) на другое с последующим сохранением нового значения. В блоках мы можем менять как свойства слоя, так и свойства самого объекта.

Пример 1. У кнопки анимационно меняется альфа со скоростью три кадра в секунду

Objective-c:

[UIView animateWithDuration:3.0 animations:^{
       		 _theButton.alpha = 0.0;
	}];

Swift:

UIView.animateWithDuration(3.0, animations: { 
self.theButton.alpha = 0.0 
})

a18a035c23504b50868449b2bdd0f313.gif

Пример 2. Поменяем у кнопки позицию и высоту

Objective-c:

  CGRect frame = _animationButton.frame;
  frame.origin.y = 100;
  frame.size.height = 200;
  [UIView animateWithDuration:1.5
                          delay:0.0
                        options: UIViewAnimationOptionCurveEaseOut
                     animations:^{
                         _animationButton.frame = frame;
                     }
                     completion:^(BOOL finished){
                         NSLog(@"Done!");
                     }];

Swift:

var frame = self.animationButton.frame;
frame.origin.y = 100;
frame.size.height = 200;
        
UIView.animate(withDuration: 1.5, delay: 0.0, options: .curveEaseOut, animations: {
self.animationButton.frame = frame;
}) { (true) in
print("Done")
}

9f54ff1f853c4616894fc13ab68c5e39.gif

Здесь первый параметр — скорость, с которой будет воспроизводиться анимация;
второй параметр — задержка;
третий параметр — опции, то есть в каком виде будет проигрываться анимация.

Опции проигрывания анимации:

UIViewAnimationCurveLinear — анимация выполняется на постоянной скорости в течение заданного времени;
UIViewAnimationCurveEaseOut — анимация начинается быстро и замедляется ближе к концу;
UIViewAnimationCurveEaseIn — анимация начинается медленно и ускоряется ближе к концу;
UIViewAnimationCurveEaseInOut — анимация начинается медленно, ускоряется и снова замедляется.

Есть блоки анимаций, в которых осуществляются анимационные переходы между UIView или добавление элементов на UIView, как в примере ниже. В них используется блок transitionWithView, нужный для анимационного перехода или представления UIView или унаследованных от UIView объектов.

Objective-c:

[UIView transitionWithView:self.view duration:1.5
                       options:UIViewAnimationOptionTransitionFlipFromBottom 
                    animations:^ {
                        [self.view addSubview:self.imageView];
                    }
                    completion:^(BOOL finished){
                        if (finished) {
                            // Successful
                        }
                        NSLog(@"Animations completed.");
                        // do something…
 }];

Swift:

UIView.transition(with: self.view, duration: 1.5, options: .transitionFlipFromBottom, animations: { 
            self.view.addSubview(self.imageView)
}, completion: nil)

Вот что в этом случае получится: В этом примере мы добавляем картинку на UIView с анимацией:

c0423221a11c46ba87f77c0c6b96868e.gif

Объект UIImageView отображает одно изображение или последовательность изображений в анимированном интерфейсе. Мы можем анимировать UIImageView без использования блоков и анимаций типа CABasicAnimation. У UIImageView есть свойства animationImages, animationDuration, animationRepeatCount, а это значит, что мы можем, передав в animationImages массив с картинками, которые нам нужно проиграть, стартануть анимацию UIImageView.

Objective-c:

_chicken = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"chicken_normal_fly_1.png"]];
[_chicken setCenter:CGPointMake(150.f, 400.f)];
NSMutableArray *flyAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 8; ++i) {
[flyAnimFrames addObject:[UIImage imageNamed:[NSString stringWithFormat:@"chicken_normal_fly_%d.png", i]]];
}
        
_chicken.animationImages = flyAnimFrames;
_chicken.animationDuration = 0.25f;
[_chicken startAnimating];

Swift:
var imgListArray :NSMutableArray = []
for countValue in 1...8 {
var strImageName : String = "chicken_normal_fly_\(countValue).png"
var image = UIImage(named:strImageName)
imgListArray.add(image as Any)
}

self.imageView.animationImages = imgListArray;
self.imageView.animationDuration = 0.25
self.imageView.startAnimating()

67077e2f0318441391f61abe71112f22.gif

Заключение


В этой статье я дал минимальный уровень знаний об iOS-анимации, с которым ваше приложение станет более красочным и надёжнее захватит внимание пользователя iPhone или iPad. Сейчас наша команда работает над проектом с более сложными анимациями, в которых играют значительную роль timing function и delay. Полученный опыт мы вновь воплотим в статью, поэтому оставайтесь на связи и спасибо за внимание. Надеюсь, материал оказался полезен.

Если у вас возникли вопросы, пожелания или замечания — добро пожаловать в комментарии.

Комментарии (0)

© Habrahabr.ru