About us
Our services

Capabilities

Legacy Modernization
Data Platforms
AI & Advanced Analytics

Industries

Automotive
Finance
Manufacturing

Solutions

Databoostr

Data Sharing & Monetization Platform

Cloudboostr

Multicloud Enterprise Kubernetes

Looking for something else?

Contact us for tailored solutions and expert guidance.

Contact
Case studies
Resources

Resources

Blog

Read our blog and stay informed about the industry’s latest trends and technology.

Ready to find your breaking point?

Stay updated with our newsletter.

Subscribe

Insights

Ebooks

Explore our resources and learn about building modern software solutions from experts and practitioners.

Read more
Contact
Blog
Software development

Server Side Swift with Vapor - end to end application with Heroku cloud

Grape up Expert
November 24, 2025
•
5 min read

Table of contents

Heading 2
Heading 3
Heading 4
Heading 5
Heading 6

Schedule a consultation with software experts

Contact us

In this tutorial, I want to show you the whole production process of back-end and front-end swift applications and push the backend side to the heroku cloud.

First, please make sure that you have installed at least Xcode 10. Let’s start from the backend side:

Open a terminal and check your Swift version and make sure that your swift version is 4.2.

swift –-version

Time to install Vapor CLI, for this one we should use homebrew.

brew install vapor/tap/vapor

Vapor CLI is already installed.

Let’s create a new project. Vapor CLI have simple commands for lots of things. Most of them you will learn in this tutorial later.

Vapor new {your-project-name}

Go into the project directory and build it using "vapor build" command:

vapor build

Let’s create xcode project using vapor xcode which will be needed later.

And verify if your backend works fine locally:

vapor run

As we can see the server is starting on our machine to be sure if that works fine, go to the web browser and check the localhost:8080.

Yupi! Here is your first application running with Perfect Swift. Let's try to deploy it to the cloud, but first, we need to install heroku cli.

brew install heroku/brew/ heroku

Now, deploy your first application to the cloud, but first you need to create free Heroku account. I will skip this process in tutorial. When the account was created go to the terminal install Heroku CLI and try to deploy.

vapor heroku init

Before we push to Heroku we have to change Heroku stack from 18 to 16 as 18 is in the beta stage and doesn’t support vapor yet.

Let’s deploy: git push Heroku master.

The app is already deployed. You can log in via a web browser to your Heroku account and see if it is running and if it works!
Now we need to configure the project. Go to Xcode or other source editor and install PostgreSQL framework. In Package.swift we need to add the FluentPostreSQL. In Package.swift we need to add the FluentPostreSQL.

Run vapor clean, vapor build and then vapor Xcode.

It's time to configure the database and clean up the project. First of all, go to configure.swift file and remove FluentSQL and remove related functions related to it. Then we need to configure the PostgreSQL database in our project. We need to remember that few things need to be done like at the beginning import the FluentPostgreSQL and register provider into services.

try services.register(FluentPostgreSQLProvider())

Then we need to create a database config and register the database.

// Configure a database

var databases = DatabasesConfig()

let databaseConfig: PostgreSQLDatabaseConfig

if let url = Environment.get("DATABASE_URL") {

guard let urlConfig = PostgreSQLDatabaseConfig(url: url) else {

fatalError("Failed to create PostgresConfig")

}

print(urlConfig)

databaseConfig = urlConfig

} else {

let databaseName: String

let databasePort: Int

if (env == .testing) {

databaseName = "vapor-test"

if let testPort = Environment.get("DATABASE_PORT") {

databasePort = Int(testPort) ?? 5433

} else {

databasePort = 5433

}

}

else {

databaseName = Environment.get("DATABASE_DB") ?? "vapor"

databasePort = 5432

}

let hostname = Environment.get("DATABASE_HOSTNAME") ?? "localhost"

let username = Environment.get("DATABASE_USER") ?? "robertbaran"

databaseConfig = PostgreSQLDatabaseConfig(hostname: hostname, port: databasePort, username: username, database: databaseName, password: nil)

}

