About Guanshan Liu

Guanshan is an iOS Developer currently working as an iOS Engineer at Alibaba Inc. Before working at Alibaba, Guanshan worked with 2K Games on Civilization Revolution 1 and 2 for iOS. He has a Masters in Software Engineering from the University of York, UK as well as a Bachelors of Engineering in Information Security from Nanjing University of Aeronautics and Astronautics. Find him on twitter, @guanshanliu.

Function Currying in Swift

Function Currying in Swift

The concept of currying is that instead of accepting multiple arguments, a function accepts only one, and returns a function which acepts the remaining arguments. The returned function will also accept only one argument, and returns another function. This process continues until all arguments are exhausted and we are left only with a single return value.

For example, usually we define a function that returns the sum of two integers as follows:

func add1(x: Int, y: Int) -> Int {
    return x + y
add1(1, 2) // Output: 3

We can always transform a function taking multiple arguments into a curried one, by separating the function into a series of function that each takes only one argument. The curried version of add1 is as follows:

func add2(x: Int) -> (Int -> Int) {
    return { y in return x + y }
add2(1)(2) // Output: 3

This function has this type specified: Int -> Int -> Int. This may seem a little strange to newcomers to functional programming. Which part is the argument, and which part is the return type?

Here, add2 is taking an Int, and returning a Function which takes another Int, which in turn returns a third Int. You could say it’s something like this: Int -> (Int -> Int), or we could use typealias to make (Int -> Int) more clear:

typealias IntTransformer = (Int -> Int)

Now, any time we see IntTransformer, it’s easier to comprehend that it’s a function that transforms an Int value. With that, we could redfine add2 like this:

func add2Aliased(x: Int) -> IntTransformer {

This probably looks a little more familiar, but under the hood this is exactly the same as the add2 function we defined with the data type Int -> Int -> Int. This is just a more familiar looking way to write it.

Calling add2() with just a single argument returns a function that takes another (Int -> Int) function, which means we can store that function in a separate variable if we so choose:

let addTwentyTransformer = add2(20)
addTwentyTransformer(5) // Output: 25

Now, add2(20) is a function that takes one integer and returns the value of that integer plus 20.

The -> operator is defined as associativity right. Instead of writing A -> (B -> (C -> D)), we can simply write A -> B -> C -> D.

Swift also supports another way to define a curried function:

func add3(x: Int)(y: Int) -> Int {
    return x + y
add3(1)(y: 2) // Output: 3

This is helpful if you want named parameters, which can sometimes help your code easier to read. It’s also easy to make the syntax the same as add2 and remove the explicit argument name.

func add4(x: Int)(_ y: Int) -> Int {
    return x + y
add4(1)(2) // Output: 3

Benefits of Currying

What are the benefits currying provides? Let’s look at the add functions above.

With add1 the regular function, we cannot apply this function until both of its arguments are ready. With the curried add2, we can apply one or two arguments.

Let’s see an example that uses add1 and add2 with mapto transform an array of integers by adding 7 to each.

// Apply map to the array using the two functions
let xs = [1, 2, 3]
let addSeven1 = { add1($0, 7) }
map(xs, addSeven1) // Output: [8, 9, 10]
let addSeven2 = add2(7)
map(xs, addSeven2) // Output: [8, 9, 10]

The curried function add2 is much cleaner in this case.

There is another case when curried functions have obvious advantages. To demonstrate the example, first we define a function (a custom operator) that composes two functions together:

// Define a new operator |> that has left associativity
infix operator |> { associativity left }
func |> <A, B, C>(f: B -> C, g: A -> B) -> A -> C {
    return { x in

Let’s say we want to transform an array of integers by adding 7 to each element, and then adding 3 again. We could write:

let xs = [1, 2, 3]
// Returns a function that adds three to it's only argument
let addThree = add2(3)
// Apply addSeven1 to every item in xs
// Then apply addThree to every item in that list
xs.map(addSeven1).map(addThree) // Output: [11, 12, 13]

It first adds 7 to each element in xs, wraps the results into a temporary array, then add 3 to each in the temporary array, and return the last results in a new array. This creates a temporary array that we never need.

By composing the curried functions, addSeven2 and addThree, we can eliminate the creation of the temporary array.

xs.map(addSeven1 |> addThree) // Output: [11, 12, 13]

Builtin Currying Functions in Swift

In the Swift standard library there is a function on the Int type called advancedBy that takes an amount, and returns an Int that has been adjusted by that amount.

extension Int : RandomAccessIndexType {
    func advancedBy(amount: Distance) -> Int

It’s simple enough to use this function on an Int and get the advanced value:

5.advancedBy(6) // Output: 11

But because of function currying, we could get the partial application of this function by not specifying the initial value to be advanced:

let add6 = Int.advancedBy(6)
add6(5) // Output: 11
add6(10) // Output: 10

Let’s look at the following example. To insert another string at the end of the given string, we could call the splice function on an instance of String:

var s = "Hello"
s.splice(", world", atIndex: s.endIndex)
// Output: "Hello, world"

Or we can call it on String data type, and pass the String instance as its only argument:

String.splice(&s)("!!!", atIndex: s.endIndex)
s // Output: "Hello, world!!!"

The splice function on a String instance is not a curried function. s.splice("!!!")(atIndex: s.endIndex) will not compile.

The term partial application, is a function that accepts only some of its arguments, and returns another function that takes the rest of arguments. While a curried function takes only one argument.

Don’t confuse partial application with another term called partial function. Partial function is a function that cannot provide a valid result for all its possible inputs. In Objective-C, if we call [[NSString alloc] initWithString:nil], it will compile but throw a NSInvalidArgumentException at runtime. -initWithString: is a partial function, because there is no return value for nil.

Next Steps

Is this all making sense? This can all be quite a chunk of new information to take in if you are new to functional programming in general. For that reason we are preparing a free functional programming course that is in private beta testing right now. If you want to be part of the beta, or just want us to let you know when it’s ready, sign up for the beta here.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS

Functional Programming in Swift

Thoughts on Functional Programming in Swift

Like most of you, I have to use Objective-C at my day job. I could only craft my Swift skills at night. Swift is not a purely functional language. It can be use imperatively because all frameworks from Apple are written in Objective-C at the time of writing. However, it is also functional, learning from modern languages like Haskell, F#, etc. At the beginning when reading Swift blogs and tutorials, many people talked about terms like functors and monads, those sounded alien-like to me. I started to learn Haskell to understand what they were talking about. I’m not an expert, and still learning functional programming and Swift, but I wanted to share what I’ve learned so far. Hopefully it will save you some troubles to get into the functional programming world.

Key Concepts

Higher-order functions

One key concept of functional programming is higher-order functions. According to Wikipedia, a higher-order function:

  • takes one or more functions as an input
  • outputs a function

In Swift, functions are first-class citizens, like structs and classes, as we can see in the following example:

let addFive = { $0 + 5 }
addFive(7) // Output: 12

We define a function as an inline closure, and then assign it to an inline closure. Swift provides shorthand argument to it. Instead of by name, we can refer to the arguments by number, $0, $1 and so on.

func addThreeAfter(f: Int -> Int) -> Int -> Int {
    return { f($0) + 3 }
let addEight = addThreeAfter(addFive)
addEight(7) // Output: 15

The argument type Int -> Int means it is a function that takes an Int as an argument, and returns an Int. If a function requires more than one arguments, for example, it has two Int argument, we can define the type as (Int, Int) -> Int.

The return type of addThreeAfter is also a function. It equivalents to func addThreeAfter(f: Int -> Int) -> (Int -> Int).

[addFive, addEight].map { $0(7) } // Output: [12 15]

map is a special function for container type, such as Array, Optional. It unwraps values from the container type, then applies a transform for each, and wraps them again. We use map here to pass 7 as an argument to each function in the array, and wraps the results into a new array.

In summary, we can assign functions to variables, store them in data structures, pass them as arguments to other functions, and even return them as the values from other functions.

Pure functions

A function is called a pure function if it has no observable side effects. But what the heck is side effect?

A function or expression is said to have a side effect if, in addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world.
— from Wikipedia

The two functions addFive and addEight are pure functions. They don’t modify the input value, or change any global state. In the example below, addFive2 modifies the input, so it is not a pure function.

func addFive2(inout a: Int) -> Int {
    a += 5
    return a
var a = 7
a // Output: 12

Functions that access or modify a database, the local file system, or printing strings to the screen are also considered impure. Because they modify the state of the database records, file system, and display buffers respectively. Sometimes side effects are desirable. Without side effects, we could not interact with the program.

Haskell introduces types like IO to separate pure and impure layers. But in Swift, we don’t need to worry too much about this separation. We could still use Cocoa APIs as usual. But, I strongly encourage the use of pure functions whenever possible. With pure functions, the code is more predictable. A pure function will always return the same value if given the same input. It’s easy to test, too. We don’t need to mock a data structure to satisfy its internal state requirements to test a function.

Imperative & Functional Programming

All above are very theoretical. You may want to know how functional programming with help us solve problems in a better, clearer, or less error-prone way. What are the benefits we could gain from it?

First, you need to understand that we could do almost anything in imperative programming, and functional programming does not extend the possibilities of what we could do.

Second, if you come from the imperative programming world, some functional code is harder to understand at first, especially those with custom operators. It is not because functional programming is hard and obscure, but it’s because our mindsets are trained from years of imperative programming practices.

Take a look at this example of imperative code, which we can rewrite as functional code. These two do exactly the same things.

// Imperative
var source = [1, 3, 5, 7, 9]
var result = [Int]()
for i in source {
    let timesTwo = i * 2
    if timesTwo > 5 && timesTwo < 15 {
result // Output: [6, 10, 14]
// Functional
let result2 = source.map { $0 * 2 }
                    .filter { $0 > 5 && $0 < 15 }
result2 // Output: [6, 10, 14]

It is arguable which one is clearer. But from this simple example you can see the main difference between imperative and functional programming. In imperative programming, we instruct the computer how to do something:

  1. Iterate through source
  2. Get the result from the element, and multiply by 2
  3. Compare it with 5 and 15
  4. If it is bigger than 5 and less than 15, put it into result

However, in functional programming, we describe what to do:

  1. Transform each element in source to itself multiplied by 2
  2. Only select the ones with value bigger than 5 and less than 15

I’m not going to persuade you functional programming is better. It’s your personal preference. After all, good code is all about writing code that:

  1. Works as intended
  2. Is clear to you and your team

An Example: Reverse Polish Notation Calculator

I like Swift and functional programming, because it enables me to solve a problem in a different perspective. There is usually more than one way to solve a problem. Exploring a better solution helps us grow to become good developers.

Let me show you a functional example. It is a calculator for algebraic expressions of reverse polish notation, or RPN in short. (It is a Swift implementation of the Haskell example in Learn You a Haskell for Great Good.)

A RPN expression of (3 + 5) 2 is 3 5 + 2 . You may think of this as a stack of numbers. We go through the RPN expression from left to right. When encountering a number, we push it onto the stack. When we encounter an operator, we pop two numbers from the stack, use the operator with those two numbers, and then push the result back onto the stack. When reaching the end of the expression, the only one number left on the stack is the result (assuming the RPN expression is valid). For more explanation about RPN, please check on Wikipedia.

We want a function that returns the result for an RPN expression.

func solveRPN(expression: String) -> Double {
    // Process the expression and return the result

Given a RPN expression String “3 5 + 2 *”, first we need to transform it into an array of elements that we can process. There are two kind of elements, operand and operator. The Enum data type in Swift comes in handy for defining the element. We name it RPNElement.

enum RPNElement {
    case Operand(Double)
    case Operator(String, (Double, Double) -> Double)

Next, we split the expression into an array of Strings, then map it into an RPNElement array.

extension String {
    func toRPNElement() -> RPNElement {
        switch self {
        case "*": return .Operator(self, { $0 * $1 })
        case "+": return .Operator(self, { $0 + $1 })
        case "-": return .Operator(self, { $0 - $1 })
        default: return .Operand(Double(self.toInt()!))
func stringToRPNElement(s: String) -> RPNElement {
    return s.toRPNElement()
func solveRPN(expression: String) -> Double {
    let elements = expression.componentsSeparatedByString(" ").map(stringToRPNElement)
        // Further process

Next, we will go through the array and process it according to how RPN works, as I described earlier. We reduce the array into a Double array. Assuming the expression is valid, the Double array should only contain one element. It will be the result we want.

func solveRPN(expression: String) -> Double {
    let elements = expression.componentsSeparatedByString(" ").map(stringToRPNElement)
    let results = elements.reduce([]) { (acc: [Double], e: RPNElement) -> [Doublein
        switch e {
        case .Operand(let operand):
            return [operand] + acc
        case .Operator(let op, let f):
            let r = f(acc[0], acc[1])
            return [r] + acc[2..<acc.count]
    return results.first ?? 0
solveRPN("3 5 + 2 *") // Output: 16

Where to Go From Here?

If you are interested in learning more about functional programming, I highly recommend the following two books:

Even though the second book is written for Haskell, but the concepts also apply to Optional in Swift as well. Besides, it explains Functor, Applicative, Monad in details.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS

Fun with CAShapeLayer

CAShapeLayer is a specialized subclass of CALayer that draws itself using the shape you define via the path property. path is an instance of CGPath. We could leverage the convenient UIBezierPath APIs to create a path, and then retrieve the CGPath from it. Besides all the animatable properties inherited from CALayer, there are other animatable properties that let you control attributes such as fill color, stroke color, line thickness, etc. In this tutorial, I will illustrate some visual effects using these properties.

Looking for something more in-depth? Try my book & video courses
Get The Swift Book
Learn About My Book & Video Packages »

Let’s Get Started!

First, you need to download the starter project from here.

Rounded Corners

There are different ways to draw rounded rectangles.

Go to RoundedCornersViewController class, add the following codes at the end of viewDidAppear method.

// 1
rectShape1.backgroundColor = UIColor.redColor().CGColor
rectShape1.cornerRadius = 20
// 2
rectShape2.fillColor = UIColor.greenColor().CGColor
rectShape2.path = UIBezierPath(roundedRect: rectShape2.bounds, cornerRadius: 20).CGPath
// 3
rectShape3.fillColor = UIColor.blueColor().CGColor
rectShape3.path = UIBezierPath(roundedRect: rectShape3.bounds, byRoundingCorners: .BottomLeft | .TopRight, cornerRadii: CGSize(width: 20, height: 20)).CGPath

1. The first way to draw a rounded rectangle is to change a layer’s cornerRadius property. This applies to all CALayerS.
2. We could also use path to draw a rounded rectangle. Assign a rounded rectangle path via this convenient method on UIBezierPath. By doing this, we have to use fillColor instead of backgroundColor. Because backgroundColor is color of the layer’s background, while fillColor is the color used to fill the shape’s path.
3. Using path, we are not limited to round all corners. We could specify which corner we want to round. In this example, I only change bottom left and top right corners.

Run and select Rounded Corners cell.

Path Animation

path is also an animatable property. We could achieve the basic Material-Design-feel effect by animating it.

Go to PathViewController class, add the following codes at the end of viewDidAppear method.

// fill with yellow
rectShape.fillColor = UIColor.yellowColor().CGColor

// 1
// begin with a circle with a 50 points radius
let startShape = UIBezierPath(roundedRect: bounds, cornerRadius: 50).CGPath
// animation end with a large circle with 500 points radius
let endShape = UIBezierPath(roundedRect: CGRect(x: -450, y: -450, width: 1000, height: 1000), cornerRadius: 500).CGPath

// set initial shape
rectShape.path = startShape

// 2
// animate the `path`
let animation = CABasicAnimation(keyPath: "path")
animation.toValue = endShape
animation.duration = 1 // duration is 1 sec
// 3
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // animation curve is Ease Out
animation.fillMode = kCAFillModeBoth // keep to value after finishing
animation.removedOnCompletion = false // don't remove after finishing
// 4
rectShape.addAnimation(animation, forKey: animation.keyPath)

1. Calculate begin and end shapes for the animation. Then assign the startShape to path.
2. Use CABasicAnimation to animate path. Destination value for path is the end shape we defined before. Set it to toValue. Then set the animation duration to 1 second.
3. Set the animation curve to ease out, making it look more natural. With removedOnCompletion set to false, fillMode to kCAFillModeBoth, when the animation finishes, rectShape will remain the end shape.
4. Add the animation to the layer.

Run and select path Animation cell. See the animation:

View Movie

Line Width Animation

lineWidth defines the stroke line width of the shape’s path, and it’s also animatable. There are some cool effects we could make via lineWidth.

Go to LineWidthViewController class, add the following codes at the end of viewDidAppear method.

// setup
let rect = CGRect(x: 0, y: 0, width: view.bounds.width, height: 1)
rectShape.bounds = rect
rectShape.position = view.center
rectShape.path = UIBezierPath(rect:rect).CGPath

// 1
rectShape.lineWidth = 10
rectShape.strokeColor = UIColor.blueColor().CGColor

// animate
let animation = CABasicAnimation(keyPath: "lineWidth")
// 2
animation.toValue = 1000
animation.duration = 1 // duration is 1 sec
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // animation curve is Ease Out
animation.fillMode = kCAFillModeBoth // keep to value after finishing
animation.removedOnCompletion = false // don't remove after finishing
rectShape.addAnimation(animation, forKey: animation.keyPath)

1. Give an initial line width of 10 points. To set the line color, we use strokeColor.
2. Similar to path animation, animate line width to 1000.

Run and select lineWidth Animation cell. See the animation:

Stroke Animation

strokeStart and strokeEnd defines the relative location at which to begin stroking the path, ranging from 0 to 1. Many cool activity indicator can be made by using them. Here is a simple example that show you how to animate these properties.

Go to StrokeViewController class, add the following codes at the end of viewDidAppear method.

// 1
rectShape.path = UIBezierPath(ovalInRect: rectShape.bounds).CGPath

rectShape.lineWidth = 4.0
rectShape.strokeColor = UIColor.lightGrayColor().CGColor
rectShape.fillColor = UIColor.clearColor().CGColor

// 2
rectShape.strokeStart = 0
rectShape.strokeEnd = 0.5

// 3
let start = CABasicAnimation(keyPath: "strokeStart")
start.toValue = 0.7
let end = CABasicAnimation(keyPath: "strokeEnd")
end.toValue = 1

// 4
let group = CAAnimationGroup()
group.animations = [start, end]
group.duration = 1.5
group.autoreverses = true
group.repeatCount = HUGE // repeat forver
rectShape.addAnimation(group, forKey: nil)

1. Here is another way to draw a circle using another UIBezierPath’s convenient initializer.
2. Set the initial values for strokeStart and strokeEnd.
3. Create animations like before.
4. Group two animation together. Because we want to both animations to happen simultaneously. The duration is 1.5 seconds. It will auto reverse the animations upon finishing. And it will repeat forever.

Run and select Stroke Animation cell. See the animation:


You could find the complete project on Github. If you have further questions, you can leave a comment or ask me on Twitter.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS

Core Animation Swift Tutorial – Animatable Properties

This tutorial series requires a basic understanding of UIView hierarchy. If you are new to iOS development, you might want to begin with Developing iOS Apps Using Swift first.

When you first hear about Core Animation, you might think it is all about animation. However, animation is only a part of this framework. Core Animation is an infrastructure for compositing and manipulating visual content onscreen. It uses the GPU to accelerate the rendering of on-screen objects. It divides the content onscreen into individual objects called layers, and arranges them in a tree hierarchy (known as the layer tree). You are familiar with the UIView tree hierarchy, which is built upon the layer tree. In other words, Core Animation accounts for everything that you see onscreen.

In the first part of this tutorial series, you are going to learn the basics of CALayer, how to use its properties to create neat visual effects easily, and at the end of this tutorial, you will learn the most important subclass of CALayer – CAShapeLayer.

What Is CALayer?

CALayers are rectangular objects that can contain visual content. They are stored into a tree hierarchy, and each manages the position of its children sublayers.

Sound familiar? You may say, “It’s like UIView!”

That’s true, but it’s not just a coincidence. Every UIView has a layer property known as the backing layer, which is an instance of CALayer. UIView just ensures the corresponding back layers are correctly connected in the layer tree when subviews are added or removed from the view. It is the backing layer that is responsible for display and animation of the view. The only major feature that the backing layer does not handle is user interaction.

For the purposes of going through this tutorial, I recommend creating an empty single-view iPhone application.

Exploring CALayer

Creating a CALayer instance is very straightforward.

let redLayer = CALayer()

We can set its frame, backgroundColor, and add it to a superlayer just like we do with UIView.

redLayer.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
redLayer.backgroundColor = UIColor.redColor().CGColor

Add this code to a function called setup in the file ViewController.swift, and call the method from viewDidLoad. You should have something like this:

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
    func setup() {
        let redLayer = CALayer()
        redLayer.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
        redLayer.backgroundColor = UIColor.redColor().CGColor

Notice that the backgroundColor property of CALayer is a CGColor instead of UIColor.

You can also set an image as the content via the contents property. contents is also known as backing image.

We’ll use an image of a butterfly:

Note that you’ll have to drag your image named ButterflySmall.jpg in to your Xcode project hierarchy in order for the UIImage() command to find the picture.

func setup() {
    let redLayer = CALayer()
    redLayer.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
    redLayer.backgroundColor = UIColor.redColor().CGColor
    let imageLayer = CALayer()
    let image = UIImage(named: "ButterflySmall.jpg")!
    imageLayer.contents = image.CGImage
    imageLayer.frame = CGRect(x: 0, y: 100, width: image.size.width, height: image.size.height)
    imageLayer.contentsGravity = kCAGravityResizeAspect
    imageLayer.contentsScale = UIScreen.mainScreen().scale

contentsGravity is a constant that specifies how the layer’s contents are positioned or scaled within its bounds.

contentsScale defines a ratio mapping between the size of the layer (measured in points) and the size of the bitmap used to present the layer’s content (known as backing image, measured in pixels). The default value is 1.0. Normally we set the value as the scale of the image, as shown above. However, when working with image that are generated programmatically, or image that missing @2x/@3x suffix, you will remember to manually set the layer’s contentsScale to match the screen scale. Otherwise, you will get a pixelated image on your device.

Corners and Border

CALayer has a property called cornerRadius, which applies rounded corners to the layer’s background and border. When the masksToBounds property is set to true, the layer’s backing image and sublayers are clipped to this curve.

On our redLayer, let’s apply some rounded corners, and make the border visible.

func setup() {
    let redLayer = CALayer()
    redLayer.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
    redLayer.backgroundColor = UIColor.redColor().CGColor
    // Round corners
    redLayer.cornerRadius = 25
    // Set border
    redLayer.borderColor = UIColor.blackColor().CGColor
    redLayer.borderWidth = 10

borderWidth, borderColor defines the width and color of a layer’s border.

Drop Shadow

There are four properties that you could configure the drop shadow for a layer, shadowOpacity, shadowColor, shadowOffset and shadowRadius . shadowRadius controls the blurriness of the shadow. A larger value could create a softer shadow that looks more natural.

Let’s add a shadow to our redLayer as well.

redLayer.shadowColor = UIColor.blackColor().CGColor
redLayer.shadowOpacity = 0.8
redLayer.shadowOffset = CGSizeMake(2, 2)
redLayer.shadowRadius = 3

Unlike the layer border, the layer’s shadow derives from the exact shape of its contents, not just the bounds and cornerRadius. However, if you know what the shape of your shadow would be in advance, you could specify it via shadowPath (an instance of CGPath). You could improve performance by doing this.

Animating Layers

Now that we’ve covered a few of the types of properties that are present in Core Animation, let’s quickly walk through creating some actual animations.

// Create a blank animation using the keyPath "cornerRadius", the property we want to animate
let animation = CABasicAnimation(keyPath: "cornerRadius")

// Set the starting value
animation.fromValue = redLayer.cornerRadius

// Set the completion value
animation.toValue = 0

// How may times should the animation repeat?
animation.repeatCount = 1000

// Finally, add the animation to the layer
redLayer.addAnimation(animation, forKey: "cornerRadius")

Here we create a new CABasicAnimation for the cornerRadius property. Run your app and take a look, cool right?

You could just as easily make this animation apply to any other animatable property of CALayer. Try swapping the “cornerRadius” keyPath value in the CABasicAnimation() constructor with the value “borderWidth”. What happens? What about a value of “shadowRadius”?

From this tutorial you can see how the basics of Core Animation work. In the next part we’ll move on to learn about more animatable properties, and how to work with masks to do really nifty effects.

Full code for this tutorial available here, on Github.

Want to get notified when the next part is out? Be sure to subscribe to our newsletter here.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS