Parse data types on Swift
Introduction
When saving data on a Back4App Database, each entity is stored in a key-value pair format. The data type for the value field goes from the fundamental ones (such as String
, Int
, Double
, Float
, and Bool
) to more complex structures. The main requirement for storing data on a Back4App Database is that the entity has to conform the ParseSwift
protocol. On its turn, this protocol provides a set of methods to store, update and delete any instance of an entity.
In this guide, you will learn how to create and setup an entity to save it on your Back4App Database. In the project example the entity we are storing encloses information about a Recipe
.
This tutorial uses a basic app created in Xcode 12 and iOS 14.
At any time, you can access the complete Project via our GitHub repositories.
Goal
To understand how objects are parsed and stored on a Back4App Database.
Prerequisites
To complete this quickstart, you need:
- Xcode.
- An app created at Back4App.
- Follow the New Parse App tutorial to learn how to create a Parse app at Back4App.
- Note: Follow the Install Parse SDK (Swift) Tutorial to create an Xcode Project connected to Back4App.
Understanding our Recipes App
The app functionality is based on a form where one can enter information about a recipe. Depending on the information, the data type may vary. In our example the recipe has the following features:
Field | Data type | Description |
---|---|---|
Name | String | Name of the recipe |
Servings | Int | Number of servings |
Available | Bool | Determines whether the recipe is available or not |
Category | Category | A custom enumeration which classifies a recipe in three categories: Breakfast, Lunch and Dinner |
Ingredients | [Ingredient] | The set of ingredients enclosed in a custom Ingredient struct |
Side options | [String] | Names of the additional options the recipe comes with |
Nutritional information | [String:String] | A dictionary containing information about the recipe’s nutritional content |
Release date | Date | A date showing when the recipe was available |
Additionally, there are more data types which are used to implement Database functionality like relation between objects. These data types are not covered in this tutorial.
Quick reference of commands we are going to use
Given an object, say Recipe
, if you want to save it on a Back4App Database, you have to first make this object to conform the ParseSwift
protocol (available via the ParseSwift SDK
).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import Foundation
import ParseSwift
struct Recipe: ParseObject {
/// Enumeration for the recipe category
enum Category: Int, CaseIterable, Codable {
case breakfast = 0, lunch = 1, dinner = 2
var title: String {
switch self {
case .breakfast: return "Breakfast"
case .lunch: return "Lunch"
case .dinner: return "Dinner"
}
}
}
...
/// A *String* type property
var name: String?
/// An *Integer* type property
var servings: Int?
/// A *Double* (or *Float*) type property
var price: Double?
/// A *Boolean* type property
var isAvailable: Bool?
/// An *Enumeration* type property
var category: Category?
/// An array of *structs*
var ingredients: [Ingredient]
/// An array of *Strings*
var sideOptions: [String]
/// A dictionary property
var nutritionalInfo: [String: String]
/// A *Date* type property
var releaseDate: Date?
}
Before storing instances of this object in a Back4App Database, all its properties must conform the Codable
and Hashable
protocols.
We make use of the following methods for managing these objects on the Back4App Database:
The procedure for reading and updating a Recipe
object is similar since they rely on the save()
method. How a Recipe
is instantiated determines if we are creating or updating the object on the Back4App Database.
When creating a new instance we use
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var newRecipe: Recipe
// Setup newRecipe's properties
newRecipe.name = "My recipe's name"
newRecipe.servings = 4
newRecipe.price = 3.99
newRecipe.isAvailable = false
newRecipe.category = .breakfast
newRecipe.sideOptions = ["Juice"]
newRecipe.releaseDate = Date()
...
// Saves newRecipe on your Back4App Database synchronously and returns the new saved Item. It throws and error if something went wrong.
let savedRecipe = try? savedRecipe.save()
// Saves savedRecipe on your Back4App Database asynchronously, and passes a Result<ToDoListItem, ParseError> object to the completion block to handle the save proccess.
savedRecipe.save { result in
// Handle the result to check the save was successfull or not
}
And to update an existing instance, we have to provide the objectId
value which identifies the the object on the Back4App Database. A satandard update can be implemented in the following way
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let recipeToUpdate = Recipe(objectId: "OBJECT_ID")
// Update the properties you need
recipeToUpdate.name = "My updated recipe's name"
recipeToUpdate.servings = 5
recipeToUpdate.price = 5.99
recipeToUpdate.isAvailable = true
recipeToUpdate.category = .lunch
recipeToUpdate.sideOptions = ["Juice", "Coffee"]
recipeToUpdate.releaseDate = Date().addingTimeInterval(3600 * 24)
...
// Save changes synchronousty
try? recipeToUpdate.save()
// Or save changes asynchronously
recipeToUpdate.save { result in
// handle the result
}
For reading objects stored on your Back4App Database, Recipe
now provides the query()
static method which returns a Query<Recipe>
. This query object can be constructed using one or more QueryConstraint
objects in the following way
1
2
3
4
5
6
7
8
9
10
11
let query = Recipe.query() // A query to fetch all Recipe items on your Back4App Database.
let query = Recipe.query("name" == "Omelette") // A query to fetch all Recipe items with name "Omelette" on your Back4App Database.
let query = Recipe.query(["name" == "Omelette", "price" = 9.99]) // A query to fetch all Recipe items with name = "Omelette" and price = 9.99.
// Fetches the items synchronously or throws an error if found.
let fetchedRecipes = try? query.find()
// Fetches the items asynchronously and calls a completion block passing a result object containing the result of the operation.
query.find { result in
// Handle the result
}
Any deletion process is performed by calling the method delete()
on the object to be deleted
1
2
3
4
5
6
7
8
9
var recipeToDelete: Recipe
// Delete recipeToDelete synchronously
try? recipeToDelete.delete()
// Delete recipeToDelete asynchronously
recipeToDelete.delete { result in
// Handle the result
}
Step 1 - Create the Recipe App Template
We start by creating a new XCode
project. This this tutorial the project should look like this