let database = PostgreSQLDatabase(config: databaseConfig)

databases.add(database: database, as: .psql)

services.register(databases)

Once the database is registered we need to create our model and controller. In the example project, we have todo model and controller you could remove that as we will create a new model and controller. The idea for the app is creating a shopping list, so we need to think about what we need for that. So we have already database registered so we have to create a model which will contain our productName and id. Create ShoppingList.swift file which will be our PostgreSQL model.

import FluentPostgreSQL

import Vapor



final class ShoppingList: PostgreSQLModel {

var id: Int?

var productName: String



init(id: Int? = nil, productName: String) {

print("Shopping List Model was sucesfully created")

self.id = id

self.productName = productName

}

}



extension ShoppingList: Migration { }

extension ShoppingList: Content { }

extension ShoppingList: Parameter { }

Go back to the configuration file and register database migration for that model.

var migrations = MigrationConfig()

migrations.add(model: ShoppingList.self, database: .psql)

services.register(migrations)>

The model is already done, the database is registered so it’s time to create a controller which we gonna use for getting, creating and deleting data from our database. We have provided functions for that:

import Vapor



final class ShoppingListController {

func index(_ req: Request) throws -> Future<[ShoppingList]> {

return ShoppingList.query(on: req).all()

}



func create(_ req: Request) throws -> Future {

return try req.content.decode(ShoppingList.self).flatMap { item in

return item.create(on: req)

}

}



func delete(_ req: Request) throws -> Future {

return try req.parameters.next(ShoppingList.self).flatMap { item in

return item.delete(on: req)

}.transform(to: .ok)

}

}

Also, we need to configure the routes. Go to routes.swift and initialize ShoppingListController and create routes for the functions which we already created in the controller:

let shoppingListController = ShoppingListController()

router.get("shoppingList", use: shoppingListController.index)

router.post("shoppingList", use: shoppingListController.create)

router.delete("shoppingList", ShoppingList.parameter, use: shoppingListController.delete)

Try to build the project. Once the project is built successfully you can test that locally but first, you need to setup PostgreSQL locally. I would recommend you to configure Postgres on Heroku and push it directly. First, add Heroku add-ons. I’m using the free hobby-dev database for this project:

heroku addons:create heroku-postgresql:hobby-dev

Then:

heroku config

Edit procfile web: Run serve --env production --port $PORT --hostname 0.0.0.0

Commit the changes and push to Heroku .

gu98:VaporSwiftTutorial robertbaran$ cd HerokuVaporSwiftTutorial/

gu98:HerokuVaporSwiftTutorial robertbaran$ clear

gu98:HerokuVaporSwiftTutorial robertbaran$ heroku addons:create heroku-postgresql:shoppingList-dev

Creating heroku-postgresql:shoppingList-dev on ⬢ heroku-vapor-swift-tutorial... !

▸ Couldn't find either the add-on service or the add-on plan of "heroku-postgresql:shoppingList-dev".

gu98:HerokuVaporSwiftTutorial robertbaran$ clear

gu98:HerokuVaporSwiftTutorial robertbaran$ heroku addons:create heroku-postgresql:hobby-dev

Creating heroku-postgresql:hobby-dev on ⬢ heroku-vapor-swift-tutorial... free

Database has been created and is available

! This database is empty. If upgrading, you can transfer

! data from another database with pg:copy

Created postgresql-tetrahedral-24833 as DATABASE_URL

Use heroku addons:docs heroku-postgresql to view documentation

gu98:HerokuVaporSwiftTutorial robertbaran$ heroku config

=== heroku-vapor-swift-tutorial Config Vars

DATABASE_URL: postgres://hpeyjgreawzvrs:f42deaf2a16932b18b3a693fff7a577711e2c10c19efe731a0d0165e12d5a6c4@ec2-23-21-188-236.compute-1.amazonaws.com:5432/dab0tadl6uco6a

