Currently I have an image of clouds of 2048x435 that scrolls across a Landscaped oriented UIImageView of 1024x435 using CABasicAnimation. The cloud image scrolls as it should, however I am having some trouble trying to to get a duplicate clouds image to connect to the back of the the current clouds image so that there is no gap between the clouds images. I've been struggling for about a day trying to find a solution, so any help would be greatly appreciated. My current code:
developing on Xcode 4.2 for iOS 5 with ARC non-storyboard ipad landscaped orientation.
-(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]; }
What Is Infinite Scroll? A web design technique where, as the user scrolls down a page, more content automatically and continuously loads at the bottom, eliminating the user's need to click to the next page. The idea behind infinite scroll is that it allows people to enjoy a frictionless browsing experience.
Infinite scroll is a user experience (UX) practice in which content continually loads when a user reaches the bottom of the page. This creates the experience of an endless flow of information on a single, seemingly never-ending page.
The idea here is to create the appearance of a slideshow without the carousel. In other words, we're making a series of images the slide from left to right and repeat once the end of the images has been reached.
You say that your image is 2048 wide and your view is 1024 wide. I don't know if this means you have duplicated the contents of a 1024-wide image to make a 2048-wide image.
Anyway, here's what I suggest. We'll need to store the cloud layer and its animation in instance variables:
@implementation ViewController { CALayer *cloudLayer; CABasicAnimation *cloudLayerAnimation; }
Instead of setting the cloud layer's content to the cloud image, we set its background color to a pattern color created from the image. That way, we can set the layer's bounds to whatever we want and the image will be tiled to fill the bounds:
-(void)cloudScroll { UIImage *cloudsImage = [UIImage imageNamed:@"TitleClouds.png"]; UIColor *cloudPattern = [UIColor colorWithPatternImage:cloudsImage]; cloudLayer = [CALayer layer]; cloudLayer.backgroundColor = cloudPattern.CGColor;
However, a CALayer's coordinate system puts the origin at the lower left instead of the upper left, with the Y axis increasing up. This means that the pattern will be drawn upside-down. We can fix that by flipping the Y axis:
cloudLayer.transform = CATransform3DMakeScale(1, -1, 1);
By default, a layer's anchor point is at its center. This means that setting the layer's position sets the position of its center. It will be easier to position the layer by setting the position of its upper-left corner. We can do that by moving its anchor point to its upper-left corner:
cloudLayer.anchorPoint = CGPointMake(0, 1);
The width of the layer needs to be the width of the image plus the width of the containing view. That way, as we scroll the layer so that the right edge of the image comes into view, another copy of the image will be drawn to the right of the first copy.
CGSize viewSize = self.cloudsImageView.bounds.size; cloudLayer.frame = CGRectMake(0, 0, cloudsImage.size.width + viewSize.width, viewSize.height);
Now we're ready to add the layer to the view:
[self.cloudsImageView.layer addSublayer:cloudLayer];
Now let's set up the animation. Remember that we changed the layer's anchor point, so we can control its position by setting the position of its upper-left corner. We want the layer's upper-left corner to start at the view's upper-left corner:
CGPoint startPoint = CGPointZero;
and we want the layer's upper-left corner to move left by the width of the image:
CGPoint endPoint = CGPointMake(-cloudsImage.size.width, 0);
The rest of the animation setup is the same as your code. I changed the duration to 3 seconds for testing:
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;
We'll call another method to actually attach the animation to the layer:
[self applyCloudLayerAnimation]; }
Here's the method that applies the animation:
- (void)applyCloudLayerAnimation { [cloudLayer addAnimation:cloudLayerAnimation forKey:@"position"]; }
When the application enters the background (because the user switched to another app), the system removes the animation from the cloud layer. So we need to reattach it when we enter the foreground again. That's why we have the applyCloudLayerAnimation
method. We need to call that method when the app enters the foreground.
In viewDidAppear:
, we can start observing the notification that tells us the app has entered the foreground:
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; }
We need to stop observing the notification when our view disappears, or when the view controller is deallocated:
- (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]; }
When the view controller actually receives the notification, we need to apply the animation again:
- (void)applicationWillEnterForeground:(NSNotification *)note { [self applyCloudLayerAnimation]; }
Here's all the code together for easy copy and paste:
- (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]; }
Rob, you rock brother. I was looking for a way to do the same thing but vertically. After reading your answer above I had no problem adjusting it to suit my needs. For anyone that may end up here on their hunt for a vertical solution this is what I ended up with, modeled after the above:
- (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"]; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With