Examples of Use Cases in Swift

In the last post we went over what use cases are in software development and how they can be used in iOS development. We also went over their origin as a requirements gathering technique for teams to document the functionality of the system they’re designing. In this post, I want to go over a few (somewhat abstract) examples of use cases, in both their requirements gathering form, and the examples of the Swift code backing them. I’ll be using actual examples from a Side-Project-in-Development™.

Overview of Side-Project-in-Development™

The project is an app which allows user to take time-lapse videos. It takes pictures on a user set timer. Those photos will eventually be stitched together to produce a video time-lapse.

Camera Functionality Use Case

As mentioned in the previous article, written use cases typically encapsulate a larger system, with the code encapsulating the smaller systems within. These templates are highly team dependent, and you may find you want more (or less!) information in this template depending how strict you’re being with the requirements gathering. For more information on what a template could include, I’d recommend checking out Writing Effective Use Cases.

The below represents the overall camera functionality for taking a collection of time-lapse photos:

Title: Taking a time-lapse

Primary Actor: User of the app.

Scope: Taking a time-lapse with the app.

Stakeholder:

  • User of app: Wants to take a time-lapse using their phone camera.

Main success scenario:

  1. User loads the app and goes to record time-lapse screen.
  2. Camera is turned on and back camera system is selected.
  3. User selects a time interval in which they want photos to be taken for the time-lapse.
  4. User starts the time-lapse and is provided feedback about the photos timings.
  5. Photos are taken during the time-lapse from the user selected time interval.
  6. Photos from the time-lapse are saved to the device.

Error scenarios:

  • The default camera we want to use isn’t available
    • Produce an error alerting the user that the default camera isn’t available.
  • Unable to take a photo
    • Stop the time-lapse and produce an error saying they’re unable to take photos.
  • There is an issue saving the photo
    • Stop the time-lapse and alert the user that there has been an error saving the photo.

Breaking it down

This is quite a sizeable amount of functionality baked into one use case. When adapting this use case to code, you’ll want to pick out individual bits of specific functionality, and build a use case around that.

There isn’t an exact science to this, however, I’d advise you to keep the scope small so your use cases can be specific, leading to smaller classes and more self describing code.

Here, I’ll divide up the above use cases into these specific portions of functionality. Which end up being:

  • Loading the camera.
  • Setting the time-interval.
  • Scheduling the time-lapse.
  • Taking and save photos.

Which will give us:

  • LoadCameraUseCase
  • SetSelectedTimeIntervalUseCase
  • StartTimelapseUseCase
  • TakePhotoUseCase

LoadCameraUseCase

In the LoadCameraUseCase, we’re going to worry about the second bullet point in the main success scenario:

Main success scenario:

  1. Camera is turned on and back camera system is selected.

And we need to handle the error if the default camera isn’t available, as set out in the error scenarios:

Error scenarios:

  • The default camera we want to use isn’t available
    • Produce an error alerting the user that the default camera isn’t available.

The resulting use case ends up looking like the below:

class LoadCameraUseCase {

    private let controller: CameraController

    init(controller: CameraController) {
        self.controller = controller
    }

    func load() throws {
        // If back camera is available, then we set the 
        // camera system we want to use and turn it on.
        if let camera = controller.availableCameras.first(where: { $0.position == .back }) {
            try controller.set(camera.id)
            controller.turnOnCamera()
        } else {
            throw TurnOnCameraUseCaseError.unableToSetCamera
        }
    }
}

SetSelectedTimeIntervalUseCase

In the SetSelectedTimeIntervalUseCase, we’re going to encapsulate the fourth and fifth bullet points in the main success scenario:

Main success scenario:

  1. User selects a time interval in which they want photos to be taken for the time-lapse.

The resulting use case ends up looking like the below:

class SetSelectedTimeIntervalUseCase {

    private let dataSource: TimeIntervalDataSource

    init(dataSource: TimeIntervalDataSource) {
        self.dataSource = dataSource
    }

    func set(id: String) {
        dataSource.set(id: id)
    }
}

StartTimelapseUseCase

In the StartTimelapseUseCase, we’re going to encapsulate the fourth and fifth bullet points in the main success scenario:

Main success scenario:

  1. User starts the time-lapse and is provided feedback about the photos timings.
  2. Photos are taken during the time-lapse from the user selected time interval.

The resulting use case ends up looking like the below:

class StartTimelapseUseCase {

    private let timeIntervalDataSource: TimeIntervalDataSource
    private let timelapseState: TimelapseState

    init(timeIntervalDataSource: TimeIntervalDataSource,
         timelapseState: TimelapseState) {
        self.timeIntervalDataSource = timeIntervalDataSource
        self.timelapseState = timelapseState
    }