gu98:HerokuVaporSwiftTutorial robertbaran$

Verify the Heroku build, you can use it for that Postman or any other tool for API Development. Make post/get/delete and verify if that works. If you followed this tutorial everything should work fine.

Now let's begin work on the iOS app for creating a shopping list. Usually, I create a workspace and new project for iOS, but you can do it whatever you like. There are a few options. You can have 2 separate projects for frontend and backend, you can group everything in one project and use different targets for it or you can have a workspace with 2 projects. In this tutorial, I will be working with the workspace and 2 separates projects.

Save your backend project as a workspace from Xcode -> File menu now close the current project and open the workspace. Create SingleViewApplication for frontend application select shared scheme for it and drag n drop your project file to the workspace. Now you have to close Xcode and open again your workspace. All projects should appear properly.

Make sure that you have all schemes here and also your projects appear properly.

Let’s start working on the iOS app. This will be just quick as we need just add a button text field and table view with the list. I’m not gonna create MVVM but it’s recommended also will use just NSURLSession for it as this tutorial it’s not about it. I just want to show you how quick and easy the whole application can be created.

Go to Main.storyboard and create UI for this app first. Add tableView, button, and textField into you view Controller and setup constraints.

Create outlets for button, textField and connect delegate and dataSource for the tableView. Create an action for the button, create an extension for tableView delegate and data source. Create CellView. In your storyboard, you could create a prototype cell and use it in the data source. You could also create some labels and loading indicator to make that application a nice look. This tutorial is not about swift – iOS so I just give you an example of usage APIs here. You can find the whole project here. This for both frontend and backend with the workspace created. To be able to run it first you need to follow all the changes related to backend specific and Heroku as URL needs to be changed here for Heroku deployed an app or if you want to run it locally you need to configure PostgreSQL database on your machine and change database configuration in the configure.swift file.

Conclusion

Vapor is the best choice until you don’t need to use a non-SQL database like Kassandra as there is no easy way to configure that and make it work. As for me, this is no issue at all. This tutorial is easy but this is just to show you how that simple is writing backend in Swift. It’s the most important thing for the ios developers and also for every other delveloper as in my opinion this language is becoming to be the most popular language in a few years. I’m not a backend developer just enthusiast of server-side swift but thinking about change my frontend role to become a full stack.

Grape Up guides enterprises on their data-driven transformation journey

Ready to ship? Let's talk.

Check our offer
Blog

Check related articles

Read our blog and stay informed about the industry's latest trends and solutions.

Software development

Server Side Swift – quick start

Building Swift web applications allows you to reuse the skills you already have. However, you can also start from scratch if you are not an ”iPhone mac„ developer. This article shows you the most popular Server-Side Swift frameworks and lets you dive into the world of faster and safer APIs using Swift.

Swift was released in 2014. Ever since then, it has become a popular solution for the iOS and macOS development. Since Swift was the open source and people started playing with it. There were a lot of attempts to find other usages of Swift. They turned out to be perfect for the server-side while being faster and safer than other languages. The next step in the process was to apply Swift to the server-side development. Here, it is important to mention that Swift can be compiled on macOS as well as Linux and even Windows. Moreover, some frameworks allow developers to create cloud applications .

Just after Server-Side Swift has been released I felt the urge to take a quick look on it. It was really hard to tell if it at that point, it was ready for commercial use. Now, as I look at it, I would say it is stable enough. So let’s try to compare the most popular frameworks. Personally, I would recommend trying each framework before you even pick the one that works best for you – there are slight differences between them.

Currently, the most popular Server-Side Swift frameworks are Vapor, Perfect and Kitura. There are also a few other ones such as Zewo and NozelIO, however they’re not as popular, but that doesn’t take away from their value. As a matter of fact, I looked into each framework and my verdict is: Perfect and Vapor.

Why Server-Side Swift?

