Chapter 9
Parallax Testing (Optional)
This optional chapter will walk you through how I originally tested the background.
We’re mostly going to be working in the StarsBackground.swift
file. However, we’ll also use Stars.swift
for testing our class.
There are 4 layers of background stars, each of whose content is on an infinite scrollview. To populate these layers we need to know 4 things:
We can construct a layer once we have these 4 elements. After we’ve done so and added that layer to the app, we won’t need to access it or its elements… So, we can make our life a little easier by building everything into a single initializer.
Open StarsBackground.swift
and you’ll see the following shell of an init:
1
2
3
convenience public init(frame: CGRect, imageName: String, starCount: Int, speed: CGFloat) {
self.init(frame: frame)
}
This method takes the 4 variables necessary to build the layer. So, all we need to do from here on out is fill it in.
There’s a liiiiiiittle design spec that we want to consider before we do anything… The idea is that there should be a LOT of space between the constellations so that the user has to scroll a ways to get from one to the next.1 To do this we need a variable that we can use to calculate content sizes.
Open the Stars.swift
file and add the following above / outside of the class itself:
1
var gapBetweenSigns : CGFloat = 10.0
The first step is to calculate the content size for the layer. This is an important calculation because we want to optimize the adding of assets, and since this layer will move at a slower speed than other layers its contentSize should be smaller as well.
To do this we need 3 things:
singleContentSize
.This is quite simple. Add the following to your init method:
1
let adjustedFrameSize = frame.width * speed
If the speed is less than 1.0 we want the content size to be adjusted equally.
The “space” between sections of the layer is defined by the frame size and the number of gaps (e.g. frame widths) that occur at the highest level between astrological sign constellations. The calculation is straightforward, add:
let singleSignContentSize = adjustedFrameSize * gapBetweenSigns
To get the number of signs in the app we’ll access the sign provider. Add the following:
1
let count = CGFloat(AstrologicalSignProvider.sharedInstance.order.count)
With the three variables we’ve just made, we can now calculate the actual content size for the layer. Add the following:
1
contentSize = CGSizeMake(singleSignContentSize * count + frame.width, 1.0)
We add a frame.width
to the content size because we need the layer to keep moving past its end point, and since we’re working within the space of the main app’s frame, this is the amount of space we need to add to our size.
We are going to iterate through our entire layer one frame at a time, dropping stars randomly in each frame and moving on until we’re done. And, only in the first frame will we make a copy of each star and add that to the end of our content so that when we scroll past the 12th we’ll end up with that nice hidden snapping effect that makes the InfiniteScrollview
so nice.
We add stars via a couple of embedded loops, for each “frame” we want to run the same code a predefined number of times.
Start by adding the following shell of a loop after you’ve set the contentSize
:
1
2
3
for currentFrame in 0..<Int(count) {
//this will iterate 12 times
}
Then, add another for loop inside that one, like so:
1
2
3
4
5
for currentFrame in 0..<Int(count) {
for _ in 0..<starCount {
//add a star
}
}
If we want 20 stars per frame, we’re going to get 12 (i.e. # signs) * 20 = 240 stars per layer.
We need to calculate an offset for the current frame, like so:
1
2
3
4
5
6
for currentFrame in 0..<Int(signCount) {
let dx = Double(singleSignContentSize) * Double(currentFrame)
for _ in 0..<starCount {
//add a star
}
}
We’re starting to cast to Double from Int and CGFloat. Unfortunately, this is a bit of an annoying process with Swift because it doesn’t implicitly change between different value types… So, you can’t add an Int to a Double. And, we’re working with both a UIKit and C4 object which use CGFloat and Double (respectively).
So, we know where to offset our images for each frame. Now, we need to calculate a random point within that frame.
Add the following to the inner loop:
1
2
3
let x = dx + random01() * Double(singleSignContentSize)
let y = random01() * Double(frame.size.height)
var pt = Point(x, y)
And then, add an image at that point:
1
2
3
let img = Image(imageName)!
img.center = pt
add(img)
Finally, if the currentFrame
is the first frame then we want to make a copy of all the stars and add that to the 13th frame of the layer (i.e. the one that overlaps).
After adding the image, insert this bit of code:
1
2
3
4
5
6
if currentFrame == 0 {
pt.x += Double(signCount * singleSignContentSize)
let img = Image(imageName)!
img.center = pt
add(img)
}
Done. That’s it. Finito. We’re going to be able to use this simple class to create 4 of the 8 layers that are going in our app’s background.
Grab this StarsBackground.swift gist to see the final state of this class:
To test this class, add the following to your project’s WorkSpace:
1
2
3
4
override func setup() {
canvas.backgroundColor = COSMOSbkgd
canvas.add(StarsBackground(frame: view.frame, imageName: "6smallStar", starCount: 20, speed: 1.0))
}
You’ll notice that if you drag right automatically that the scrollview doesn’t snap.
Perrrrrfect.
There are a few reasons for this… One, its a bit weird to have all the stars side by side because we’re going to apply some nice scaling that would otherwise overlap them. Two, if the gaps were really short then there’d be no need for a menu to select and animate between the layers. Three, the animations between layers look way better if there’s a big gap. Four, we’re thinking about V2 where we add some little interactive bits into those spaces so that people can explore the app more. ↩