Анимированная бесконечная прокрутка изображения в бесшовной петле

В настоящее время у меня есть изображение облаков размером 2048x435, которое прокручивается по ориентированному на ландшафт UIImageView 1024x435 с использованием CABasicAnimation. Изображение облака прокручивается, как и должно быть, однако у меня возникли некоторые проблемы при попытке получить дублированное изображение облаков для подключения к задней части текущего изображения облаков, чтобы между изображениями облаков не было зазора. Я боролся около дня, пытаясь найти решение, поэтому любая помощь будет принята с благодарностью. Мой текущий код:

разработка на Xcode 4.2 для iOS 5 с ARC без раскадровки ipad ландшафтной ориентации.

-(void)cloudScroll
{
    UIImage *cloudsImage = [UIImage imageNamed:@"TitleClouds.png"];
    CALayer *cloud = [CALayer layer];
    cloud.contents = (id)cloudsImage.CGImage;
    cloud.bounds = CGRectMake(0, 0, cloudsImage.size.width, cloudsImage.size.height);
    cloud.position = CGPointMake(self.view.bounds.size.width / 2, cloudsImage.size.height / 2);
    [cloudsImageView.layer addSublayer:cloud];

    CGPoint startPt = CGPointMake(self.view.bounds.size.width + cloud.bounds.size.width / 2, cloud.position.y);
    CGPoint endPt = CGPointMake(cloud.bounds.size.width / -2, cloud.position.y);
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    anim.fromValue = [NSValue valueWithCGPoint:startPt];
    anim.toValue = [NSValue valueWithCGPoint:endPt];
    anim.repeatCount = HUGE_VALF;
    anim.duration = 60.0;
    [cloud addAnimation:anim forKey:@"position"];
}

-(void)viewDidLoad
{
    [self cloudScroll];
    [super viewDidLoad];
}

3 ответа

Решение

Вы говорите, что ваше изображение имеет ширину 2048, а ваше изображение - 1024. Я не знаю, означает ли это, что вы дублировали содержимое изображения шириной 1024, чтобы сделать изображение шириной 2048.

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

@implementation ViewController {
    CALayer *cloudLayer;
    CABasicAnimation *cloudLayerAnimation;
}

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

-(void)cloudScroll {
    UIImage *cloudsImage = [UIImage imageNamed:@"TitleClouds.png"];
    UIColor *cloudPattern = [UIColor colorWithPatternImage:cloudsImage];
    cloudLayer = [CALayer layer];
    cloudLayer.backgroundColor = cloudPattern.CGColor;

Однако система координат CALayer помещает начало координат внизу слева, а не вверху слева, причем ось Y увеличивается вверх. Это означает, что рисунок будет нарисован в обратном порядке. Мы можем исправить это, перевернув ось Y:

    cloudLayer.transform = CATransform3DMakeScale(1, -1, 1);

По умолчанию точка привязки слоя находится в его центре. Это означает, что установка положения слоя устанавливает положение его центра. Будет проще расположить слой, установив положение его верхнего левого угла. Мы можем сделать это, переместив точку привязки в верхний левый угол:

    cloudLayer.anchorPoint = CGPointMake(0, 1);

Ширина слоя должна быть шириной изображения плюс ширина содержащего вида. Таким образом, когда мы прокручиваем слой так, что появляется правый край изображения, справа от первой копии будет нарисована еще одна копия изображения.

    CGSize viewSize = self.cloudsImageView.bounds.size;
    cloudLayer.frame = CGRectMake(0, 0, cloudsImage.size.width + viewSize.width, viewSize.height);

Теперь мы готовы добавить слой к виду:

    [self.cloudsImageView.layer addSublayer:cloudLayer];

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

    CGPoint startPoint = CGPointZero;

и мы хотим, чтобы верхний левый угол слоя переместился влево на ширину изображения:

    CGPoint endPoint = CGPointMake(-cloudsImage.size.width, 0);

Остальные настройки анимации совпадают с вашим кодом. Я изменил продолжительность до 3 секунд для тестирования:

    cloudLayerAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    cloudLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    cloudLayerAnimation.fromValue = [NSValue valueWithCGPoint:startPoint];
    cloudLayerAnimation.toValue = [NSValue valueWithCGPoint:endPoint];
    cloudLayerAnimation.repeatCount = HUGE_VALF;
    cloudLayerAnimation.duration = 3.0;

Мы вызовем другой метод, чтобы фактически прикрепить анимацию к слою:

    [self applyCloudLayerAnimation];
}