A few years ago, when Server-Side Swift was still new to me, I wasn’t convinced that it would be the optimal language for backend specific. Why? Because it worked just like every other backend solution and there was nothing particularly specific in it making it special, but still for me as an IOS developer it allowed me to be at a full-stack. I know Java and JS and, in my opinion, the newest technologies are good to get hold of mainly because they let us become better developers. I haven’t found too many tutorials or articles about it. Certainly not as many as there are about Java or JS. Therefore, you have to create lots of things on your own. In my opinion, Swift is also faster than other languages. If you want to compare it with others, here is the benchmark server-side swift frameworks vs nodeJS.

Tools

SPM - Swift Package Manager - manages the distribution Swift code, integrated with the build system to automate downloading, compiling and linking dependencies process runs on Mac and Linux.

XCode – Apple IDE to compile and debug Obj-C and Swift code.

AppCode – Jetbrains IDE for compile and debug Obj-C and Swift code.

Other text editors like Brackets, VS Code, Sublime etc. + SPM

There are also a bunch of very specific tools for the frameworks and I will tell a bit more about that in a quick overview below.

Overview

Vapor

It has a very active community and simple syntax as well. This framework is mostly focused on being written purely in Swift. Just as the Vapor - Swift is a very readable and understandable API, due to the fact that it keeps the naming and conventions.

Data formats:
JSON, XML

Databases:
MySQL, PostgresSQL, SQLite, MongoSB

Cloud:
Heroku, Amazon Web Services and Vapor Cloud

Tools:
Vapor toolbox

Perfect:

The most popular framework for the server-side swift development and as the author claims, it is production ready and can be used with commercial products. This one is highly recommended to use as it’s big, powerful and fast. The framework is updated on a monthly basis.

Data formats:
JSON, XML, native support for direct operations on files, directories and .zip files

Databases:
MySQL, PostgresSQL, SQLite, CouchDB, MongoSB, MariaDB, FileMaker

Cloud:
Heroku, Amazon Web Services

Tools:
Perfect Assistant

Kitura:

This Framework is like a WebFramework. Kitura is unique in its approach to databases. Example SQL DB types Kitura uses abstraction layer called "Kuery" instead of "Query". Kuery supports MySQL, SQLite and PostgreSQL. Kitura also has support for Redis, CouchDB, Apache Cassandra and ScyllaDB, but through native packages these are not related to Kuery.

Supported Data formats:
JSON, Aphid MQTT (IoT/IoT Messaging)

Databases:
MySQL, PostgresSQL, SQLite, CouchDB, Apache Cassandra, ScyllaDB

Cloud:
Heroku, Amazon Web Services, Pivotal Web Services, IBM Bluemix

Tools:
Kitura CLI

Summary

Server-Side Swift is a fast growing solution and can be used commercially with the newest technologies like cloud. After a few years, we have acquired a mature language and amazing frameworks which constantly make Server-Side Swift better. When it comes to the Vapor and Perfect, both of them seems to be in a pretty good shape by now. Vapor, for example, is growing quite fast since its first release in September 2016 and currently there is 3.1 version of it available. Perfect has similar qualities and was first released in November 2015. As I mentioned before, I would personally recommend trying to play a bit with each framework before choosing the right one.

Tutorials and code examples

Perfect

  • Source
  • Slack
  • Tutorials
  • Ray Wenderlich course
  • Sample code
  • Github

Vapor

  • Source
  • Discord
  • Tutorials
  • Ray Wenderlich course
  • Sample code
  • Github

Kitura

  • Source
  • Slack
  • Tutorials
  • Sample code
  • Gith u b

Swift NIO:

Released by Apple on 1st March 2018 – low-level, cross platform, event driven network application framework.

  • Github
  • NIO reference
  • Tutorial

Other frameworks

There are also Zewo and NozelIO but those just based on Swift and Zewo is a Go-Style framework, NozelIO is the node.js style framework.

  • Zewo Github
  • NozelIO Github

Swift for specific OS

  • Linux
  • Windows
