The CALayer class has many specialized subclasses, such as CAScrollLayer , CAEmitterLayer , CAGradientLayer , CAReplicatorLayer , CAEAGLLayer , CAShapeLayer , CATextLayer , CATransformLayer . Each of these subclasses has been designed for a very specific task. In this tutorial, I will show you how to use a CAScrollLayer .

A CAScrollLayer can be used to display a portion of its sublayers and to animate them. A CAScrollLayer has only a property ( scrollMode ) and two instance methods ( scrollToPoint(_:) and scrollToRect(_:) ). Although this class provides only a property and two methods, it is quite powerful, since it allows you to create incredible visual effects and animations that would be very complex to do with a CALayer object.

Scrolling a single layer

Before starting, please, download the image assets for this tutorial from here.

Launch Xcode and create a new Single-View Application project. Name it ScrollLayer. Open the ViewController.swift and add the following properties to the view controller:

1 var translation : CGFloat = 0.0

We will use later this property to track the amount of points the scroll layer scrolls during the animation. In the viewDidLoad , add the following code:

1 2 3 4 5 6 7 8 9 10 11 12 override func viewDidLoad ( ) { super . viewDidLoad ( ) if let img = UIImage ( named : "sky" ) { // 1 let imageSize = img . size let layer = CALayer ( ) // 2 layer . bounds = CGRect ( x : 0.0 , y : 0.0 , width : imageSize . width , height : imageSize . height ) // 3 layer . position = CGPoint ( x : imageSize . width / 2 , y : imageSize . height / 2 ) // 4 layer . contents = img . CGImage // 5 view . layer . addSublayer ( scrollLayer ) // 6 scrollLayer . addSublayer ( layer ) // 7 } }

In line 1, I load the sky.png image. In line 2, I create a CALayer instance. Then, I set its bounds to the image size (line 3). I set its position (line 4). Finally, I add the loaded image to the layer’s contents (line 5).

In line 6, I add the scroll layer to the layer tree and then, I add the initial layer containing the image to the scroll layer (line 7). Then, add the following property:

1 2 3 4 5 6 7 8 9 lazy var scrollLayer : CAScrollLayer = { let scrollLayer = CAScrollLayer ( ) // 8 scrollLayer . bounds = CGRect ( x : 0.0 , y : 0.0 , width : 150 . 0 , height : 300 . 0 ) // 9 scrollLayer . position = CGPoint ( x : self . view . bounds . size . width / 2 , y : self . view . bounds . size . height / 2 ) // 10 scrollLayer . borderColor = UIColor . blackColor ( ) . CGColor // 11 scrollLayer . borderWidth = 5.0 // 12 scrollLayer . scrollMode = kCAScrollHorizontally // 13 return scrollLayer } ( )

In line 8, I create a CAScrollLayer and set its bounds and its position (lines 9 and 10). Then, I change CAScrollLayer ’s borderColor (line 11) and borderWidth (line 12). In line 13, I set the scroll mode to horizontal, since I want my image to scroll only horizontally for this particular example. You can choose a vertical mode or both or also none, if you don’t want to apply any scroll.

Build and run. As you can see, only a portion of the image is rendered on screen.

So far, we didn’t do anything interesting. Now, let’s add an animation to our scene. I want to scroll the layer at regular intervals of time. To achieve this, I am going to use a CADisplayLink . A CADisplayLink is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.

Let’s add the following property to the view controller:

1 2 3 4 5 lazy var displayLink : CADisplayLink = { let displayLink = CADisplayLink ( target : self , selector : "scrollLayerScroll" ) displayLink . frameInterval = 10 return displayLink } ( )

In the closure assigned to this property, I create a display link and then, I set the frame interval to 10. Each frame should be executed at ~16.6 ms (60 frame/s is the screen frame rate of any iOS device). So, here I am asking the display link to fire the method scrollLayerScroll every ~166 ms. Finally add the following line to the viewDidLoad . Here, I register the display link with the application run loop:

1 displayLink . addToRunLoop ( NSRunLoop . currentRunLoop ( ) , forMode : NSRunLoopCommonModes )

The method fired by the display link is the following:

1 2 3 4 5 6 7 8 func scrollLayerScroll ( ) { var newPoint = CGPoint ( x : translation , y : 0.0 ) scrollLayer . scrollPoint ( newPoint ) translation += 10 . 0 if translation > 1600 . 0 { stopDisplayLink ( ) } }

In this method, I compute the new amount of translation and I ask the scroll layer to scroll that amount. If the total scrolling reaches 1600 screen points, I invalidate the display link:

1 2 3 func stopDisplayLink ( ) { displayLink . invalidate ( ) }

Build and run. You should see the following animation:

Scrolling 2 layers

Let’s introduce a few other elements to make the previous example more interesting. We will be adding a layer to the previous layer that will scroll vertically (oscillating up and down) while our previous layer continues to scroll horizontally. So, add the following property to the view controller:

1 var moveUp : Bool = true // 14

This property will act as a switch, helping with the up and down scrolling. Then, replace line 9 from the previous example with the following:

1 scrollLayer . bounds = CGRect ( x : 0.0 , y : 0.0 , width : self . view . bounds . size . width , height : self . view . bounds . size . height ) // 9

Delete lines 11 and 12 to remove the black line bordering from the scroll layer. Then, add the following code in the viewDidLoad() to create a new CALayer and a new CAScrollLayer , to accommodate the second layer that I need for this example.

1 2 3 4 5 6 7 8 9 if let img = UIImage ( named : "sky" ) , let imgTop = UIImage ( named : "train" ) { // 15 let imageSizeTop = imgTop . size let layerTop = CALayer ( ) // 16 layerTop . bounds = CGRect ( x : 0.0 , y : 0.0 , width : imageSizeTop . width , height : imageSizeTop . height ) // 17 layerTop . position = CGPoint ( x : self . view . bounds . size . width / 2 , y : ( self . view . bounds . size . height - imageSizeTop . height / 2 ) ) // 18 layerTop . contents = imgTop . CGImage // 19 view . layer . addSublayer ( scrollLayerTop ) // 20 scrollLayerTop . addSublayer ( layerTop ) // 21 }

In line 15, I load the train.png image (replace line 1 from example 1 with line 15). In line 16, I create a CALayer instance. Then, I set its bounds to the image size (line 17). I set its position (line 18). Finally, I add the loaded image to the layer’s contents (line 19).

In line 20, I add the new scroll layer to the layer tree and finally, I add the new layer containing the train image to the new scroll layer (line 21). Let’s add the following property to the view controller:

1 2 3 4 5 6 7 lazy var scrollLayerTop : CAScrollLayer = { let scrollLayerTop = CAScrollLayer ( ) // 22 scrollLayerTop . bounds = CGRect ( x : 0.0 , y : 0.0 , width : self . view . bounds . size . width , height : self . view . bounds . size . height ) // 23 scrollLayerTop . position = CGPoint ( x : self . view . bounds . size . width / 2 , y : self . view . bounds . size . height / 2 ) // 24 scrollLayerTop . scrollMode = kCAScrollVertically // 25 return scrollLayerTop } ( )

In line 22, I create a CAScrollLayer and set its bounds and its position (lines 23 and 24). In line 25, I set the scroll mode to vertical, since I want my image to scroll only vertically for this particular example.

Finally, add the following code to the scrollLayerScroll() method to make the train move up and down to simulate the train is moving:

1 2 3 4 5 6 7 if ( moveUp != false ) { scrollLayerTop . scrollToPoint ( CGPoint ( x : 0.0 , y : 10 . 0 ) ) moveUp = false } else { scrollLayerTop . scrollToPoint ( CGPoint ( x : 0.0 , y : - 10 . 0 ) ) moveUp = true }

Now, build and run and you should see this animation:

Scrolling multiple layers

I decided to go a bit further and add a CAEmitterLayer to simulate the steam produced by the train when running. I added this new layer to the scroll layer scrollLayerTop . The CAEmitterLayer provides a particle emitter system in Core Animation. You can learn more about it here.

Let’s see how to do that. Add the following code to the viewDidLoad from example 2:

1 2 3 let emitterLayer = CAEmitterLayer ( ) // 26 emitterLayer . emitterPosition = CGPoint ( x : ( layerTop . bounds . width / 2 ) + 40 . 0 , y : - 10 . 0 ) // 27 emitterLayer . emitterShape = kCAEmitterLayerPoint // 28

Line 26 creates a particle emitter system, line 27 specifies the position where the layer will be. In this case, I want it to be positioned at the top of the train chimney. Then, line 28 specifies the emitter shape. If you check the CAEmitterLayer in the documentation you will learn about different geometries you can set for the emitter layer. In our example, I want all the particles to be emitted from a single point so I chose kCAEmitterLayerPoint .

Now, add the following method:

1 2 3 4 5 6 7 8 9 10 11 12 lazy var emitterCell : CAEmitterCell = { let emitterCell : CAEmitterCell = CAEmitterCell ( ) // 29 emitterCell . scale = 0.1 // 30 emitterCell . scaleRange = 0.3 // 31 emitterCell . lifetime = 10 . 0 // 32 emitterCell . birthRate = 2 // 33 emitterCell . velocity = 20 // 34 emitterCell . velocityRange = 50 // 35 emitterCell . xAcceleration = - 250 // 36 emitterCell . yAcceleration = - 50 // 37 return emitterCell } ( )

Line 29 creates the emitter cell. Line 30 specifies the scale factor applied to the cell and line 31 the range over which the scale value can vary during the animation (the system controls that). From line 32 to 37 I set different emitter cell temporal attributes like the lifetime of the cell (in seconds) in line 32, the birth rate (the number of emitted objects created every second) in line 33, the initial velocity of the cell (line 34), the velocity range (the amount by which the velocity of a cell can vary) in line 35 and the x and y component of the acceleration vector applied to the cell (lines 36 and 37).

Now, back to the viewDidLoad , add the following lines of code.

1 2 3 4 5 if let img = UIImage ( named : "sky" ) , let imgTop = UIImage ( named : "train" ) , let imgParticle = UIImage ( named : "steam" ) { // 38 emitterCell . contents = imgParticle . CGImage // 39 emitterLayer . emitterCells = [ emitterCell ] // 40 layerTop . addSublayer ( emitterLayer ) // 41 }

Let’s load the steam.png image (line 38 replaces line 15 from example 2). Line 39 provides the cell’s contents. Add the emitter cell to the emitter layer (line 40). The last thing we need to do is to add the emitter layer to our layerTop layer (layer containing the train image), so that all the work we’ve done is actually visible (line 41).

Build and run. Your application will look like this:

Conclusions

I hope you enjoyed this tutorial on CAScrollLayer . As you could learn here, you can combine a scroll layer with other core animation layers and create spectacular visual effects. Check out all our tutorials about core animation here.

Keep innovating, swiftly!

Eva

Eva Diaz-Santana (@evdiasan) is cofounder of InvasiveCode. She develops iOS applications and teaches iOS development since 2008. She also worked at Apple as Cocoa Architect and UX designer.

(Visited 933 times, 1 visits today)