Вот метод, который применяет анимацию:

- (void)applyCloudLayerAnimation {
    [cloudLayer addAnimation:cloudLayerAnimation forKey:@"position"];
}

Когда приложение переходит в фоновый режим (поскольку пользователь переключился на другое приложение), система удаляет анимацию из облачного слоя. Таким образом, нам нужно снова прикрепить его, когда мы снова выйдем на передний план. Вот почему у нас есть applyCloudLayerAnimation метод. Нам нужно вызвать этот метод, когда приложение выходит на передний план.

В viewDidAppear:, мы можем начать наблюдать уведомление о том, что приложение вышло на передний план:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

Нам нужно прекратить наблюдать уведомление, когда наше представление исчезает или когда контроллер представления освобождается:

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}

Когда контроллер представления фактически получает уведомление, нам нужно снова применить анимацию:

- (void)applicationWillEnterForeground:(NSNotification *)note {
    [self applyCloudLayerAnimation];
}

Вот весь код для легкого копирования и вставки:

- (void)viewDidLoad {
    [self cloudScroll];
    [super viewDidLoad];
}

-(void)cloudScroll {
    UIImage *cloudsImage = [UIImage imageNamed:@"TitleClouds.png"];
    UIColor *cloudPattern = [UIColor colorWithPatternImage:cloudsImage];
    cloudLayer = [CALayer layer];
    cloudLayer.backgroundColor = cloudPattern.CGColor;

    cloudLayer.transform = CATransform3DMakeScale(1, -1, 1);

    cloudLayer.anchorPoint = CGPointMake(0, 1);

    CGSize viewSize = self.cloudsImageView.bounds.size;
    cloudLayer.frame = CGRectMake(0, 0, cloudsImage.size.width + viewSize.width, viewSize.height);

    [self.cloudsImageView.layer addSublayer:cloudLayer];

    CGPoint startPoint = CGPointZero;
    CGPoint endPoint = CGPointMake(-cloudsImage.size.width, 0);
    cloudLayerAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    cloudLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    cloudLayerAnimation.fromValue = [NSValue valueWithCGPoint:startPoint];
    cloudLayerAnimation.toValue = [NSValue valueWithCGPoint:endPoint];
    cloudLayerAnimation.repeatCount = HUGE_VALF;
    cloudLayerAnimation.duration = 3.0;
    [self applyCloudLayerAnimation];
}

- (void)applyCloudLayerAnimation {
    [cloudLayer addAnimation:cloudLayerAnimation forKey:@"position"];
}

- (void)viewDidUnload {
    [self setCloudsImageView:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)applicationWillEnterForeground:(NSNotification *)note {
    [self applyCloudLayerAnimation];
}

Роб, ты рок-брат. Я искал способ сделать то же самое, но по вертикали. После прочтения вашего ответа выше у меня не было проблем с настройкой в ​​соответствии с моими потребностями Для тех, кто может оказаться здесь на охоте за вертикальным решением, это то, чем я закончил, смоделированный после вышеописанного:

- (IBAction)animateBackground6
{
    UIImage *backgroundImage = [UIImage imageNamed:@"space.png"];
    UIColor *backgroundPattern = [UIColor colorWithPatternImage:backgroundImage];

    CALayer *background = [CALayer layer];
    background.backgroundColor = backgroundPattern.CGColor;
    background.transform = CATransform3DMakeScale(1, -1, 1);
    background.anchorPoint = CGPointMake(0, 1);

    CGSize viewSize = self.backgroundImageView.bounds.size;
    background.frame = CGRectMake(0, 0, viewSize.width,  backgroundImage.size.height +   viewSize.height);
    [self.backgroundImageView.layer addSublayer:background];

    CGPoint startPoint = CGPointZero;
    CGPoint endPoint = CGPointMake(0, -backgroundImage.size.height);

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.fromValue = [NSValue valueWithCGPoint:endPoint];
    animation.toValue = [NSValue valueWithCGPoint:startPoint];
    animation.repeatCount = HUGE_VALF;
    animation.duration = 5.0;
    [background addAnimation:animation forKey:@"position"];
}

Оригинальное решение с возможностью вертикальной и горизонтальной прокрутки

//
//  originally found here: http://stackru.com/questions/8790079/animate-infinite-scrolling-of-an-image-in-a-seamless-loop
//

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface TiledCloundScrollViewController : UIViewController {
    CALayer *cloudLayer;
    CABasicAnimation *cloudLayerAnimation;

    UIImage *cloudsImage;
    BOOL verticalScroll;
    CFTimeInterval animationDuration;
}

- (id) initWithImage:(UIImage*)cloudsImage verticalScroll:(BOOL)verticalScroll animationDuration:(CFTimeInterval)animationDuration;

@end



#import "TiledCloundScrollViewController.h"

@interface TiledCloundScrollViewController ()
@end

@implementation TiledCloundScrollViewController

- (id) init {
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

- (id) initWithImage:(UIImage*)image verticalScroll:(BOOL)vScroll animationDuration:(CFTimeInterval)duration {
    self = [super init];
    if (self ) {
        cloudsImage = image;
        verticalScroll = vScroll;
        animationDuration = duration;
    }
    return self;
}

- (void) viewDidLoad {
    [super viewDidLoad];

    self.view.clipsToBounds = YES;
    const CGSize viewSize = self.view.bounds.size;
    const CGSize imageSize = cloudsImage.size;

    UIColor *cloudPattern = [UIColor colorWithPatternImage:cloudsImage];
    cloudLayer = [CALayer layer];
    cloudLayer.backgroundColor = cloudPattern.CGColor;
    cloudLayer.transform = CATransform3DMakeScale(1, -1, 1);
    cloudLayer.anchorPoint = CGPointMake(0, 1);
    [self.view.layer addSublayer:cloudLayer];

    CGPoint startPoint = CGPointZero;
    CGPoint endPoint;
    if (verticalScroll) {
        endPoint = CGPointMake(0, -imageSize.height);
        cloudLayer.frame = CGRectMake(0, 0, viewSize.width, viewSize.height + imageSize.height);
    } else {
        endPoint = CGPointMake(-imageSize.width, 0);
        cloudLayer.frame = CGRectMake(0, 0, viewSize.width + imageSize.width, viewSize.height);
    }

    cloudLayerAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    cloudLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    cloudLayerAnimation.fromValue = [NSValue valueWithCGPoint:startPoint];
    cloudLayerAnimation.toValue = [NSValue valueWithCGPoint:endPoint];
    cloudLayerAnimation.repeatCount = HUGE_VALF;
    cloudLayerAnimation.duration = animationDuration;
    [self applyCloudLayerAnimation];
}

- (void) viewDidUnload {
    cloudLayer = nil;
    cloudLayerAnimation = nil;
    [super viewDidUnload];
}

- (void) applyCloudLayerAnimation {
    [cloudLayer addAnimation:cloudLayerAnimation forKey:@"position"];
}

- (void)applicationWillEnterForeground:(NSNotification *)note {
    [self applyCloudLayerAnimation];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}


@end
Другие вопросы по тегам