Read more
Software development

Painless view controller configuration in Swift

Back in the pre-storyboard era of iOS development, developers had to write hundreds of lines of boilerplate code that served to manage UIViewController object hierarchies. Back then, some were inventing generic approaches to the configuring of controllers and transitions between them. Others were just satisfied with the ad-hoc view controller creation and presented them directly from other code controllers. But things changed when Apple introduced storyboards in iOS 5. It was a huge step forward in the UI design for iOS. Storyboards introduced an ability to visually define app screens and - what is the most important - transitions between them (called segues) in a single file. Storyboard segues allow to discard all the boilerplate code related to transitions between view controllers.

Of course, every solution has its advantages and disadvantages. When it comes to storyboards, some may note issues such as hard to resolve merge conflicts, coupling of view controllers, poor reusability etc. Some developers don’t even use storyboards because of such disadvantages. For others the advantages play a more important role. However, the real bottleneck of the storyboards is the initialization of view controllers. In fact, there is no true initialization for the view controllers presented by storyboard segues.

Problems with the view controller configuration

Let’s start from some basics. In Objective-C/Swift, in order to give an object an initial state, the initializer (  init() ) is called. This call assigns values to properties of the class. It always happens at the point where the object is created. When subclassing any class, we may provide the initializer and this is the only proper way. We may also provide such initializer for the  UIViewController subclass. However, in case such controller is created/presented using the storyboard, the segue creation takes place through a particular initializer –  init(coder:) . Overriding it in subclass may give us the ability to initialize properties added by the subclass. However, we don’t have the ability to pass additional arguments to the overridden method. Moreover, even if we had such an ability, it would make no sense. This is because for storyboard-driven view controllers there is no particular point in code which allows them to pass data to the initializer. That is, we cannot catch the moment of creation of such controller. The creation of view controllers managed by storyboard segues is hidden from the programmer. It happens when segue to the controller is triggered – either entirely handled by the system (when triggering action is set up in the storyboard file) or using  performSegue() method.

Apple, however, provides a place where we can pass some data to an already created view controller after the segue is triggered. It’s a  prepare(for : sender:) method. From its first parameter (of  UIStoryboardSegue type), we can get the segue’s destination view controller. Because the controller has already been created (initialization is already performed when triggering segue) the only option for passing the required data is to configure it. This means that after the initialization, but before the  prepare(for : sender:) is called, the properties of the controller that hold such data should not have initial value or should have fake ones. While the second option is meaningless in most cases, the first one is widely used. Such absence of data means that the corresponding controller’s properties should be of an optional type(s). Let’s take a look on the following sample:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

 if segue.identifier == "ToSomeViewControllerSegueID",

    let controller = segue.destination as? SomeViewController {

     controller.someValue = 12345

   }

 }

}

This is how the view controller configuring is implemented in most cases when dealing with segues:

  •  check segue id;
  •  get the destination view controller from the segue object;
  •  try to cast it to the type of the expected view controller subclass.

In case all conditions are satisfied we can set values to the properties of the controller that need to be configured. The problem with the approach is that it has too much service code related to verification and data extraction. It may not be visible in simple cases like the one shown above. However, taking into account the fact that each view controller in application often has transitions to several other view controllers such service code becomes a real boilerplate code we’d like to avoid. Take a look at the following example that generalizes the problem with  prepare(for : sender:) implementation.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

 if segue.identifier == "ToViewControllerA",

    let controller = segue.destination as? ViewControllerA {

     // configure  View Controller A

   }

 } else if segue.identifier == "ToViewControllerB",

    let controller = segue.destination as? ViewControllerB {

     // configure  View Controller B

   }

 } else if segue.identifier == "ToViewControllerC",

    let controller = segue.destination as? ViewControllerC {

     // configure  View Controller C

   }

 } else ...

   ...

 } else if segue.identifier == "ToViewControllerZ",

    let controller = segue.destination as? ViewControllerZ {

     // configure  View Controller Z

   }

 }

}

