Author: pedrocoloma

  • A Beginner’s Guide to Core Animation in iOS

    A Beginner’s Guide to Core Animation in iOS

    Core Animation might seem intimidating for new iOS developers, but it’s essential to understand how animations work under the hood. While UIKit (and SwiftUI) provide high-level APIs for animations, Core Animation (CA) is the underlying system responsible for the motion you see.

    Understanding Core Animation

    Core Animation manages the visual content of your app using CALayer objects. Every UIView has an associated CALayer that you can manipulate directly if you need more fine-grained control than UIKit’s higher-level animation APIs. This is helpful when you need effects that aren’t possible through property-based animators or standard view transitions.

    Under the hood, Core Animation uses two models:

    • The model layer: Reflects the “real” values of the view’s properties (position, size, corner radius, etc.).
    • The presentation layer: Reflects the current onscreen values during an active animation. When the animation completes, you usually update the model layer to match the final animated state.

    1. Moving a View

    Below is a basic Swift example showing how to move the image from one position to another using Core Animation:

    class ViewController: UIViewController {
        
        let duck = UIImageView(image: UIImage(named: "duck"))
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = UIColor(named: "orange")
            
            view.addSubview(duck)
            animateLeftToRight()
        }
        
        private func animateLeftToRight() {
            let animation = CABasicAnimation()
            animation.keyPath = "position.x"
            animation.fromValue = 20 + 140/2
            animation.toValue = 300
            animation.duration = 1
            
            duck.layer.add(animation, forKey: "move")
            
            // defines the final position so it doesn't reset
            duck.layer.position = CGPoint(x: 300, y: 100 + 100/2)
        }
    }

    You can get the source code of this project here

    When you remove the line that sets redView.layer.position.x = 300, the square will slide then “snap back” to its original position because the model layer wasn’t updated with the animation’s final value.

    2. Scaling a View

    Need to grow or shrink a view? You can animate the scale using the "transform.scale" keyPath:

    private func animateScale() {
        let animation = CABasicAnimation()
        animation.keyPath = "transform.scale"
        animation.fromValue = 1
        animation.toValue = 2
        animation.duration = 2
        
        duck.layer.add(animation, forKey: "scale")
        
        // defines the final scale
        duck.layer.transform = CATransform3DMakeScale(2, 2, 1)
    }

    3. Rotation

    Rotating a view about its Z-axis is similar, except you use "transform.rotation.z":

    private func animateRotation() {
        let animation = CABasicAnimation()
        animation.keyPath = "transform.rotation.z" // z-axis
        animation.fromValue = 0
        animation.toValue = CGFloat.pi / 4 //45 degress in radians
        animation.duration = 2
        
        duck.layer.add(animation, forKey: "rotate")
        duck.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)
    }

    Here, "transform.rotation.z" indicates rotation about the Z-axis (the axis “coming out of the screen”).

    4. Keyframe Animations (Shake Effect)

    Keyframe animations let you define multiple points in time, making it easy to create effects like “shaking” a login form when the user enters invalid credentials:

    private func animateShakeTextField() {
          let animation = CAKeyframeAnimation()
          animation.keyPath = "position.x"
          animation.values = [0, 10, -10, 10, 0]
          animation.keyTimes = [0, 0.16, 0.5, 0.83, 1]
          animation.duration = 0.4
          
          animation.isAdditive = true
          duck.layer.add(animation, forKey: "shake")
    }

    You can apply this same approach to any view—just change "position.x" to "position.y" or another property as needed.

    Tips for Using Core Animation

    1. Update the Model Layer
      Always remember to set the final position or transform on the view’s model layer if you want its new state to persist after the animation finishes.
    2. Anchor Point Gotchas
      The default anchor point is the view’s center. If you change your view’s anchor point, your animations might shift unpredictably if you don’t adjust positions accordingly.
    3. Coordinate System
      Core Animation uses a coordinate system that starts at the top-left corner, increasing down and to the right. For position-based animations, ensure you calculate the correct center points (especially if you set frames or anchor points manually).
    4. Combine with UIKit
      You can mix these animations with standard UIKit property animators. This is sometimes easier for simpler transitions, but dropping down to Core Animation’s layer-based approach gives you maximum power.
    5. Experiment in Small Steps
      Because it’s easy to get lost in the details of the model/presentation layer, test small animations first. Even drawing a shape or path to see if it lines up with what you expect can make debugging much easier.

    Conclusion

    Core Animation unlocks a wealth of possibilities beyond UIKit or SwiftUI’s higher-level animations. By understanding how CALayer, keyPaths, and the model/presentation layers work, you can combine transforms, positions, and keyframe animations to produce delightful user experiences.

  • Error handling in Swift explained

    Error handling in Swift explained

    Swift allows us to throw, propagate and catch errors across functions. Here we’ll take a look at how this works with a few examples.Functions that throws errors.There are several already implemented functions that we use on a daily basis that throws errors. You can see them on Xcode when it contains the keyword throws at the end.

    To call these functions we need to handle any error that it might throw, as we are going to see later on. We can also implement our own function that throws an error. For that we need to add the keyword throws at the end, like this:

    func functionName() throws -> Int {}

    The error could be anything we like as long as it conforms to the Error protocol.

    The Do – Try – Catch syntax

    Let’s get started by writing a function that opens an image to be converted to Data. The initializer for Data can throw an error.

    So Xcode will ask us to use try before calling the initializer.

    Now we have two options: Use try? and don’t handle the error. With this the result would be an optional Implement a Do-Try-Catch to handle the errorUsing optional try We can simply add a try? which will not throw an error but the response would be an optional. If there’s an error the result would be nil.

    let imageData = try? Data(contents0f: URL(filePath: "imagePath"))

    Implementing a do-try-catch

    With this syntax we have a successful block and a failure block.

    do {
      let imageData = try Data(contents0f: URL(filePath: "imagePath"))
      // Successful block
      // Handle the image data
    } catch {
      // Failure block
      // Handles the error
      print(error.localizedDescription)
    }

    Create you own error

    You can create your own error Enum that works according to what your application needs. The enum must conform to the Error protocol. This is an example of an error enum for a login feature:

    enum LoginError: Error {
      case incompleteForm 
      case shortPassword
      case invalidEmail
      case invalidPassword
    }

    How to propagate an error

    Instead of handling the error, our function can throw them by propagating to the function that called our function.

    func getUserData throws {
      do {
        let user = try getUser ()
      } catch {
        throw LoginError.differentError
      }
    }

    Some use cases in iOS development

    Implementing a login function that throws errors

    With the same LoginError enum we can implement a login function that throws different errors.

    func login(email: String, password: String) throws {
      if email.isEmpty || password. isEmpty {
        throw LoginError.incompleteForm
      }
      
      if password.count < 6 {
        throw LoginError.shortPassword
      }
      
      if !isValid(email: email) {
        throw LoginError.inavlidEmail
      }
    
      if !isValid(password: password) {
        throw LoginError.invalidPassword
      }
    }

    And another function catches and handles those errors. We need to implement the last catch is not specific to an error.

    do {
      try login(email: email, password: password)
      // Login succeeded
    } catch LoginError.incompleteForm {
      alertMessage = "Incomplete form"
    } catch LoginError.shortPassword {
      alertMessage = "Password is too short"
    } catch LoginError.inavlidEmail {
      alertMessage = "Invalid email"
    } catch LoginError.invalidPassword {
      alertMessage = "Invalid password"
    } catch {
      alertMessage = "Unable to login" {
    }

    JSON parsing

    Decoding JSON data for the details of a user.

    do {
      let userDetails = try JSONDecoder).decode(UserDetails.self, from: data)
      completion(.success(userDetails))
    } catch {
      print(error.localizedDescription)
      completion (.failure(parseError))
    }

    Conclusion

    Error handling is an essential part of developing apps. Knowing how to do so will greatly improve your ability to work on different user cases and properly handle any error.