    func start(takePhotoAction: @escaping () -> (),
               timerIncrementAction: @escaping (Double) -> ()) {
        if let interval = timeIntervalDataSource.get().first(where: { $0.selected })?.value {
            timelapseState.photoTimer.startTimer(withInterval: Foundation.TimeInterval(1)) {
                self.timelapseState.timerIncrement += 1
                timerIncrementAction(self.timelapseState.timerIncrement)

                if self.timelapseState.timerIncrement == interval {
                    self.timelapseState.timerIncrement = 0
                    takePhotoAction()
                    return
                }
            }
        }
    }
}

We call the takePhotoAction() which is passed in as a closure to execute when its time to take a photo. The system handling the above use case will then execute the TakePhotoUseCase. So whilst there is overlap in bullet points in this and the below use case, its important to keep the scope of the use cases small. The scope of this use case it just to handle the timers of the time-lapse, and any photo-taking should be handled by a separate use case.

TakePhotoUseCase

In the TakePhotoUseCase, we’re going to worry about the fifth and sixth bullet points in the main success criteria:

Main success scenario:

  1. Photos are taken during the time-lapse from the user selected time interval.
  2. Photos from the time-lapse are saved to the device.

And we need to handle the error the below errors:

Error scenarios:

  • Unable to take a photoe
    • Stop the time-lapse and produce an error saying they’re unable to take photos.
  • There is an issue saving the photo
    • Stop the time-lapse and alert the user that there has been an error saving the photo.

The resulting use case ends up looking like the below:

class TakePhotoUseCase {

    private let controller: CameraController
    private let imageDataSource: ImageDataSource

    init(controller: CameraController,
         imageDataSource: ImageDataSource) {
        self.controller = controller
        self.imageDataSource = imageDataSource
    }

    func takePhoto(completion: @escaping (Error?) -> ()) {
        controller.takePhoto { result in
            switch result {
            case .success(let data):
                do {
                    try self.imageDataSource.save(data)
                    completion(nil)
                } catch let error {
                    completion(error)
                }
            case .failure(let error):
                completion(error)
            }
        }
    }
}

Whilst we don’t stop the recording here, as that would be out of scope for this use case, any error returned here will eventually be passed onto the use case that handles the stopping of the recording functionality.

Conclusion

This was quite an abstract-code heavy post, however I hope some of these examples read well enough to be able to see how use cases that provide single chunks of functionality are useful for encapsulating portions of the requirements we’ve set out above.

As mentioned before, for further reading on this, I’d recommend checking out the below books:

  • The Double Edged Sword: Apple Music and Dolby Atmos

    A month or so back I bought “Dark Side of The Moon” on Blu-Ray to finally listen to the Atmos remix and – not to mince words her – it was revelatory. Maybe the most…

  • ImageSequencer – Build a video from a collection of images in iOS/macOS/tvOS

    I’ve been working on Lapsey – an app to make beautiful timelapse’s with – and whilst I won’t be open-sourcing the entire project, I am trying to open-source various components in the app that feel…

  • Get all available cameras in iOS using AVCaptureDevice.DiscoverySession

    I’m working on an iOS project that required finding all of the users available cameras on their device. This used to be simple enough prior to iOS 10, as you could just call: This would…

  • Examples of Use Cases in Swift

    In the last post we went over what use cases are in software development and how they can be used in iOS development. We also went over their origin as a requirements gathering technique for…

  • Use Cases in iOS Development

    Use cases have historically been a somewhat confusing topic to venture into for me personally, and I’m now of the believe that is because they typically have a couple of definitions, depending on who you’re…

  • UML Diagrams with PlantUML and SwiftPlantUML

    PlantUML is an open-source tool used to produce an assortment of diagrams using text. With other diagramming tools, the paradigm is typically a GUI and some dragging and dropping of various objects to build up…

  • Camera for iOS – A Swift Package

    Currently preparing a large post going over Clean Architecture in iOS and how that relates to modularization, but whilst that is in the making, I figured I’d post about my newly released Camera framework, and…

  • Feature Modularization in iOS

    So you’ve decided a loosely coupled, highly cohesive, modular architecture with well defined boundaries, is the approach you want to take with your project. Now its time to go over how to deal with separating…

  • Module Boundaries in iOS

    We’ve talked about what modularization is, and what its advantages are when used in a decoupled, cohesive way. It feels only reasonable that we should dig into the meat of what a modular architecture could…

  • Advantages to modularization in iOS

    We’ve already talked about what modularization is, and why a team might want to architect their codebase in such a way. But what are the real life advantages to having multiple units of code to…