All those  if… else if… blocks are making code hard to read. Moreover, each block is for the a different view controller that has to be configured. That is, the more view controllers are going to be present by this one, the more  if… else if… will be added. This, in turn, reveals another problem with such configuration. There is a single method for a particular controller that does all configurations for every controller we’re going to present.

Solution

Let’s try to find the approach to the view controller configuration that may eliminate the outlined problems. We’re limited to the usage of  prepare(for : sender:) since it’s the only point where the configuration can be done. So we cannot do anything with the type of the destination view controller and with the check of segue identifier. Instead we’d like to generalize the process of configuration in a way that allows us to have a single type check and single verification for identifier. That is, check with some generalized type of destination view controller and variable segue identifier rather than enumerating all the possible concrete types/identifiers. For this, we need to pass somehow the information about the type and the segue identifier to the  prepare(for : sender:) method. We would like to have something like the following:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

 if segue.identifier == ,

    let viewerController = segue.destination as?  {

   // configure viewerController

 }

}

In order to have a single configuration code for all the controllers we need two things: unified interface to configure the controller, and a way to get the configuration data for the particular destination controller and segue identifier. Let’s define each part of the solution.

1. Unified interface for view configuration

As defined previously, configuration means setting values to one or more properties of destination view controller. So it’s natural to associate the configuration interface with the destination controller rather than with the one that triggers the segue. Obviously, each destination view controller has a different number of properties of different types to configure.

In order to provide a unified configuration interface we may implement a method for configuring each controller. We should pass there the values that will be assigned to the corresponding controller properties. To unify such method, every configured controller should have the same signature. To achieve this, we should wrap a set of passed configuring values into a single object. Then such method will always have one argument – no matter how many properties should be set. The type of the argument is a type of the wrapping object and is different for each view controller. This means that the view controller should implement a method for configuring and somehow define a type of the argument of the method. This is a perfect task for protocols with associated types. Let’s define the following protocol:

protocol Configurable {

 associatedtype ConfigurationType

 func configure(with configuration: ConfigurationType)

}

Each view controller that is going to be configured (is configurable) should conform to this protocol by implementing the  configure(with:) method and defining a concrete type for  ConfigurationType . In the easiest case where we only have one property that needs to be configured, the ConfigurationType is the type of that property. Otherwise, the  ConfigurationType may be defined as a structure or tuple to represent several values. Consider the following examples:

class SomeViewController: UIViewController, Configurable {

 var someValue: Int?

 var someObject: MyModelType?

 …

 func configure(with configuration: (value: Int, object: MyModelType)) {

   value = configuration.value

   someObject = configuration.object

   }

}

class OtherViewController: UIViewController, Configurable {

 var underlyingObject: MyObjectType?

 …

 func configure(with object: MyObjectType) {

   underlyingObject = object

   }

}

2. Defining the configuration data for view controller

Now, let’s go back to the controller that is triggering a segue. We’re going to use the configuration protocol we’ve defined. For this, we need to have data for passing it to the  configure(with:) method. This should be something as follows:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

 if let segueIdentifier = segue.identifier {

   // 1. Get configuration object

   // for segue.destination and segueIdentifier

   // 2. Pass configuration object to the destination controller

 }

}

Let's focus on how should we obtain the configuration object. Each segue is unique within a controller that triggers those segues. For each segue we have a single destination controller that has its own type of configuration. This means that segue id unambiguously defines a configuration type that should be used for configuring the destination view controller.

On the other hand, just returning the configuration of a concrete type per each segue id is not enough. If we did so, we would need to pass it somehow to a destination controller that has a type UIViewCotroller. It has nothing to do with the configuration. On the other hand, we cannot use the  Configurable protocol as a type of an object directly because it has an associated type constraint. That is, we cannot cast the destination view controller to the  Configurable type like as follows:
 (segue.destination as? Configurable)?.configure(with: data) . Instead, we need to use some proxy generic type that is constrained to being a Configurable.