At any time, you can access the complete Project via our GitHub repositories.
Go to Xcode, and find the SceneDelegate.swift
file. In order to add a navigation bar on top of the app, we setup a UINavigationController
as the root view controller in the following way
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = .init(windowScene: scene)
window?.rootViewController = UINavigationController(rootViewController: RecipesController())
window?.makeKeyAndVisible()
// Additional logic
}
...
}
The root view controller class (RecipesController
) for the navigation controller is a subclass of UIViewController
in which we will layout a form to create and update Recipe
objects on the Back4App Database.
Step 2 - Setup the Recipe object
Objects you want to save on your Back4App Database have to conform the ParseObject
protocol. On our Recipes app this object is Recipe
. Therefore, you first need to create this object. Create a new file Recipe.swift
and add the following
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import Foundation
import ParseSwift
struct Recipe: ParseObject {
/// Enumeration for the recipe category
enum Category: Int, CaseIterable, Codable {
case breakfast = 0, lunch = 1, dinner = 2
var title: String {
switch self {
case .breakfast: return "Breakfast"
case .lunch: return "Lunch"
case .dinner: return "Dinner"
}
}
}
// Required properties from ParseObject protocol
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
/// A *String* type property
var name: String?
/// An *Integer* type property
var servings: Int?
/// A *Double* (or *Float*) type property
var price: Double?
/// A *Boolean* type property
var isAvailable: Bool?
/// An *Enumeration* type property
var category: Category?
/// An array of *structs*
var ingredients: [Ingredient]
/// An array of *Strings*
var sideOptions: [String]
/// A dictionary property
var nutritionalInfo: [String: String]
/// A *Date* type property
var releaseDate: Date?
/// Maps the nutritionalInfo property into an array of tuples
func nutritionalInfoArray() -> [(name: String, value: String)] {
return nutritionalInfo.map { ($0.key, $0.value) }
}
}
where we already added all the necessary properties to Recipe
according to the recipes’s features table.
The Ingredient
data type is a struct holding the quantity
and the description
of the ingredient. As mentioned before, this data type should conform the Codable
and Hashable
protocols to be part of Recipe
’s properties
1
2
3
4
5
6
import Foundation
struct Ingredient: Hashable, Codable {
var quantity: Float
var description: String
}
Additionally, the property category
in Recipe
has an enumeration (Category
) as data type which also conforms the corresponding protocols
1
2
3
4
5
6
7
8
9
struct Recipe: ParseObject {
/// Enumeration for the recipe category
enum Category: Int, CaseIterable, Codable {
case breakfast = 0, lunch = 1, dinner = 2
...
}
...
}
Step 3 - Setting up RecipesController
In RecipesController
we should implement all the necessary configuration for the navigationBar
and the form used to capture all the Recipe properties. This tutorial does not cover how to implement the layout for the form. We then focus on the logic related with managing data types using ParseSwift SDK
. Below we highlight the key points in RecipesController
which allow us to understand how we implement the connection between the user interface and the data coming from your Back4App Database
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class RecipesController: UIViewController {
enum PreviousNext: Int { case previous = 0, next = 1 }
...
var recipes: [Recipe] = [] // 1: An array of recipes fetched from your Back4App Database
// Section header labels
private let recipeLabel: UILabel = .titleLabel(title: "Recipe overview")
private let ingredientsLabel: UILabel = .titleLabel(title: "Ingredients")
private let nutritionalInfoLabel: UILabel = .titleLabel(title: "Nutritional information")
// 2: A custom view containing input fields to enter the recipe's information (except nutritional info. and ingredients)
let recipeOverviewView: RecipeInfoView
// 3: A stack view containig the fields to enter the recipe's ingredients
let ingredientsStackView: UIStackView
// 4: A stack view containig the fields to enter the nutritional information
let nutritionalInfoStackView: UIStackView
// 5: Buttons to handle the CRUD logic for the Recipe object currently displayed
private var saveButton: UIButton = UIButton(title: "Save")
private var updateButton: UIButton = UIButton(title: "Update")
private var reloadButton: UIButton = UIButton(title: "Reload")
var currentRecipeIndex: Int? // 6: An integer containing the index of the current recipe presenten from the recipes property
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
setupViews()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
handleReloadRecipes()
}
private func setupNavigationBar() {
navigationController?.navigationBar.barTintColor = .primary
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.barStyle = .black
navigationItem.title = "Parse data types".uppercased()
}
private func setupViews() {
... // See the project example for more details
saveButton.addTarget(self, action: #selector(handleSaveRecipe), for: .touchUpInside)
updateButton.addTarget(self, action: #selector(handleUpdateRecipe), for: .touchUpInside)
reloadButton.addTarget(self, action: #selector(handleReloadRecipes), for: .touchUpInside)
}
...
}
Step 3 - Handling user input and parsing a Recipe object
In a separate file (called RecipesController+ParseSwiftLogic.swift
), using an extension we now implement the methods handleSaveRecipe()
, handleUpdateRecipe()
and handleUpdateRecipe()
to handle the input data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import UIKit
import ParseSwift
extension RecipesController {
/// Retrieves all the recipes stored on your Back4App Database
@objc func handleReloadRecipes() {
view.endEditing(true)
let query = Recipe.query()
query.find { [weak self] result in // Retrieves all the recipes stored on your Back4App Database and refreshes the UI acordingly
guard let self = self else { return }
switch result {
case .success(let recipes):
self.recipes = recipes
self.currentRecipeIndex = recipes.isEmpty ? nil : 0
self.setupRecipeNavigation()
DispatchQueue.main.async { self.presentCurrentRecipe() }
case .failure(let error):
DispatchQueue.main.async { self.showAlert(title: "Error", message: error.message) }
}
}
}
/// Called when the user wants to update the information of the currently displayed recipe
@objc func handleUpdateRecipe() {
view.endEditing(true)
guard let recipe = prepareRecipeMetadata(), recipe.objectId != nil else { // Prepares the Recipe object for updating
return showAlert(title: "Error", message: "Recipe not found.")
}
recipe.save { [weak self] result in
switch result {
case .success(let newRecipe):
self?.recipes.append(newRecipe)
self?.showAlert(title: "Success", message: "Recipe saved on your Back4App Database! (objectId: \(newRecipe.id)")
case .failure(let error):
self?.showAlert(title: "Error", message: "Failedto save recipe: \(error.message)")
}
}
}
/// Saves the currently displayed recipe on your Back4App Database
@objc func handleSaveRecipe() {
view.endEditing(true)
guard var recipe = prepareRecipeMetadata() else { // Prepares the Recipe object for storing
return showAlert(title: "Error", message: "Failed to retrieve all the recipe fields.")
}
recipe.objectId = nil // When saving a Recipe object, we ensure it will be a new instance of it.
recipe.save { [weak self] result in
switch result {
case .success(let newRecipe):
if let index = self?.currentRecipeIndex { self?.recipes[index] = newRecipe }
self?.showAlert(title: "Success", message: "Recipe saved on your Back4App Database! (objectId: \(newRecipe.id))")
case .failure(let error):
self?.showAlert(title: "Error", message: "Failed to save recipe: \(error.message)")
}
}
}
/// When called it refreshes the UI according to the content of *recipes* and *currentRecipeIndex* properties
private func presentCurrentRecipe() {
...
}
/// Adds the 'Next recipe' and 'Previous recipe' button on the navigation bar. These are used to iterate over all the recipes retreived from your Back4App Database
private func setupRecipeNavigation() {
...
}
/// Reads the information the user entered via the form and returns it as a *Recipe* object
private func prepareRecipeMetadata() -> Recipe? {
let ingredientsCount = ingredientsStackView.arrangedSubviews.count
let nutritionalInfoCount = nutritionalInfoStackView.arrangedSubviews.count
let ingredients: [Ingredient] = (0..<ingredientsCount).compactMap { row in
guard let textFields = ingredientsStackView.arrangedSubviews[row] as? DoubleTextField,
let quantityString = textFields.primaryText,
let quantity = Float(quantityString),
let description = textFields.secondaryText
else {
return nil
}
return Ingredient(quantity: quantity, description: description)
}
var nutritionalInfo: [String: String] = [:]
(0..<nutritionalInfoCount).forEach { row in
guard let textFields = nutritionalInfoStackView.arrangedSubviews[row] as? DoubleTextField,
let content = textFields.primaryText, !content.isEmpty,
let value = textFields.secondaryText, !value.isEmpty
else {
return
}
nutritionalInfo[content] = value
}
let recipeInfo = recipeOverviewView.parseInputToRecipe() // Reads all the remaining fields from the form (name, category, price, serving, etc) and returns them as a tuple
// we collect all the information the user entered and create an instance of Recipe.
// The recipeInfo.objectId will be nil if the currently displayed information does not correspond to a recipe already saved on your Back4App Database
let newRecipe: Recipe = Recipe(
objectId: recipeInfo.objectId,
name: recipeInfo.name,
servings: recipeInfo.servings,
price: recipeInfo.price,
isAvailable: recipeInfo.isAvailable,
category: recipeInfo.category,
ingredients: ingredients,
sideOptions: recipeInfo.sideOptions,
nutritionalInfo: nutritionalInfo,
releaseDate: recipeInfo.releaseDate
)
return newRecipe
}
/// Called when the user presses the 'Previous recipe' or 'Next recipe' button
@objc private func handleSwitchRecipe(button: UIBarButtonItem) {
...
}
}
Step 4 - Run the app!
Before pressing the run button on XCode
, do not forget to configure your Back4App
application in the AppDelegate
class!
The first time you run the project you should see something like this in the simulator (with all the fields empty)

Now you can start entering a recipe to then save it on your Back4App Database. Once you have saved one recipe, go to your Back4App dashboard and go to your application, in the Database section you will find the class Recipe
where all recipes created by the iOS
App.
In Particular, it is worth noting how non-fundamental data types like Ingredient
, Recipe.Category
or dictionaries are stored. If you navigate through the data saved under the Recipe
class, you will find that
- The
nutritionalInformation
dictionary is stored as a JSON object. - The
[Ingredients]
array is stored as an array of JSON objects. - The enumeration
Recipe.Category
, since it is has an integer data type asRawValue
, it is transformed to aNumber
value type. - The
releaseDate
property, aDate
type value in Swift, is also stored as aDate
type value.
To conclude, when retrieving data from your Back4App Database, you do not need to decode all these fields manually, ParseSwift SDK
does decode them automatically. That means, when creating a query (Query<Recipe>
in case) to retrieve data, the query.find()
method will parse all the data types and JSON objects to return a Recipe
array, there is no additional parsing procedure to implement.