Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


Sponsored No Maintenance Intended

This project showcases the Model-View-ViewModel (MVVM) architecture with ReactiveCocoa 4, while serving as a digital logbook of FIFA matches. It was inspired on a theoretical level by Justin Spahr-Summers' talk Enemy of the State, and on a more practical one by Ash Furrow's C-41 app.

As the Swift language and the ecosystem around it matured, porting the original ObjectiveGoal project became a natural next step, as Swift's type safety makes it a perfect fit for functional reactive programming.


SwiftGoal runs on iOS 9+ and requires Xcode 8 with Swift 2.3 to build.


No separate backend is required to use the app, as it stores all its data locally in the Documents directory by default. Note that things might break in future releases, e.g. if some model fields change! Also, you need to terminate the app to trigger a write to local storage.

For serious use and if you want to share data across multiple devices, I recommend you use Goalbase as a backend. It's easy to get started:

  1. Follow the setup instructions in the Goalbase documentation.
  2. Enable the "Use Remote Store" switch under Settings > SwiftGoal.
  3. Make sure the base URL is set correctly. The default value should be fine if you run rails server in your Goalbase directory, but for a remote setup (e.g. on Heroku) you'll need to update this setting.

Unit Tests

SwiftGoal is thoroughly covered by unit tests, which are written with Quick and Nimble. An advantage of such BDD-style frameworks is that they document the behavior of the tested code in plain English. To run the unit tests, simply hit Cmd + U in Xcode.

User Features

  • Create players
  • Create matches with home/away players and score
  • View list of matches
  • Edit existing match information
  • Delete matches
  • Pull-to-refresh any list in the app
  • See animated list changes
  • Enjoy custom fonts and colors
  • Get alerts about network and server errors
  • View player rankings
  • Switch between different ranking periods (last month, all time, …)
  • See date and time of each match
  • See matches grouped by date range (e.g. last week, last month, earlier)
  • View more player statistics (e.g. won/drawn/lost count, nemesis player, …)

Code Checklist

  • Validate player name before creating
  • Validate match player counts before creating
  • Move base URL to Settings for easy customization
  • Cancel network requests when the associated view becomes inactive
  • Retry network requests 1 or 2 times before giving up
  • Detect and animate match data changes
  • Write tests for models
  • Write tests for view models
  • Write tests for helpers and store
  • Deduplicate isActiveSignal code on view controllers (via a class extension)
  • Create watchOS 2 app for quick match entry

Benefits of MVVM

High testability: The basic premise of testing is to verify correct output for a given input. As a consequence, any class that minimizes the amount of dependencies affecting its output becomes a good candidate for testing. MVVM's separation of logic (the view model layer) from presentation (the view layer) means that the view model can be tested with minimal setup. For instance, injecting a mock Store that provides a known amount of Match instances is enough to verify that the MatchesViewModel reports the correct amount of matches. The view layer becomes trivial, as it simply binds to those outputs.

Better separation of concerns: UIViewController and its friends have been rightfully scorned for handling far too many things, from interface rotation to networking to providing table data. MVVM solves this by making a clear cut between UI and business logic. While a view controller would still acts as its table view's data source, it forwards the actual data queries to its own view model. Presentation details, such as animating new rows into the table view, will be handled in the view layer.

Encapsulation of state: As suggested by Gary Bernhardt in his famous talk “Boundaries”, view models offer a stateful shell around the stateless core of the app, the model layer. If need be, the app's state can be persisted and restored simply by storing the view model. While the view may be extremely stateful too, its state is ultimately derived from that of the view model, and thus does not require to be stored.


This project is kindly sponsored by Futurice as part of their fantastic open-source program. Kiitos!

The icons within the app are courtesy of Icons8 – a resource well worth checking out.