Also, creating all the configuration objects for the controllers in a single method has no sense since it brings the same issue as the one with  prepare(for ) . That is, in this case we have a concentration of code intended to configuring different objects in a single method. Instead, the better solution is to group the code for creating the particular configuration and the type of the controller which is configured into a separate object. Consider the following example:

class Configurator {

 let configurationProvider: () -> ConfigurableType.ConfigurationType



 init(configuringCode: @escaping () -> ConfigurableType.ConfigurationType) {

   self.configurationProvider = configuringCode

 }



 func performConfiguration(of object: ConfigurableType) {

   let configuration = configurationProvider()

   object.configure(with: configuration)

 }

}

In the code above, a single  Configurator<T> instance is responsible for configuring the controller of a particular type. The code that creates the configuration is injected to the configurator in the  init() method during creation.

According with the reasoning given above, we should associate a segue ID with the particular configuration and type. Considering the approach with the  Configurator<T> , the easiest way to do it is to create a mapping object where the key is a segue ID and a value is the corresponding  Configurator<T> instance. We may also create those  Configurator<T> objects in place of the map definition. This will make the code more clear and readable. The following example demonstrates such map:

var segueIDToConfigurator: [String : Any] {

 return [

   "ToSomeViewControllerSegueID": Configurator {

     return (value: 123, object: MyModelType())

   },

   "ToOtherViewControllerSegueID": Configurator {

     return MyObjectType()

   }

 ]

}

Let’s now try to use the configuration from the dictionary above in  prepare(for ) method. Let’s take a look at the following example

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

 if let segueIdentifier = segue.identifier,

    let configuring = segueIDToConfigurator[segueIdentifier] as? Configurator {

     configuring.performConfiguration(of: segue.destination)

 }

}

The problem is that the value type of the dictionary  segueIDToConfigurator is  Any . We cannot call on it any method directly. Instead, we need to cast it to the type that contains the  performConfiguration(of:) method. On the other hand, the only type in our implementation that contains the  performConfiguration(of:) method is the generic type  Configurator<T> . And to use it we should pass a certain type of the destination view controller in place of the generic type placeholder. At this point, the problem is in  prepare(for ) method. In this method we don’t have the information about that view controller type. Let’s try to resolve the problem. We need  Configurator<T> only to call the  performConfiguration(of:) method. Instead of having the whole interface of  Configurator<T> type inside the  prepare(for ) method we may use some intermediate interface that does not depend on a generic type and allows us to call  performConfiguration(of:) .

var segueIDToConfigurator: [String : Configuring] {

 return [

   "ToSomeViewControllerSegueID": Configurator {

     return (value: 123, object: MyModelType())

   },

   "ToOtherViewControllerSegueID": Configurator {

     return MyObjectType()

   }

 ]

}

For this, let’s create a protocol  Configuring and modify the  Configurator<T> type to make it conform to it. The example below demonstrates the refined approach.

protocol Configuring {

 func performConfiguration(of object: SomeType) throws

}



class Configurator: Configuring {

 let configurationProvider: () -> ConfigurableType.ConfigurationType



 init(configuringCode: @escaping () -> ConfigurableType.ConfigurationType) {

   self.configurationProvider = configuringCode

 }



 func performConfiguration(of object: SomeType) throws {

   if let configurableObject = object as? ConfigurableType {

     let configuration = configurationProvider()

     configurableObject.configure(with: configuration)

   } else {

     throw ConfigurationError()

   }

 }

}

Now, the  performConfiguration(of:) is a generic method. This allows us to call it without knowing the exact type of the object which is configured. The method however became throwable. This is because the type of its argument is widened so that the arbitrary type can be passed. But the method can still handle only the objects that conform to the  Configurable protocol. And if the passed object is not  Configurable we don’t have anything to do with it. In this case we throw an error.

