C4: V1 v V2

This year we’ve completely rebuilt C4. It’s more solid, more flexible and definitely more badass than before. In doing so, we’ve changed a LOT.

Here is a brief overview of those changes.

Swift

C4 is now 100% Swift (2.0). No more square brackets. Throughout the api we’ve taken advantage of all the new facets of Swift, including things like tuples, optionals, initializer inheritance, initial stored values, generics, subscripts, closures, new control-flow statements (e.g. for-in), advanced operators… I could go on.

Keeping with our fundamental guidelines for C4, our new API is very Swift-like (similar to how the old version was very ObjC-like). So, if you’re learning C4 you’re actually learning Swift. Or, if you’re experienced with Swift, then picking up C4 will be very easy.

Blocks

Everywhere! We’re now using blocks in almost every part of the api.

Animation Blocks

Creating an animation used to look like this:

circle.animationDuration = 1.0f;
circle.animationOptions = AUTOREVERSE | REPEAT;
circle.center = CGPointMake(384, 512);

You would set the duration, then any changes that happened to properties after that would create individual animations and then run.

Now, animations are their own objects that accept blocks of code to execute:

let anim = C4ViewAnimation(duration: 1.0) { 
square.center = ...
circle.fillColor = ...
}

Check out the new way of creating animations with blocks in the next section.

Delay blocks

Creating a delay used to look like this:

[self runMethod:"aMethod" afterDelay:0.5];

Now, we do things like this:

delay(0.5) {
aMethod()
}

Blocks Everywhere

These are just 2 examples of how C4 uses blocks, and there are a lot more examples I could show. However, I just want to point out that significant change in our approach to a block-based api. Throughout the rest of this article you’ll start to see how blocks appear everywhere.

Animations

C4 used to rely heavily on implicit animations, but now we’ve shifted to executing blocks of animations. Creating an animation used to look like this:

circle.animationDuration = 1.0f;
circle.animationOptions = AUTOREVERSE | REPEAT;
circle.center = CGPointMake(384, 512);

You would set the duration, then any changes that happened to properties after that would create individual animations and then run. This approach was pretty decent for V1, but getting into complex animation sequences was very tricky… For example, wanting to have 2 separate animations happen with distinct timing on the same object was a bit of a hassle:

circle.animationDuration = 1.0f;
circle.animationOptions = AUTOREVERSE | REPEAT;
circle.center = CGPointMake(384, 512);
circle.animationDuration = 0.5f;
circle.fillColor = ...

In cases like this, the durations would collide with one another because they were being set in the same method.

Animation Objects

Now, animations are their own objects and can handle changing a variety of properties of different objects, etc., rather than being restricted to the property changes of individual views.

let anim = C4ViewAnimation(duration: 1.0) { 
square.center = ...
circle.fillColor = ...
}
anim.autoreverses = true
anim.repeats = true
anim.animate()

Property changes for both the circle and the square will inherit the autoreverse/repeat characteristics of the animation. We can also store that animation as a variable and use it throughout our app (e.g. whenever an event occurs).

Animation Completion

Animations also have completion blocks. So, if you want to do something with an object after it has finished animating you can do something like this:

let anim = C4ViewAnimation(duration: 5.0) {
self.canvas.backgroundColor = C4Blue
}
anim.addCompletionObserver { 
self.canvas.backgroundColor = C4Pink
}
anim.animate()

This will animate the background of the canvas for 5 seconds, then when that’s complete it will switch immediately to another color.

Animation Groups

Previously, animating a group of objects was pretty heavy-handed. You’d create all your animation code for every case, stick that into a method, then call all the methods sequentially from another method… It might have looked something like this:

- (void)animateMoveCircle {
circle.animationDuration = 1.0
circle.options = AUTOREVERSE | REPEAT
circle.center = …
}
- (void)animateFillCircle {
circle.animationDuration = 0.5
circle.options = AUTOREVERSE | REPEAT
circle.center = …
}
- (void)animateMoveRect {
rect.animationDuration = 1.0
rect.options = AUTOREVERSE | REPEAT
rect.center = …
}
- (void)animateFillRect {
rect.animationDuration = 0.5
rect.options = AUTOREVERSE | REPEAT
rect.center = …
}
- (void)animateCircleRect {
animateMoveCircle()
animateFillCircle()
animateMoveRect()
animateFillRect()
}

Now, it looks something like this:

let move = C4ViewAnimation(duration: 1.0) {
circle.center = …
square.center = …
}
let fill = C4ViewAnimation(duration: 0.5) {
circle.fillColor = …
square.fillColor = …
}
let animations = [move, fill]
for anim in animations {
anim.autoreverses = true
anim.repeats = true
}
let group = C4ViewAnimationGroup(animations)
group.animate()

And, when you animate the group, everything initiates at the same time.

Animation Sequences

C4 also has sequences.

Instead of something like:

[self animateMoveCircle];
[self runMethod:"animateMoveRect" afterDelay:1.0];
[self runMethod:"animateFillCircle" afterDelay:2.0];
[self runMethod:"animateFillRect" afterDelay:2.5];

We now do:

let sequence = C4ViewAnimationSequence(animations: [move,fill])
sequence.animate()

And, each animation in the array executes after the previous one has completed.

Gestures

