У меня есть массив изображений, которые я хочу оживить, играя эти изображения один за другим в последовательности. Я хочу повторить весь цикл несколько раз. Я разрабатываю игру для iPad. Предложите мне способ достижения этой функциональности в Objective-C с помощью рамки Cocoa.
Анимация с использованием массива изображений в последовательности
Ответ 1
См. свойство animationImages
UIImageView
. Его трудно сказать, соответствует ли оно вашим потребностям, поскольку вы не даете нам подробностей, но это хорошее начало.
Ответ 2
NSArray *animationArray = [NSArray arrayWithObjects:
[UIImage imageNamed:@"images.jpg"],
[UIImage imageNamed:@"images1.jpg"],
[UIImage imageNamed:@"images5.jpg"],
[UIImage imageNamed:@"index3.jpg"],
nil];
UIImageView *animationView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0,320, 460)];
animationView.backgroundColor = [UIColor purpleColor];
animationView.animationImages = animationArray;
animationView.animationDuration = 1.5;
animationView.animationRepeatCount = 0;
[animationView startAnimating];
[self.view addSubview:animationView];
[animationView release];
добавьте свои собственные изображения в массив .repeat Count 0 означает бесконечный цикл. Вы также можете указать свой собственный номер.
Ответ 3
Существует как минимум 3 способа анимации массива изображений с помощью UIImageView
. Я добавляю 3 ссылки для загрузки образца кода для 3 возможностей.
Первый - это тот, который все знают. Другие менее известны.
- UIImageView.animationImages
Пример ссылки
Проблема этого заключается в том, что делегату не нужно сообщать нам, в какой момент анимация завершена. Таким образом, у нас могут быть проблемы, если мы хотим отобразить что-то после анимации.
Таким же образом невозможно автоматически сохранить последнее изображение из анимации в UIImageView
. Если мы объединим обе проблемы, у нас может быть пробел в конце анимации, если мы хотим сохранить последний кадр на экране.
self.imageView.animationImages = self.imagesArray; // the array with the images
self.imageView.animationDuration = kAnimationDuration; // static const with your value
self.imageView.animationRepeatCount = 1;
[self.imageView startAnimating];
- CAKeyframeAnimation
Пример ссылки
Этот способ анимации работает через CAAnimation
. У него есть удобный делегат для использования, и мы можем знать, когда закончится анимация.
Это, вероятно, лучший способ анимации массива изображений.
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
keyframeAnimation.values = self.imagesArray;
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = kAnimationDuration; // static const with your value
keyframeAnimation.delegate = self;
// keyframeAnimation.removedOnCompletion = YES;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:@"girlAnimation"];
}
Делегат:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag)
{
// your code
}
}
- CADisplayLink
Пример ссылки
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
Этот способ сделать это действительно интересен и открывает множество возможностей манипулировать тем, что мы показываем на экране.
GetLink getter:
- (CADisplayLink *)displayLink
{
if (!_displayLink)
{
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(linkProgress)];
}
return _displayLink;
}
Методы:
- (void)animateImages
{
self.displayLink.frameInterval = 5;
self.frameNumber = 0;
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
- (void)linkProgress
{
if (self.frameNumber > 16)
{
[self.displayLink invalidate];
self.displayLink = nil;
self.animationImageView.image = [UIImage imageNamed:@"lastImageName"];
self.imagesArray = nil;
return;
}
self.animationImageView.image = self.imagesArray[self.frameNumber++];
self.frameNumber++;
}
ОБЩАЯ ПРОБЛЕМА:
Несмотря на то, что у нас есть три возможности, если ваша анимация имеет много больших изображений, подумайте об использовании видео. Использование памяти значительно уменьшится.
Общая проблема, с которой вам придется столкнуться, заключается в момент выделения изображений.
Если вы используете [UIImage imageNamed: @ "imageName" ], у вас возникнут проблемы.
От Apple:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
Итак, imageNamed:
хранит изображение в приватном кеше.
- Первая проблема заключается в том, что вы не можете контролировать размер кеша.
- Вторая проблема заключается в том, что кеш не очищался вовремя, и если вы выделяете много изображений с помощью imageNamed:
, ваше приложение, вероятно, выйдет из строя.
РЕШЕНИЕ:
Выделите изображения непосредственно из Bundle
:
NSString *imageName = [NSString stringWithFormat:@"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName
// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];
Маленькая проблема:
Изображения в Images.xcassets
никогда не выделяются. Итак, переместите изображения вне Images.xcassets
, чтобы выделить их непосредственно из Bundle.
Ответ 4
Лучшее решение для меня использует CADisplayLink. UIImageView не имеет блока завершения, и вы не можете поймать шаги анимации. В моей задаче я должен постепенно менять фон представления с помощью секвенсора изображений. Таким образом CADisplayLink позволяет обрабатывать шаги и завершать анимацию. Если мы говорим об использовании памяти, я думаю, что лучшее решение загружает изображения из набора и удаления массива после завершения
ImageSequencer.h
typedef void (^Block)(void);
@protocol ImageSequencerDelegate;
@interface QSImageSequencer : UIImageView
@property (nonatomic, weak) id <ImageSequencerDelegate> delegate;
- (void)startAnimatingWithCompletionBlock:(Block)block;
@end
@protocol ImageSequencerDelegate <NSObject>
@optional
- (void)animationDidStart;
- (void)animationDidStop;
- (void)didChangeImage:(UIImage *)image;
@end
ImageSequencer.m
- (instancetype)init {
if (self = [super init]) {
_imagesArray = [NSMutableArray array];
self.image = [self.imagesArray firstObject];
}
return self;
}
#pragma mark - Animation
- (void)startAnimating {
[self startAnimatingWithCompletionBlock:nil];
}
- (void)startAnimatingWithCompletionBlock:(Block)block {
self.frameNumber = 0;
[self setSuccessBlock:block];
self.displayLink.frameInterval = 5;
if (self.delegate && [self.delegate respondsToSelector:@selector(animationDidStart)]) {
[self.delegate animationDidStart];
}
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
-(void)stopAnimating {
self.image = [self.imagesArray lastObject];
[self.displayLink invalidate];
[self setDisplayLink:nil];
Block block_ = [self successBlock];
if (block_) {
block_();
}
if (self.delegate && [self.delegate respondsToSelector:@selector(animationDidStop)]) {
[self.delegate animationDidStop];
}
[self.imagesArray removeAllObjects];
}
- (void)animationProgress {
if (self.frameNumber >= self.imagesArray.count) {
[self stopAnimating];
return;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(didChangeImage:)]) {
[self.delegate didChangeImage:self.imagesArray[self.frameNumber]];
}
self.image = self.imagesArray[self.frameNumber];
self.frameNumber++;
}
#pragma mark - Getters / Setters
- (CADisplayLink *)displayLink {
if (!_displayLink){
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationProgress)];
}
return _displayLink;
}
- (NSMutableArray<UIImage *> *)imagesArray {
if (_imagesArray.count == 0) {
// get images from bundle and set to array
}
return _imagesArray;
}
@end
Ответ 5
Я добавил расширение для быстрого
extension UIImageView {
func animate(images: [UIImage], index: Int = 0, completionHandler: (() -> Void)?) {
UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.image = images[index]
}, completion: { value in
let idx = index == images.count-1 ? 0 : index+1
if idx == 0 {
completionHandler!()
} else {
self.animate(images: images, index: idx, completionHandler: completionHandler)
}
})
}
}