We may now use the newly defined  Configuring protocol to define the dictionary for segue-to-configurator mapping:

This allows us to use the  Configuring objects inside the  prepare(for ) method as shown below:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

 if let segueIdentifier = segue.identifier,

    let configuring = segueIDToConfigurator[segueIdentifier] {

   do {

     try configuring.performConfiguration(of: segue.destination)

   } catch let configurationError {        

     fatalError("Cannot configure (segue.destination). " +

                "Error: (configurationError)")

   }

 }

}

Refining the solution

The above  prepare(for ) implementation is the same for any controller that is going to use the described approach. There are several ways to avoid such code duplication. But you must keep in mind that each has its downsides.
The first and the most obvious way is to use some base view controller across the project that will implement the method  prepare(for ) and the  segueIDToConfigurator property for holding configurations:

class BaseViewController {



 var segueIDToConfigurator: [String: Configuring] {

   return [String: Configuring]()

 }



 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

   if let segueIdentifier = segue.identifier,

      let configuring = segueIDToConfigurator[segueIdentifier] {

     do {

       try configuring.performConfiguration(of: segue.destination)

     } catch let configurationError {

       // throw an error or just write to log

       // if you just want to silently it ignore      

       fatalError("Cannot configure (segue.destination). " +

                  "Error: (configurationError)")

     }

   }

 }



}



class MyViewController: BaseViewController {



 // Define needed configurators

 override var segueIDToConfigurator: [String: Configuring]{

   ...

 }



}

The advantage of the first way is that any controller that subclasses  BaseViewController needs to define strictly the data that is needed for the configuration. That is, override the  segueIDToConfigurator property. However, it forces all the view controllers to subclass  BaseViewController . This makes it impossible to use the system  UIViewController subclasses like  UITableViewViewController , etc.

The second way is to use a special protocol that defines the interface of the controller that can configure other controllers. Consider the following example:

protocol ViewControllerConfiguring {

 var segueIDToConfigurator: [String: Configuring] { get }

}



extension ViewControllerConfiguring {



 func configure(segue: UIStoryboardSegue) {

   if let segueIdentifier = segue.identifier,

      let configuring = segueIDToConfigurator[segueIdentifier] {

     do {

       try configuring.performConfiguration(of: segue.destination)

     } catch let configurationError {        

       fatalError("Cannot configure (segue.destination). " +

                  "Error: (configurationError)")

     }

   }

 }



}



class MyViewController: UIViewController, ViewControllerConfiguring {



 // Define needed configurators

 var segueIDToConfigurator = ...



 // Each view controller still have to implement this method

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

   configure(segue: segue)

 }



}

This way is more flexible in comparison to the first one. The protocol can be implemented by any object that is going to configure the segue destination controller. It means that not only  UIViewController subclass can use it. Moreover, it doesn’t limit us to use only the  BaseViewController as a superclass. On the other hand, each view controller still needs to override  prepare(for ) and call  configure(segue:) method in its implementation.

Summary

In this article, I described the approach to configuring destination view controllers with clean and straightforward code when using storyboard segues. The approach is possible thanks to useful Swift concepts, such as Generics and Protocols with associated types. The code is also safe as it uses static typing wherever possible and handles errors. Meanwhile the dynamic types are concentrated in single place and the possible errors are handled only there. This approach allows us to avoid unnecessary boilerplate code in the  prepare(for ) methods. On the other hand, it makes configuring particular view controllers clearer and more robust by using a specific  Configurable protocol.

Read more
View all
Connect

Interested in our services?

Reach out for tailored solutions and expert guidance.

Stay updated with our newsletter

Subscribe for fresh insights and industry analysis.

About UsCase studiesContactCareers
Capabilities:
Legacy ModernizationData PlatformsArtificial Intelligence
Industries:
AutomotiveFinanceManufacturing
Solutions:
DataboostrCloudboostr
Resources
BlogInsights
© Grape Up 2025
Cookies PolicyPrivacy PolicyTerms of use