We’ve also overhauled the interaction mechanism. Where we once took advantage of a combination of touch methods as well as gestures, we now stick solely to adding gestures to objects. It’s a nice, cleaner way of handling interaction.

In the past, every object was implicitly observing touches through methods like:

-(void)tapped:(CGPoint)location {
//where you could subclass and override this method
}
-(void)move:(CGPoint)location {
//where you could subclass and override this method
}

Internally, these methods looked like:

- (void)tapped {
}
- (void)tapped:(CGPoint)location {
[self postNotification:@”tapped”];
[self tapped];
}
-(void)move:(CGPoint)location {
NSUInteger _ani = self.animationOptions;
CGFloat _dur = self.animationDuration;
CGFloat _del = self.animationDelay;
self.animationDuration = 0;
self.animationDelay = 0;
self.animationOptions = DEFAULT;
    CGPoint displacementFromCenter = CGPointMake(location.x — self.width/2 , location.y — self.height / 2);
    self.center = CGPointMake(self.center.x + displacementFromCenter.x, self.center.y + displacementFromCenter.y);
    [self postNotification:@”moved”];
self.animationDelay = _del;
self.animationDuration = _dur;
self.animationOptions = _ani;
}

We found that the overhead of 2 separate conceptual interaction models (i.e. touches and gestures) was inappropriate. Also, this approach required subclassing an object in order to customize a reaction to tapping or moving, etc. Furthermore, adding block-based gestures makes it so easy to handle interaction.

Now, we do the following:

let square = C4Rectangle(frame: C4Rect(0,0,100,100))

square.addTapGestureRecognizer { location, state in
self.canvas.backgroundColor = randomColor
}

Where the color of the canvas changes when a square is tapped.

And…

canvas.addPanGestureRecognizer { loc, trans, vel, state in 
square.center = loc
}

Where the position of a square is centered to the user’s finger as they drag around the canvas.

Observers

Say you want to know when something has happened, for example you want to know when a movie has finished playing, or when a method has run, but you don’t want to have a hard reference to an object (or set of objects) in that method. You use an observer.

In the old version of C4 you could tell any object to listen for a message, and specify a method to run when that message comes through. The syntax used to look like this:

[self listenFor:@"aMessage" andRun:"aMethod"];
[self listenFor:@"aMessage" fromObject: anObject andRun:"aMethod"];

And, to send a message an object could do the following:

[self postNotification:@"aMessage"];

However, this technique suffered from the same kind of thing as the touches approach above: you often had to subclass to properly change an object’s behaviour. Again… Blocks to the rescue!

Instead, we now do the following:

on(“aMessage”) {
//do something
}
on(“aMessage”, from: anObject) {
//do something
}

And, posting is just as easy:

post("aMessage")

Abstraction: Core & UI

The entire api has been significantly abstracted. For example, we used to have a class called C4Control that was the base class for all visual objects in C4. That class had just over 1000 lines of code… It was huge.

Now, that class is replaced with a basic C4View (~600 lines, incl. comments) and any non-critical or conceptually separate components of that class have been abstracted into extensions. For example, the following extensions are separate files:

C4View+Animation
C4View+Border
C4View+KeyValues
C4View+Shadow

This makes the api easier to read, makes the structure more clear, and is just generally less heavy-handed than before.

On top of this approach, we’ve separated code into two categories.

Core

There are a core set of classes that represent the objects and structures necessary to work with C4. Included in this are any elements that are not related to a user’s direct experience, but are necessary for the function of the framework.

C4Color
C4EventSource
C4Foundation
C4Math
C4Point
...

UI

All visual / media objects are part of the UI section of the project. These include controllers, filters and extensions that have to do specifically with visible or audio media.

C4View
C4Shape
C4Movie
C4CanvasController
...

UIKit Extensions

Finally, we’ve included a bunch of extensions that make working between C4 and UIKit easier. Though not exhaustive, these extensions make a significant difference when needing to add views, convert points, and so on…

UIViewController

UIViewContoller now has a canvas object. This is a C4 version of the object’s view property. Where the following:

aUIViewController.canvas

… will return a C4View that encapsulates the native view of the controller.

This makes things easier when changing the background color of a native viewController’s view. Instead of:

aUIViewController.view.layer.backgroundColor = UIColor.redColor().CGColor

You can do:

aUIViewController.canvas.backgroundColor = red

UIView

A C4View is actually a wrapper around a UIView. So, you can’t directly add a view to another view… To handle this (and make things consistent) we’ve included add and remove methods that make things work between UIView and C4View.

view.add(aUIView)
view.add(aC4View)

This add method also implicitly handles optionals.

There are also:

add([subviewArray]) //for adding multiple views
remove(subview)
sendToBack(subview)
bringToFront(subview)

UIColor / CIColor

Creating a UIColor or a CIColor is possible:

UIColor(C4Pink)
CIColor(blue)

CGStructs

Casting bewteen basic structure types is possible too:

CGRect(C4Rect)
CGPoint(C4Point)
CGSize(C4Size)

… and of course, the other way around:

C4Rect(CGRect)
C4Point(CGPoint)
C4Size(CGSize)

FIN

We’ve run through a basic set of changes between the first and second versions of C4. There are a TON more changes that we’re proud about, but too many to dive into.

The new version is badass.

Download it.

Use it.

Let me know what you think.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.