Back to blog

How To: Build A Blockchain App With Swift


Aasif Khan
By Aasif Khan | December 28, 2021 10:09 pm  | 10-min read

In this tutorial we are going to build a blockchain app. Blockchain technology is hot right now, and it has an immense amount of potential. But did you know that the future of the blockchain is building apps and services on top of the blockchain?

I’ll show you how in this tutorial. You’ll learn how the blockchain works, what proof of work is for, and I’ll show you step-by-step how to build an app that interacts with the blockchain.

The blockchain app we are going to build together is called Habitat, and you can use it to purchase fictive habitats on Mars.

Always wanted to put your mark on the Schiaparelli crater? Now you can. And best of all: your purchase is immortalized in the blockchain! Yes, it’ll stay there forever. I’ll show you what that means in this tutorial.

We’re not going to build the blockchain itself. There are plenty of smart people doing that!

We’ll use a webservice to connect to a Blockchain-as-a-Service, called Tierion. Why? So we can build on top of the blockchain, because that’s where the real value of the blockchain technology lies.

After completing this iOS tutorial, I want you to be able to build your own blockchain app – for whatever purpose – on top of blockchain technology. Let me know what you come up with!

What’s A Blockchain?

Let’s start by finding out what a blockchain really is. It’s awesome, important, and not that complicated.

Ledgers

A blockchain is a so-called “ledger” of facts. And a ledger is like a database, with rows and columns.

  • You can read entries from the ledger
  • And you can write entries to the ledger

You’ve probably used ledgers all your life without knowing it. Your iPhone contacts list is a ledger, and so is your back-off-the-napkin list of vacation expenditures, and so is last week’s shopping list.

The ledger is decentralized in a blockchain, which means that it does not have a central point of storage. Compare this with the opposite: a bank that has saved your money or valuables in one central vault. When the bank gets robbed, your money is gone.

The blockchain is stored across multiple computers, via a peer-to-peer network, replicated on each one of them. Members of this network are called nodes. Every node has a copy of the blockchain. Blockchain nodes use cryptography to securely communicate with each other, and verify the identities of sender and receiver.

The big problem that blockchain solves is so-called consensus. Let’s look at an example.

I know about ledgers mostly from a video game called L.A. Noire, published by Rockstar Games. In the game, that’s set in Los Angeles in 1947, you’re a police detective and you have to solve murders.

One way to gather evidence in your investigation is by checking out ledgers in shops, for instance for the sale of guns. In one murder case, you can find out that Bob bought a gun similar to the murder weapon in a gun shop close to the murder location. That’s a pretty helpful ledger!

Which Ledger Entry Is Right?
But how do you know that the ledger contains the right information? That’s a big question!

You’d typically solve that by means of authority and trust. You trust that the shopkeeper puts the correct entries in his ledger. You can also verify entries in the ledger with other independent data sources.

A few examples:

  • You trust the bank that they’re keeping verified records of the amount of money in your bank account
  • We trust the judicial system, courts and judges to apply the law consistently and fairly
  • You trust a new hire that their CV is truthful, and you verify that with former employers

In most of these examples you rely on the trust, integrity and authority of an intermediary. When your trust in this intermediary fails, all hell breaks loose. When you can’t trust the independent intermediary anymore, who can you trust?

The blockchain now has a problem. Because it’s decentralized, there is no central source you can trust. How can you trust any of its nodes? How do you add correct entries to the ledger? What’s keeping a node from sending false information for its own benefit?

Proof of Work

The blockchain uses a principle called proof-of-work. Before a fact is accepted as true, you need to prove that you did some work.

Here’s how it works:

  • The facts in our ledger are part of a block. The block contains a group of facts. Facts can contain any sort of data.
  • All blocks are part of a chain. Every block points to the previous block in the chain.
  • There is only one chain of blocks (“blockchain”) in the entire system, and every node has an identical copy.
  • Facts that haven’t been added to a block yet are pending or “unconfirmed”.
  • Special nodes in the network, called miners, create new blocks from pending facts. Miners compete over who gets to add the new block to the chain.
  • Every miner tries to find the solution to a hard mathematical problem. This is done with a cryptographically secure hashing algorithm.
  • Such a hash is hard to calculate, but easy to validate once it has been found. (Securely storing passwords, as a hash, works in a similar way.)
  • The only way you can find the solution is by brute-forcing. Miners try random combinations until they find a result that is correct.
  • When a miner has successfully found the solution, the facts in that block are considered confirmed. The miner gets a reward.
  • The block is sent to other nodes for validation and becomes part of the blockchain.

Every block contains a reference to the previous block. Because of the way hashing works, you can’t change a block once it has been confirmed. So you can’t easily go back in time and change a block earlier in the chain.

The blocks of popular cryptocurrencies, like Bitcoin and Ethereum, contain monetary transactions. When a transaction is confirmed, you can ensure that the money can only get spent once.

Miners get a reward when they successfully mine a block. Such a reward, like bitcoin tokens, is typically worth a lot of money. That’s why they keep mining!

Thanks to the proof-of-work principle and cryptographically secure hashing algorithms, the blockchain is secure. You can only spend your bitcoin once. But how does that work for blockchains that aren’t used for monetary purposes?

The explanations in this chapter are deliberately kept simple. I’m sure I made mistakes somewhere. You need a PhD in cryptocurrency if you want to understand 100% how this stuff works. Did you spot a mistake? Feel free help us understand blockchain technology better by leaving a comment below!

How To: Blockchain-as-a-Service

The Bitcoin blockchain works because bitcoin are worth money. Miners mine bitcoin, and confirm monetary transactions, because they’re incentivized to do so. As a result, we can pay for things with bitcoin.

If you want to build an app on top of blockchain technology, to provide proof of some facts, like housing prices, medical records or education certificates, then do you need miners?

Yes.

Think about it like this. Confirming monetary transactions is just a service that miners provide. Anything that provides a proof of facts is a service too.

Just as a service-providing developer, writer, or designer charges money for their time and creativity, the facilitator of a blockchain can charge for the proof of facts that their blockchain provides.

An organisation can pay miners with the money it makes from providing the blockchain service. It can do so with cryptocoins, or by using other means. Reading data from the blockchain is free, but writing to it costs money. It’s an intriguing system, isn’t it?

Blockchain-as-a-service is capitalism at its finest. You need the proof, and we’ve got the miners who can provide it.

Some permissioned blockchains don’t use proof of work, but instead rely on other ways to verify facts. These blockchains don’t need miners, and as a result, some consider them shared ledgers instead of blockchains.

A great number of companies offer Blockchain-as-a-Service platforms, including the typical giants like Microsoft, IBM, Amazon, SAP and Oracle. Interesting projects include Hyperledger, from the Linux Foundation, and R3, that’s backed by a ton of big banks.

In this tutorial we’re going to use the public Blockchain-as-a-Service, or blockchain proof engine, Tierion. Tierion has a simple and powerful API, and we are going to use it to store data in the blockchain, read from it, and verify that data.

The Tierion platform has an added benefit: they’re using the Chainpoint open standard to link their own blockchain to the larger public Bitcoin and/or Ethereum blockchains. It’s like a chaining of blockchains by using a so-called Merkle tree, that ultimately leads to a chain of proof that’s linked to the larger blockchains.

What that means in layman’s terms is that, when using a Blockchain-as-a-Service such as Tierion’s, you’re not just relying on the integrity of their organization and platform, but also using the decentralized power of the Bitcoin and Ethereum blockchains. It’s another level of chain-of-proofs and security.

Important: In this iOS tutorial we’ll directly interact with the public Tierion API, for educational purposes. You’d never do that in a production app! I’ll explain more going forward, but if you are building a production-ready blockchain app, you’ll want to use a go-between webservice for authentication, logging and access control, depending on the requirements of your app.

Now, back to the blockchain.

Blockchain technology is as useful as the purpose for which you use it. Although it is compelling to become a facilitator of blockchain technology, I think the real innovations will be made by small companies that build on top of the blockchain. That’s you, by the way!

Some ideas for blockchain apps:

  • Proof that you are who you say you are, by storing your DNA in the blockchain, linked to your digital identity
  • Provide proof that you bought the house you live in, and for what price, and who had it before you
  • Create access control systems where you can’t delete logs, like they always do in spy movies
  • Upload proof of your CV, badges, honours, former employers, pay-grades etc.
  • Provide doctors with medical records, and some form of access permissions, and making them only readable and portable by their owner
  • Provide escrow-like services with smart contracts, to make sure tenants pay their rent on time
  • Customer reviews and testimonials you can actually trust and verify
  • A “chain” of who bought that eBay product or second-hand car before you
  • Concert tickets, conference passes – basically any kind of ticket or access pass
  • First Order access codes that can’t be falsified, copied or used more than once (sorry, rebels!)

When implemented correctly, blockchain technology allows you to verify the truth.

I think that the current state of blockchain technology is only 1% of its full potential. It’s insane! (On a side note, just because it says “blockchain” doesn’t mean it’s 10x better than the solution we use now. Means vs. ends!)

Alright, enough about blockchain technology. Let’s use it to build a blockchain app!

Blockchain App: Purchasing Habitats On Mars

This tutorial wouldn’t be much fun if it didn’t show you how to create a blockchain app. So that’s what we’re going to do next!

The blockchain app we’ll build is called Habitat. Here’s how it works:

  • The app lists ~ 10 habitats for sale on Mars
  • Users of the app can purchase these habitats
  • The blockchain stores the rights of these purchases

Think of it like a land registry or cadastre, that stores proof that Bob is the truthful owner of Bob’s Red Fantasy Land in the Schiaparelli crater on Mars.

When a user taps one of the cells, they can choose to Purchase or Verify a habitat. Any habitat will show its most recent owner. You can also track the blockchain status of a purchase with the app.

We’ll use two datastructures in our app:

  • The names of Mars craters where users can build habitats, as an array that includes a name and a price:
  • let habitats = [
    [“Schiaparelli”, 35000],
    [“Huygens”, 50000],
    [“Copernicus”, 250000]
    ]

  • The purchase rights of Mars craters and their owners, stored in the blockchain as simple objects:
  • var purchase = Purchase()
    purchase.habitat = “Schiaparelli”
    purchase.owner = “Elon Musk”
    purchase.date = “March 14, 2018”
    purchase.recordID = “IAgIOru770WwipjxJXj1zQ”

Note: It’s only logical that we use Mars’ unofficial currency for habitat prices: the Marsoleon, with symbol M.

We’ll use a number of tools and frameworks to build our app:

  • Xcode 9 and Swift 4, for iOS 11, of course
  • CocoaPods to manage dependencies
  • Tierion, a blockchain-as-a-service with a simple but powerful REST API
  • Alamofire, a super useful networking library for Swift

This tutorial assumes you have at least a basic understanding of Swift and Xcode, and that you’re familiar with CocoaPods, networking, JSON and REST APIs. If you feel out of your depth, I recommend you check out my iOS development course.

Set Up The Habitat Blockchain App Project

We are going to start with setting up the blockchain app project in Xcode. The steps are roughly as follows:

  1. Create the project in Xcode
  2. Make a Podfile and install pods with CocoaPods
  3. Set up a Tierion account
  4. Create a Datastore and write down its ID

Let’s go!

1. Create The Project In Xcode

Creating the project in Xcode is as easy 1-2-3. Here’s how:

  • Start Xcode
  • Choose File -> New -> Project…
  • Choose the Single View App and click Next
  • Name the project HabitatApp, set your Organization Name and Identifier and click Next
  • Select a convenient location to save and click Create

2. Make A Podfile And Install Pods With CocoaPods

Do this to set up the pods for this project:

  • First, right-click on the HabitatApp project in the Project Navigator on the left and choose New File….
  • Then, scroll down, choose the Empty template from Other and click Next.
  • Then, name the file Podfile (no extension, just “Podfile” with an uppercase “P”) and make sure to save it alongside the HabitatApp.xcodeproj file.
  • Then, add the following code to the podfile. This tells CocoaPods we want to use the Alamofire pod in our app project.
  • source ‘https://github.com/CocoaPods/Specs.git’
    platform :ios, ‘11.0’
    use_frameworks!

    target ‘HabitatApp’ do
    pod ‘Alamofire’
    end

  • Then, save the file, then close the Xcode project, and then close Xcode.
  • Then, use Terminal to go to your project folder with cd and type this on the command-line: pod install. This installs Alamofire into the Xcode project.
  • Finally, open the new HabitatApp.xcworkspace workspace file in Xcode.

3. Set Up A Tierion Account

Our blockchain app will use the Tierion Blockchain-as-a-Service (BaaS) platform. You’ll need a free account to use their API.

Here’s how you get one:

  • First, go to https://v1.tierion.com/signup
  • Then, fill out the fields to create your account
  • Then, complete the steps in the onboarding process
  • Then, when asked, create your first Datastore named HabitatApp (don’t forget this step)
  • Finally, go to your Tierion Dashboard at https://v1.tierion.com/app

If you already have a Tierion account, or if you forgot to make a Datastore, you can create the `HabitatApp` datastore on your Dashboard with the `+`-button in the bottom-left of the Dashboard page.

4. Get Tierion API Key And Datastore ID

Next, you’ll need to note two things:

  • Your Tierion API Key (needed later)
  • The ID of the Datastore you just created (needed later)

Here’s how you get them:

  • First, select your HabitatApp Datastore in the list on the left
  • Then, click on the API tab at the top
  • Then, locate the Tierion API Key and Datastore ID on the page
  • Finally, copy both to a text file, so you can get them later

Create The Habitat Table View Controller

Our blockchain app will use a simple table view to display information about the habitats and their owners.

Here’s what we’ll do next:

  1. Delete the boilerplate view controller from our Xcode project
  2. Create a new table view controller class
  3. Set up the new table view controller in our app’s Storyboard

1. Delete Boilerplate View Controller

The Single View App template contains a boilerplate view controller. We don’t need it, so let’s delete it.

Here’s how:

  • First, locate the ViewController.swift file in the Project Navigator and delete it, by right-clicking and choosing Delete, and then Move to Trash.
  • Then, open Main.storyboard in Interface Builder
  • Finally, locate the View Controller Scene in the Document Outline on the left, and delete it with the Backspace key

2. Create A New Table View Controller Class

Next, let’s create that table view controller we need.

As you know, a table view controller is just a view controller with a table view as its view property, and a few extra delegates.

Here’s what you need to do:

  • First, right-click on the HabitatApp folder in the Project Navigator and choose New File…
  • Then, choose the Cocoa Touch Class template and click Next
  • Then, type UITableViewController in the Subclass of field
  • Then, type HabitatTableVuewController in the Class field
  • Then, make sure that Also create XIB file is unchecked and click Next
  • Then, save the file in the same folder as the AppDelegate.swift file and make sure that HabitatApp is checked for Targets
  • Finally, click Create

The new table view controller class appears. Feel free to remove the code comments, so your code looks a little bit more like mine. Also, I usually put view controllers in their own folder in the Project Navigator.

For the remainder of this tutorial, next time when you’ll need to create a new file, you now know what to do. Right?

3. Set Up The Table View Controller In Storyboard

Awesome! We just removed the only view controller of our blockchain app from the app’s Storyboard. Let’s add another back in!

Here’s how:

  • First, open Main.storyboard in Interface Builder (click on it in the Project Navigator)
  • Then, in the Library in the bottom right, locate the Table View Controller component and drag it to the storyboard’s canvas
  • Then, with the table view controller selected, tick the Is Initial View Controller setting, in the Attributes Inspector, so it becomes checked
  • Then, with the table view controller selected, set the Class to HabitatTableViewController in the Identity Inspector (important!)
  • Finally, with the Table View Controller Scene selected, choose Editor -> Embed In -> Navigation Controller

That last step wraps the table view in a navigation controller. Even though we won’t use navigation in this blockchain app, it gives a nicer structure to the app’s user interface.

You can also set the navigation title to “Habitats”, by setting the Title field of the Navigation Item of the table view controller (with the Attributes Inspector).

When you run the app with Command-R or the Play button at this point, the navigation bar and table view controller should show up nicely!

Creating The Custom Table View Cell

Our blockchain app will display information about habitats on Mars, including their owners and purchase prices.

The table view isn’t of much use to us right now, because it can’t display that data. Let’s change that!

We’re going to create a custom table view cell. That’s a UITableViewCell subclass with a custom XIB file, that contains layout information.

Here’s what we’ll do:

  1. Create the UITableViewCell subclass
  2. Set up its properties
  3. Code a commonInit() function
  4. Code a set(name:owner:price:) function
  5. Configure the custom cell XIB in Interface Builder

Get a move on!

1. Create The UITableViewCell Subclass

First, create a new file with the following settings:

  • Template: Cocoa Touch Class
  • Class: HabitatCell
  • Subclass of: UITableViewCell
  • Also create XIB file: checked

2. Set Up HabitatCell’s Properties

Then, we’re going to create 3 properties, one for each of the UI elements this table view cell has.

Add these 3 properties to the HabitatCell class:

  • nameLabel of type UILabel
  • ownerLabel of type UILabel
  • priceLabel of type UILabel

Also do this:

  • Mark the properties with @IBOutlet and weak
  • Make sure that the property types are optionals

You’ll (hopefully) end up with something like this:

@IBOutlet weak var nameLabel:UILabel?
@IBOutlet weak var ownerLabel:UILabel?
@IBOutlet weak var priceLabel:UILabel?

@IBOutlet attribute tells Interface Builder that these properties can be connected to a UI element, as an outlet. The weak attribute ensures that an instance of HabitatCell doesn’t retain references to these outlets when the cell gets deallocated, and helps us to avoid a strong-retain cycle. Lastly, the properties are optionals because outlets can be nil during their life-time.

3. Code A “commonInit()” Function

You already know that table view controllers efficiently reuse their cells, so they won’t have to be constantly created anew.

Every table view cell has two functions, awakeFromNib() and prepareForReuse(), to help with cell reuse.

  • When a table view cell is newly created, the awakeFromNib() function is called
  • When a table view cell is reused, the prepareForReuse() function is called

In both functions, you’ll want to set the UI of the cell to a default value. This “resets” the cell and ensures that a reused cell won’t accidentally contain the UI of a previous iteration of that cell. (If it does, you forgot to implement those 2 functions…)

So, long story short, we’re going to use a function called commonInit() to code that resetting once, and use it twice.

Add the following function to the HabitatCelll class:

func commonInit()
{
nameLabel?.text = “”
ownerLabel?.text = “”
priceLabel?.text = “”
}

Easy-peasy, right? This function resets each of the labels to contain an empty string.

Next, call this new commonInit() function in awakeFromNib(). That function now looks like this:

override func awakeFromNib()
{
super.awakeFromNib()

commonInit()
}

Finally, create a new function prepareForReuse(). This function needs to be overridden from the superclass with the override keyword. You’ll also need to call the superclass implementation for prepareForReuse(). And of course, you need to call commonInit().

That function now looks like this:

override func prepareForReuse()
{
super.prepareForReuse()

commonInit()
}

You now have 3 functions set up:

  • awakeFromNib()
  • prepareForReuse()
  • commonInit()

And the first two functions both call commonInit() in their function bodies. Awesome!

Do you notice how I’m first explaining the steps we are going to take, and only later on give you the right Swift code for that step? I do that deliberately! It’s easy to fast-forward through this tutorial by just copying-and-pasting the source code. You learn nothing that way! Instead, pace yourself, complete the steps as I describe them, and check your work with the Swift code samples.

4. Code A “set(name:owner:price:)” Function

Later on in this tutorial, we’ll use the custom table view cell together in the table view controller.

The table view controller will load the cell, and based on the habitat data, we fill that cell up with the right information. Technically, we could access the properties of the cell directly – but that stinks.

Instead, we’ll create a set(name:owner:price:) function. It’ll set the properties. Doing this in your own apps has a few benefits:

  1. It (slightly) reduces the strong-coupling between the table view controller and the cell
  2. It separates the concerns of the table view controller and the cell
  3. It makes the cell responsible for the UI of the cell, and not the table view controller

Let’s code it! Here are the rough steps:

  • Start with the function declaration. Give it 3 parameters, name of type String, owner of type String? and price of type Int
  • In the function body, use the parameters to assign a text to each of the cell’s labels: nameLabel, ownerLabel and priceLabel

You can add a few nice-to-haves:

  • When a habitat does not have an owner, the owner parameter will be nil. Use it to show a “For sale!” or “Currently owned by: [owner]” text depending on the ownership status.
  • Use NumberFormatter and numberStyle = .decimal to nicely format the habitat price with thousands separators

Here’s the Swift code:

func set(name: String, owner: String?, price: Int)
{
nameLabel?.text = name
ownerLabel?.text = owner == nil ? “For sale!” : “Currently owned by: \(owner!)”

let formatter = NumberFormatter()
formatter.numberStyle = .decimal

if let price = formatter.string(for: price) {
priceLabel?.text = “\(price) M”
}
}

See what happens there?

  • First, we’re assigning the habitat name parameter to the text property of the nameLabel, effectively setting the text the label shows
  • Then, we’re doing the same for ownerLabel and the owner parameter, using the ternary conditional operator a ? b : c to distinguish between a sold habitat and one for sale
  • Finally, we’re using NumberFormatter to format price as a decimal with thousands separators, and including that imaginary “M” currency symbol

At a later point, we can use the set(name:owner:price:) function like this:

cell.set(name: “Schiaparelli”, owner: “Mark Watney”, price: 50000)
This will show that information in the table view cell. Neat!

In the above code sample we’re using string interpolation with “Owned by \(owner)” to use a variable’s value in a string. We’re also using conditional binding with if let because string(for:) returns an optional value.

5. Configure Cell XIB In Interface Builder

So… we’ve written some Swift code, but we haven’t set up any user interface elements in Interface Builder. Oops!

Let’s change that. Here’s what we’ll change in HabitatCell.xib:

  • Set the cell size to 375 by 60 points
  • Add a bold label to the top-left, for the habitat name
  • Add a smaller, gray label to the bottom-left, for the owner name
  • Add a regular label to the middle-right, for the price
  • Set up some sensible Auto Layout constraints

First, change the cell size:

  1. Select the Habitat Cell in the Project Navigator
  2. Locate the Size Inspector in the panel on the right
  3. Set the size to 375 for Width and 60 for Height, in the View section

Easy! Now, add the 3 labels like this:

  1. Find the Label element in the Object Library, and drag it to the cell’s canvas
  2. Set the label text and font by selecting the label, and then using the Attributes inspector
    • For the name label: bold font, 17.0 points, black color
    • For the owner label: regular font, 14.0 points, dark gray color
    • For the price label: regular font, 16.0 points, black color
  3. Add the Auto Layout constraints for each of the UI elements
    • For the name label: top, bottom, leading and height constraints
    • For the owner label: left, bottom and height constraints
    • For the price label: right, height and Center Vertically in Container constraints

A few notes:

  • It’s easiest if you let the name label bottom edge touch the top edge of the owner label, so the constraint between them is set to 0
  • The left and right margins are about 16 points, and the top and bottom margins are about 10 points
  • Make sure that there is a continual chain of constraints from top-to-bottom, otherwise you’ll run into problems with the dynamic table view cell heights
  • When all else fails, remove your constraints, try again, or leave all of them out altogether!

Finally, connect the UI elements with their outlets! Here’s how:

  • First, select the Habitat Cell from the Document Outline on the left
  • Then, locate the Connections Inspector on the right. You should see nameLabel, ownerLabel and priceLabel in the list, with small dots on the right.
  • Finally, for each of the labels, drag from the dot next to the outlet in the list to the right UI element on the canvas.

Display Habitat Data In The Table View

Now that we have the blockchain app’s UI set up, let’s bring it to life! We’ll need to add some data to the table view.

First, open the HabitatTableViewController.swift file and add the following property to the top of the class:

let habitats = [
[“Antoniadi”, 10000],
[“Cassini”, 25000],
[“Copernicus”, 35000],
[“de Vaucouleurs”, 56000],
[“Dollfus”, 99000],
[“Greeley”, 120000],
[“Herschel”, 250000],
[“Huygens”, 314150],
[“Koval’sky”, 370000],
[“Newton”, 450000],
[“Schiaparelli”, 510000],
[“Tikhonravov”, 997000]
]

Your class now looks like this:

class HabitatTableViewController: UITableViewController
{
let habitats = [
[“Antoniadi”, 10000],
[“Cassini”, 25000],

That habitats property is a constant, with the let keyword, so it can’t change once it has been set.

We’re not specifying a type, so the Swift compiler will infer that for us, but we know that the type of habitats is [[Any]].

Surprised? We’ve created a nested array, so an array of arrays, with both String and Int objects in it. An array can only have one type, so it has to be Any to accomodate both String and Int objects.

The data in the habitats array represent the names of craters on Mars, such as Schiaparelli, and the imaginary costs (in Marsoleons) of purchasing the habitats located there.

Next up, we gotta implement the following things:

  1. Use register(_:forCellReuseIdentifier:) in viewDidLoad()
  2. Override numberOfSections(in:)
  3. Override tableView(_:numberOfRowsInSection:)
  4. Override tableView(_:cellForRowAt:)

Those three functions are delegate functions, and they are part of how a table view controller works.

At certain times, our implementations of those functions are called. They are used to provide the table view controller with information, such as the number of cells the table view contains. We can use them to customize the table view and to respond to events.

This is called delegation. On top of that, UITableViewController has default implementations of these delegate functions that we override.

1. Register The Custom Table View Cell

First, we have to tell the table view we want to use a custom cell. We’ll do so by providing the XIB and a reuse identifier.

Add this code to viewDidLoad():

tableView.register(UINib(nibName: “HabitatCell”, bundle: nil), forCellReuseIdentifier: “habitatCell”)

What happens there?

  • We’re calling the function register(_:forCellReuseIdentifier:) on tableView
  • Its first argument is UINib(nibName: “HabitatCell”, bundle: nil), which is the Interface Builder “XIB” file we created earlier
  • Its second argument is a string “habitatCell” that’s used to identify this kind of cell

Always wondered what the difference between a XIB and a NIB is? Both are Interface Builder files, i.e. they contain UI information, but a XIB uses an XML format (for Xcode) and a NIB uses a binary format (for the app binary). Therefore, Swift functions that use these UI files consistently use the word “nib”.

Then, your table view controller implementation should already include stubs for these 2 functions:

  • numberOfSections(in:)
  • tableView(_:numberOfRowsInSection:)

Adjust the functions so that…

  • … there’s only 1 section
  • … the number of rows is equal to the number of items in the habitats array

Your code now looks like this:

override func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}

And this:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return habitats.count
}

Every collection in Swift, such as arrays and dictionaries, that conform to the Collection protocol, have a property count that’s equal to the number of items in that collection.

Finally, we need to change one last function: tableView(_:cellForRowAt:). This function is used to provide table view cells to the table view.

  • The input of tableView(_:cellForRowAt:) is, along with a table view instance, a value of type IndexPath, that contains the section and row index of the cell.
  • The output of this function is of type UITableViewCell, so that’s the cell we need to create and provide back to the table view controller.

Think of a table view as a spreadsheet with rows. Rows can be grouped together in a section. You need to provide the cells for the table view with tableView(_:cellForRowAt:). Here’s how it works:

  • The number of cells you need to provide are based on the return values of numberOfSections(in:) and tableView(_:numberOfRowsInSection:).
  • The function tableView(_:cellForRowAt:) is called every time the table view controller needs a cell. The function gets the row and section indices for that cell via the indexPath parameter.
  • You get where this is going. We can get the data for that cell by using the indexPath.row value as the index of your data source collection.

Can you confirm for yourself that in our blockchain app, the indexPath.section is always 0, and that the indexPath.row runs from 0 to 11 (12 in total)? Great!

Fun fact: The table view controller doesn’t request all needed cells at once. It will call tableView(_:cellForRowAt:) selectively, so only for cells that are inside the viewport.

Your implementation of tableView(_:cellForRowAt:) needs to do this:

  1. Dequeue a reusable table view cell with identifier “habitatCell”
  2. Force-downcast with as! that cell to HabitatCell, or use conditional binding with as?
  3. Get the habitat and price from habitats using the indexPath
  4. Call set(name:owner:price:) on cell with the right arguments
  5. Return the cell

And here’s what that looks like in Swift code:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: “habitatCell”, for: indexPath) as! HabitatCell

if let habitat = habitats[indexPath.row][0] as? String,
let price = habitats[indexPath.row][1] as? Int
{
cell.set(name: habitat, owner: nil, price: price)
}

return cell
}

This is what happens in tableView(_:cellForRowAt:):

  • First, we’re dequeueing a cell using the “habitatCell” identifier and the indexPath. This is how the dequeue mechanism of a table view controller works. It has a cache of cell objects that can be reused, because that’s quicker than creating new objects every time a cell is needed.
  • Then, the return type of that function is UITableViewCell. We need it to be of type HabitatCell, so we can use our set(…) function, so we force-downcast the cell to HabitatCell. The cell object doesn’t change, but we treat as if it has the HabitatCell type.
  • Then, we’re decomposing habitats to get to the right data:
  • First, we can get to the right item in the habitats array by using indexPath.row.
  • Then, we use array indices 0 and 1 for the first and second elements respectively to get to habitat and price. (Check this with the habitats array! See how it works?)
  • Finally, we’re using optional casting to go back from Any to String and Int. We’re combining this with optional binding, so the values get neatly assigned to non-optional constants habitat and price.
  • Then, using habitat and price as arguments, we call cell.set(…). This is the function we created earlier, that puts the values as text in the right UI elements. Note how we’re using nil for owner, because these habitats have no owner yet.
  • Finally, we’re returning cell. Because the type of cell is a subclass of UITableViewCell we don’t have to cast it back.

Awesome! At this point you should be able to run your blockchain app with Command-R or the Play button, and see every habitat show up in the table view.

Integrating With The Blockchain API

Oh yeah! We’re getting to the good stuff. Now you’re finally going to connect to the blockchain…

You’re going to use Tierion’s blockchain API for 3 things:

  • Getting purchase data from the blockchain
  • Storing purchases in the blockchain
  • Verifying purchases with the blockchain

Especially verification is important, because you want to know if a record has successfully become part of the blockchain.

Here are the basic steps:

  1. Create a Purchase class, to structure purchase data
  2. Create an API class, for the API code
  3. Write a getAllRecords() function to get the latest Records from the API
  4. Respond to notifications from getAllRecords()
  5. Write a getLastPurchase(of:) function to get the current owner of any habitat

1. Create A Purchase Class

The purchase data, as introduced earlier in this tutorial, needs some organizing. We could pass it around as a dictionary in our code, but that’s prone to errors, so let’s create a class.

Here’s an example:

var purchase = Purchase()
purchase.habitat = “Schiaparelli”
purchase.owner = “Elon Musk”
purchase.date = “March 14, 2018”
purchase.recordID = “IAgIOru770WwipjxJXj1zQ”

With an array of Purchase objects we can find out who owns which habitat. We’ll also store this data in the blockchain, and populate our Purchase objects with data from the blockchain.

First, create a new file in Xcode. Use the Swift File template and name it Purchase.swift.

Here’s the gist of that class:

  • The class name is Purchase and it conforms to the CustomStringConvertible protocol
  • The class has 4 properties: habitat, owner and recordID of types String and date of type Date
  • Initialize the properties with empty strings and an empty Date() object
  • Add a computed property called description to the class, and return a string value that contains a description, for instance by listing all properties

And here’s how you code that…

First, add the class definition to the file:

class Purchase: CustomStringConvertible
{

}

Then, add the 4 properties to the class:

var habitat:String = “”
var owner:String = “”
var date:Date = Date()
var recordID:String = “”

See how they’re initialized with empty strings? The Date() object represents the current date and time.

Finally, add the description property:

var description:String {
return “recordID = \(recordID), habitat = \(habitat), owner = \(owner), date = \(date)”
}

This is a computed property. Instead of containing a fixed value, it executes a small function when accessed, which returns a dynamic value based on the properties of the Purchase object.

This description property is called whenever the object is treated as a string, for instance by print(), so we can effectively use it to print out a description of Purchase instances.

We’ll use this Purchase class throughout the app. Let’s continue!

This description property is described in the CustomStringConvertible protocol. You don’t have to implement it. If you don’t, an object of type Purchase will just show up as “Purchase”. When you do implement it, however, the object will be printed out with a nicely formatted string.

2. Create An API Class

Next up is the API class. It is responsible for interacting directly with the API.

We’re creating a separate class for this purpose, so it doesn’t get all tangled up in the rest of our code. We’re using the Singleton pattern to give access to one shared API instance everywhere in our code.

  • First, create the a new Swift file and name it API.swift.
  • Then, add the following two imports to the top of the file:
  • import UIKit
    import Alamofire

This allows us to use code from the Alamofire library in this file. If you haven’t yet integrated Alamofire in your project, go back to the relevant previous step.

Then, add the class itself:

class API
{

}

That’s pretty basic! Let’s spice it up with that shared instance. Add this to the class body:

static let shared = API()

This is a static constant, also known as a class property. It’s available on the class API, instead of on an instance of API. We can use it anywhere in our code like this:

API.shared.someFunction()

Interestingly, we’re referencing an instance of the class API on the class API. That’s why it’s often referred to as a shared instance.

Don’t misuse the shared instance or Singleton pattern! It’s easy to create a God class with all sorts of code and give access to it anywhere in your code, but that’s not the purpose of a singleton. You should only use the Singleton pattern for objects that you need only one instance of, that has one single purpose, that contains code that should be accessible anywhere in your app, and only when that code doesn’t belong anywhere else.

3. Write A “getAllRecords()” Function

The blockchain app works like this:

  • Show all habitats in the table view
  • Get all records from the datastore
  • Match the records’ purchase data with habitats
  • Update the table view with the new information

So, we need a way to get all records from the blockchain. The Tierion API has an endpoint for that at https://api.tierion.com/v1/records.

You provide that endpoint with a datastoreId parameter, and some authentication data, and it returs the last 100 records. It lists the most recent first, and we can use that to find the current owner of a habitat.

But first, some housekeeping!

In the blockchain app we’re going to interact with 3 endpoints of the Tierion API. Every time we do that we’ll need to provide authentication headers.

These two:

X-Username: [email protected]
X-Api-Key: WW91IGhhZCB0byBrbm93IHdoYXQncyBpbiBoZXJlLCBkaWRuJ3QgeW91IT8=

It also makes sense to save the Datastore ID somewhere, and the URL of the API. So, as the perfectly organized developers we are, what do we do? We make a struct for configuration values, of course!

struct Constants
{
static let url = “https://api.tierion.com/v1”
static let datastoreID = “…”
static let username = “…”
static let key = “…”
}

You can add it above (not inside!) the API class, or put it in its own Constants.swift file. Make sure you fill in the correct values:

  • datastoreID is the Datastore ID you wrote down earlier
  • username is the email address of your Tierion account
  • key is the Tierion API Key you wrote down earlier

Anywhere in your code you can use these values like this:

Constants.datastoreID

OK, now we’re finally going to write that getAllRecords() function. Add it to the API class, like this:

func getAllRecords()
{

}

Then, add this behemoth of a function call inside the getAllRecords() function body:

Alamofire.request(Constants.url + “/records”, method: .get, parameters: [“datastoreId”: Constants.datastoreID], encoding: URLEncoding.default, headers: headers).responseJSON { [unowned self] response in

}

Whoah! Let’s deconstruct that. In short, this function sends a GET request to the Tierion API, with a few parameters.

Here’s what it does:

  • We’re calling the request(…) function on the Alamofire framework. It has 5 parameters:
  • The first parameter is unnamed, it’s the URL of the request as String
  • The second parameter method is set to .get, which indicates that this is a HTTP(S) GET request
  • The third parameter parameters is a dictionary with… parameters. The keys and values in this dictionary form the URL query string. They’re like variables you can add to a URL, like https://api.tierion.com/v1/records?datastoreId=1234
  • The fourth parameter encoding tells Alamofire we want to encode the parameters collection using URL Encoding. Other options include JSON, for instance.
  • The fifth and last parameter headers contains a dictionary with HTTP request headers. It’s using a variable headers that we’ll code later.
  • The request(…) function is neatly chained to another function called responseJSON(completionHandler:). We provide it with a closure, also called a completion handler. This closure is executed when the HTTP request completes, so when a response has come back from the Tierion API. The closure has one parameter response of generic type DataResponse.
  • Without going into it too deeply – we’re leaving out the argument label for the completionHandler closure, because it’s the last argument. This is called a trailing closure. The [unowned self] is called a capture list, and we’re indicating that this closure shouldn’t strongly retain self.

If your app has become a hot stinking mess of nested callbacks, also known as “callback hell”, then I recommend you check out how Promises work.

You’ll still need to code that headers variable. You can add it to the API class like this:

let headers:HTTPHeaders = [
“X-Username”: Constants.username,
“X-Api-Key”: Constants.key
]

This makes the headers constant available for any function that’s part of the API class, so we can reuse those headers in different parts of the class. If you look closely, you’ll see that the headers constant contains the constants we defined earlier. They’re now used as HTTP request headers.

Pfew! And that’s just the function call – now we’re going to implement that closure…

The above screenshot is of the Mac app Paw. It’s simply the best app to inspect, test and document webservices and REST APIs.

In the above screenshot you can see the JSON response from the /records API endpoint. This is the data we’re working with:

{
“accountId”: 1234,

“records”: [
{
“id”: “2jTcN3Infk6zDTY0YbVweg”,
“label”: “Copernicus;Arthur Dent”,
“timestamp”: 1524333917
},
{
“id”: “pYiI1hYvNkG2KxwBrSueFA”,
“label”: “Schiaparelli;Mark Watney”,
“timestamp”: 1524333144
}
]
}

The JSON has one root element, an object, with an array records that contains objects that have an id, label and timestamp.

  • id is the Record ID of the Tierion API that identifies a blockchain record
  • label is a textual representation of the object, we’ll use it to save the habitat and owner with a [habitat];[owner] format
  • timestamp is a simple Unix timestamp of the record’s creation date and time

You can read more about Tierion’s blockchain API in their documentation.

Remember how we’re using the blockchain to match habitats with their owners? When processing the JSON, all we really need to do is decompose the above data format and turn that into an array of Purchase objects.

Let’s do it step by step. In the next few paragraphs I’ll show you how to code this part of the app. The complete implementation of the getAllRecords() comes after that.

First, we need a way to save any Purchase objects we create. So add the following instance property to the API class:

var purchases = [Purchase]()

Easy, right? You can add it right after let headers … and right before the func getAllRecords() …. The property is initialized as an empty array of Purchase objects.

Note: In a properly architected blockchain app you wouldn’t use the API class to store structured data. Instead, you’d use Realm or Core Data, and save objects in the database. For the purposes of this tutorial however, we’ll use the above purchases array as our local datastore.

Here’s that HTTP request again. Inside the closure we have access to self and response, of type DataResponse.

Alamofire.request(…).responseJSON { [unowned self] response in
// Do magic stuff…
}

Then, you need to get to the JSON data in response, and the record array in that JSON data. Like this:

if let json = response.result.value as? [String: Any],
let records = json[“records”] as? [[String: Any]]
{

}

What happens here?

  • You access the JSON data with response.result.value and cast it from Any? to the dictionary type [String: Any]
  • You access the records data by its key “records” and cast it from Any to [[String: Any]] or “array of dictionaries with string keys and any values”.

You also use conditional binding to safely cast. The conditional body won’t execute when the cast fails, because it then returns nil.

How do you know what the types of the JSON data are?

  • Most JSON data consists of arrays, dictionaries, numbers and strings.
  • Most JSON dictionaries, called objects, have String keys.
  • You can mix any type of variable in JSON, so you often use the Any type in Swift
  • When you check out the JSON data with the previous screenshot, you see that the top-level element is a dictionary, and that the “record” element is an array of dictionaries.
  • As such, the type of the entire JSON is [String: Any] and the type of the records array is [[String: Any]]

A trivia question for you: how do we know that the type of json[“records”] is Any before casting it?

Then, the next thing we do is empty the purchases array, to start with a clean slate.

self.purchases.removeAll()

Why do we write purchases.removeAll() inside the if let conditional statement, instead of outside it, or even outside the Alamofire.request(…) code? That’s because inside the conditional we’re fairly certain that the JSON data has been received in good order. And we don’t run the risk of removing the data we have, only to discover that the new data is incomplete or malformed.

Then, we loop over every item in the records array. After all, you want to process every record in the JSON! Do you know the type of item?

for item in records
{

}

Then, for every item we find we want to create a new Purchase object. Like this:

let purchase = Purchase()

Then, we want to get the Record ID and add it to the purchase object. Like this:

if let recordID = item[“id”] as? String {
purchase.recordID = recordID
}

What kind of magic is that!?

  • Check the JSON data again. Every records item has an “id” key, so we get that with item[“id”]
  • The type of that variable is Any, but we know it is actually a string, so we cast it to String and treat it as such
  • As always, we use conditional binding to safely cast the variable, and inside the conditional body we simply assign the recordID constant to the recordID property on the purchase object

OK, let’s do it again! But then for the “timestamp” of item. Like this:

if let timestamp = item[“timestamp”] as? Double {
purchase.date = Date(timeIntervalSince1970: timestamp)
}

This time we’re casting to Double so we can use that value to initialize a Date object with the timestamp. As a result, the Swift Date object now points to the correct date and time.

Then, the label… That’s a bit more complicated! Remember how we’re using the “label” on an item as a shorthand to store the habitat name and owner?

Tierion’s API doesn’t return the complete blockchain object for the /records API endpoint. We need that information, so that’s why we’re kinda misusing the label for another purpose. You’ll see later on in this tutorial how we’re adding the data to that label.

First, let’s get the label as a String from the JSON item:

if let label = item[“label”] as? String
{

}

Any label value follows the [habitat];[owner] format. Like this:

“Schiaparelli;Mark Watney”

If we want to decompose the habitat and owner from that, the only thing we need to do is split the string on the ; character, and get the first and second elements.

We do that with the split(separator:) function on String, and the use the higher-order function map(_:) to turn that into a String object. (The split(…) function returns a value of type Substring.)

Like this:

let a = label.split(separator: “;”).map { String($0) }

The a array now has this value: [“Schiaparelli”, “Mark Watney”].

Nice! So the one thing we then do is simply add those values to the purchase object. Like this:

purchase.habitat = a[0]
purchase.owner = a[1]

Then, the entire block of code now looks like this:

if let label = item[“label”] as? String
{
let a = label.split(separator: “;”).map { String($0) }

if a.count != 2 {
continue
}

purchase.habitat = a[0]
purchase.owner = a[1]
}

Note: We’re adding a little error correction in there with if a.count != 2 to avoid adding purchases to the array that accidentally don’t follow the [habitat];[owner] format. For instance, when someone has a name with a semi-colon in it…

Then, the last thing we’re coding in the for-in loop is this bit:

self.purchases += [purchase]

We’re simply adding the purchase object, that we’ve just filled with values, to the purchases property on the API instance. Ultimately, this will fill up that array with purchases found in the JSON data.

There are two more things left to do:

  • Sort purchases
  • Notify the table view of the new data

First, sorting the purchases array. Why would we do that? Note that the following is true:

  • The purchases array contains purchases of habitats, identified by their owners
  • Any owner can have made multiple purchases, and own multiple habitats
  • Any habitat can be bought by multiple owners, but only one is the current owner!

How do we know who the current owner is? Think about it. I’ll wait.

Got it? It’s the last owner in the purchases array, if you sort it by most recent purchases first.

And as it turns out, that’s easy to do in Swift:

self.purchases.sort { $0.date > $1.date }

In the above code we’re using the sort(_:) function. Without going into it too deeply – the sort(_:) function takes a closure. Within the closure you compare any two values and return whichever is greater.

In the code above we’re sorting the most recent date first, so the most recent purchases end up first in the array.

Finally, we want to notify the table view controller that its data has been updated. We can do so by using Notification Center, a super useful Swift class that you can use to broadcast information between components of your app that aren’t directly related to each other.

Add the following code to the function, outside the for-in loop:

NotificationCenter.default.post(name: .recordsUpdated, object: nil)

You can also add the following extension to the API.swift file, or its own file, so we can use that notification name as .recordsUpdated.

extension Notification.Name
{
static let recordsUpdated = Notification.Name(“recordsUpdated”)
}

With the post(…) function you’re posting a notification to the Notification Center. Any observer that’s listening in for that type of notification will get subsequently notified by the Notification Center. It’s ideal for broadcasting one-to-one and one-to-many signals between components of your app.

Ultimately, the getAllRecords() function now looks like this:

func getAllRecords()
{
Alamofire.request(Constants.url + “/records”, method: .get, parameters: [“datastoreId”: Constants.datastoreID], encoding: URLEncoding.default, headers: headers).responseJSON { [unowned self] response in

if let json = response.result.value as? [String: Any],
let records = json[“records”] as? [[String: Any]]
{
self.purchases.removeAll()

for item in records
{
let purchase = Purchase()

if let label = item[“label”] as? String
{
let a = label.split(separator: “;”).map { String($0) }

if a.count != 2 {
continue
}

purchase.habitat = a[0]
purchase.owner = a[1]
}

if let recordID = item[“id”] as? String {
purchase.recordID = recordID
}

if let timestamp = item[“timestamp”] as? Double {
purchase.date = Date(timeIntervalSince1970: timestamp)
}

self.purchases += [purchase]
}

self.purchases.sort { $0.date > $1.date }

NotificationCenter.default.post(name: .recordsUpdated, object: nil)
}
}
}

You’ve just coded all that, but here’s what happens in short, again:

  • First, we set up the HTTP request and its response handler
  • Then, when the request is completed and the JSON data returns, the closure is executed, and provided with a response parameter
  • Then, we decompose the records from the JSON data, and iterate over every element in the records array with for-in
  • Then, for every record, we decompose individual data elements such as recordID, label and timestamp, and add those to a Purchase object, which is subsequently added to the purchases instance property
  • Then, we sort the purchases so they’re organized most recent first
  • Finally, we post a notification to Notification Center, telling it that the records have been updated

That wasn’t so bad, was it? Let’s continue!

4. Respond To Notifications From “getAllRecords()”

Now that we’ve written the code to get the records from the Tierion API, we can put that code to use.

Here’s what we’ll do:

  • First, register for notifications from getAllRecords()
  • Then, tell the API to get all records by calling getAllRecords()
  • Finally, responding to notifications with a custom function

All this happens in the table view controller class HabitatTableViewController, so make sure you open that file in Xcode.

First, write the following line of code in viewDidLoad(). You can add it right after tableView.register(…:

NotificationCenter.default.addObserver(self, selector: #selector(onRecordsUpdated(_:)), name: .recordsUpdated, object: nil)

This code registers the current instance of the table view controller as an observer for the .recordsUpdated notification.

When that notification is posted to the Notification Center, the function onRecordsUpdated(_:) of the table view controller is called. (We’ll code that function later.)

Note the syntax of addObserver(_:selector:name:object:). See how we’re telling Notification Center to call onRecordsUpdated(_:) on self when the notification .recordsUpdated is posted?

Then, write the following line of code below addObserver(…):

API.shared.getAllRecords()

This initiates the API request we coded earlier. So first we’re registering the observer, and then we’re calling the function that ultimately posts the notification.

Finally, let’s code that onRecordsUpdated(_:) function. Here’s what it does:

  • The function name is onRecordsUpdated and it has one unnamed parameter notification of type Notification
  • The function needs to be marked with the @objc attribute, to become available in the Objective-C runtime
  • In the function body, the table view reloads its data

In Swift code, that’s this:

@objc func onRecordsUpdated(_ notification: Notification)
{
tableView.reloadData()
}

Easy! See how that works?

5. Write A “getLastPurchase(of:)” Function To Get The Habitat Owner

Remember this line of code you wrote in the function set(name:owner:price:) of HabitatCell?

ownerLabel?.text = owner == nil ? “For sale!” : “Currently owned by: \(owner!)”
When owner is nil, the label text is “For sale!”. When it is not nil, the label text is “Currently owned by: […]”. We’re using that bit of code to show the user who the owner of a habitat is.

Previously, we structured the JSON data from the /records endpoint to create Purchase objects. We also figured out that when the purchases array is sorted most recent first, we can pick off the first item of a filtered array to find the owner of any habitat.

We’re now going to put those pieces together. Are you up for a challenge? Write a function for the API class that does the following:

  • It’s function signature is getLastPurchase(of habitat:String) -> Purchase?, so it takes one named parameter habitat and returns an optional Purchase object
  • In the function, iterate over purchases and find the Purchase object of which the habitat property is equal to the habitat function parameter
  • Pick off the first element of the result set, because that’s the most recent owner of that habitat
  • Return nil if you can’t find an owner for the habitat, otherwise return the Purchase object that you found

Here’s the one-line function I came up with:

func getLastPurchase(of habitat:String) -> Purchase?
{
return purchases.first { $0.habitat == habitat }
}

See how that works? It’s almost cheating…

We’re using the first(where:) function on the purchases array. Every Swift array has this function. What the function does is simple: it returns the first element of the array for which the where closure returns true.

The $0 shorthand is the first (and only) parameter of the closure, the current element of the array that we’re inspecting. Its type is Purchase. We’re assessing whether that element’s habitat property is equal to the habitat function parameter.

The first(where:) function will actually iterate over the purchases array, and call the closure for every array item. Whenever the result of that closure is true, the iteration stops and that Purchase object is returned.

The return type of first(where:) is the same as the type of the array objects, so that’s Purchase. It’s also an optional, because first(where:) can potentially return nil when it can’t find an item that satisfies the predicate. Therefore, the return type of our function is Purchase?.

And as usual, we’re using trailing closure syntax to make this function super concise!

That closure mechanism is called a predicate, which is like a “rule” that needs to be satisfied, or a condition that needs to be met.

Can we also code the function more explicitly, without using fancy Swift functions? Of course! Check this out:

func getLastPurchase(of habitat:String) -> Purchase?
{
for purchase in purchases
{
if purchase.habitat == habitat {
return purchase
}
}

return nil
}

See how that works?

For every purchase in purchases, assess whether the habitat property is equal to the habitat function parameter. If it is, return the purchase object. If the entire array is iterated without finding a match, the function eventually returns nil.

What’s even more interesting is that both function implementations use the exact same mechanism for finding a first array item that satisfies a given condition.

Note: The getLastPurchase(of:) function obviously only works when the purchases array is sorted properly, i.e. most recent first. You’ll often find that these software engineering problems can be solved by structuring, organizing and filtering data. In fact, that’s what a developer’s job is about 99% of the time!

Now that we can figure out the owner of any habitat, let’s put that function getLastPurchase(of:) to use!

Switch to HabitatTableViewController.swift and locate the tableView(_:cellForRowAt:) function. Locate the set(name:owner:price:) function call in the function body.

The tableView(_:cellForRowAt:) function provides table view cells to the table view controller. It’s also the function responsible for giving the cell the right data. So that’s the function that wants to know who the owner of a habitat is, so it can tell the table view cell.

How do we want to change this function?

  • If the result of getLastPurchase(of:) is not nil, we want to provide the owner’s name to the cell
  • If the result of getLastPurchase(of:) is nil, we can’t provide the owner’s name to the cell, so the name stays nil like before

Can you code that?

You’ll want to change that cell.set(…) line to this:

if let purchase = API.shared.getLastPurchase(of: habitat) {
cell.set(name: habitat, owner: purchase.owner, price: price)
} else {
cell.set(name: habitat, owner: nil, price: price)
}

Here’s how that works:

  • We’re using conditional binding to assign the result of getLastPurchase(of:) to purchase if it’s not nil
  • Remember how we’re getting the habitat’s name from habitats by using indexPath.row? We’re using that habitat value as the argument for getLastPurchase(of:), so that function can return the most recent Purchase object that has the same habitat name
  • When it finds a Purchase object, we provide the purchase.owner value to cell.set(…), so that the owner’s name is shown in the table view cell
  • When it doesn’t find a Purchase object, i.e. purchase is nil, and the else-clause is executed, we just set that owner parameter to nil, as we did before

The entire tableView(_:cellForRowAt:) function now looks like this:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: “habitatCell”, for: indexPath) as! HabitatCell

if let habitat = habitats[indexPath.row][0] as? String,
let price = habitats[indexPath.row][1] as? Int
{
if let purchase = API.shared.getLastPurchase(of: habitat) {
cell.set(name: habitat, owner: purchase.owner, price: price)
} else {
cell.set(name: habitat, owner: nil, price: price)
}
}

return cell
}

Awesome! When you run your app now with Command-R or the Play button, it should compile without errors.

You won’t see any owned habitats in the app yet, because we haven’t created any blockchain records yet. Let’s change that in the next chapter!

Important: In a production-ready blockchain app you wouldn’t interact with a public API, such as Tierion’s, directly. Even though the API requests are encrypted with SSL/HTTPS, you can easily extract your API keys and secrets from an app binary. When someone has those, they can create “fake” requests to your Tierion datastore, and basically do whatever they want with it. For the sake of education, we’re directly interfacing with the Tierion API in this iOS tutorial. In your own blockchain app, you’ll want to put a webservice between the app and the Tierion API, so you can use OAuth2 authentication, logging, access control, to create a safer and permissioned blockchain app.

Purchasing A Habitat With The Blockchain

The most important feature of your blockchain app is missing: purchasing habitats!

So far, we’ve connected to the blockchain to get data about purchases, and show that data in the app, but we haven’t yet added the functionality needed to make habitat purchases.

Let’s change that. Here’s what we’ll do:

  1. Write a createRecord(habitat:owner:) function to store purchases
  2. Observe .createRecordReceived notifications and respond to them
  3. Write a purchase(habitat:) function to initiate purchases
  4. Use the new purchase(habitat:) function in tableView(_:didSelectRowAt:)

Sounds good? Let’s continue.

1. Write A “createRecord(habitat:owner:)” Function

When a user of your blockchain app wants to purchase a habitat, what they’re really doing is storing a record in the blockchain. That record is the proof that they purchased the habitat.

So, if we want to enable the user to purchase a habitat, we need a way to store records in the blockchain. And what better place to do so than in our API class?

Here’s what you’re going to code:

  • Write a function with signature func createRecord(habitat: String, owner: String) in the API class
  • Use Alamofire to send a .post request to the /records endpoint of the Tierion API
  • Provide the request with datastoreId, habitat, owner and firstname (label) parameters, encoded as JSON. Don’t forget to send the headers!
  • When the request returns, send a .createRecordReceived to Notification Center, together with the habitat, owner and record status values

If you can do that on your own, go for it! If not, bear with me and I’ll walk you through it.

First, make sure you’re editing the API.swift file. Add the following function to the class:

func createRecord(habitat: String, owner: String)
{

}

Then, let’s set up the parameters of the HTTP request. Like this:

let parameters = [
“datastoreId”: Constants.datastoreID,
“habitat”: habitat,
“owner”: owner,
“firstname”: “\(habitat);\(owner)”
]

It’s a simple dictionary that contains values for the Datastore ID, and three fields: habitat, owner and firstname. Note that we’re using the habitat and owner function parameters, and the datastoreID constant.

That firstname parameter is what ends up in the label field of the response in getAllRecords(). Remember how we used split(separator:) to decompose the label field?

In the above code we set that semicolon format [habitat];[owner] with:


“firstname”: “\(habitat);\(owner)”

Unfortunately, the Tierion API doesn’t return all the custom fields of our datastore records for the Get All Records request. Instead, it provides a generic “label” for every record. We can change the value of that “label” by (mis)using the firstname field of the Create Record request.

Next, let’s set up the HTTP POST request for the Create Record endpoint. Like this:

Alamofire.request(Constants.url + “/records”, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { response in

}

Just as before, we’re using Alamofire to set up the request, and attach a response handler with responseJSON(…) with a closure.

The request(…) function takes the request URL, the request method .post, that parameters array, a encoding setting for JSON, and the headers property. Within the closure we have access to the parameter response that contains the request response data.

Then, inside the closure, we are going to decompose the response data. Like this:

if let json = response.result.value as? [String: Any],
let status = json[“status”] as? String
{

}

This is similar to what we did in the previous request handler. The response JSON data is of type [String: Any], and we’re trying to get to the json[“status”] value, and cast it to String.

Here, this is what the JSON data looks like:

See how status is a top-level element of the JSON response? And we’re providing those parameters in the request body, too.

The status, by the way, has 3 possible values:

  • queued, this means that the record is queued to be added to the blockchain
  • unpublished, this means that the record has been processed, but hasn’t been published to the blockchain yet
  • complete, this means that the record has been added to the blockchain, and has been confirmed by the blockchain mechanism

The difference between unpublished and published is exactly what the Proof of Work, that we talked about earlier, is about!

OK, back to the code. We now want to broadcast a notification with the relevant data of this purchase, including the blockchain record’s status. Here’s how:

let userInfo = [
“status”: status,
“habitat”: habitat,
“owner”: owner
]

NotificationCenter.default.post(name: .createRecordReceived, object: nil, userInfo: userInfo)

You also want to add the following Notification.Name to the extension you created earlier, so you can use .createRecordReceived:

static let createRecordReceived = Notification.Name(“createRecordReceived”)

The entire createRecord(habitat:owner:) function now looks like this:

func createRecord(habitat: String, owner: String)
{
let parameters = [
“datastoreId”: Constants.datastoreID,
“habitat”: habitat,
“owner”: owner,
“firstname”: “\(habitat);\(owner)”
]

Alamofire.request(Constants.url + “/records”, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { response in

if let json = response.result.value as? [String: Any],
let status = json[“status”] as? String
{
let userInfo = [
“status”: status,
“habitat”: habitat,
“owner”: owner
]

NotificationCenter.default.post(name: .createRecordReceived, object: nil, userInfo: userInfo)
}
}
}

2. Observe “createRecordReceived” Notifications And Respond

Whenever a purchase is initiated, and the request successfully completes, that .createRecordReceived notification is posted to the Notification Center.

If we observe those notifications, we can respond to them, for instance by confirming the purchase and displaying the blockchain record status.

Here’s roughly how:

  • Register an observer for the .createRecordReceived notification
  • Respond to the notification with a onCreateRecordReceived(_:) function
  • Decompose the relevant data in that function and show it to the user in an alert dialog

Let’s code that!

First, open the HabitatTableViewController class and locate the viewDidLoad() function. Add this line of code:

NotificationCenter.default.addObserver(self, selector: #selector(onCreateRecordReceived(_:)), name: .createRecordReceived, object: nil)

See how we’re adding an observer for .createRecordReceived? When that notification is broadcasted, the function onCreateRecordReceived(_:) on self is called. And self is the current instance of the table view controller, of course.

Next up, add this function to the class:

@objc func onCreateRecordReceived(_ notification: Notification)
{

}

Pretty self-explanatory, right? OK, then add this line of code to the function:

API.shared.getAllRecords()

That’s important! Whenever a purchase is made, we want to update the table view with new habitat-owner data. It makes no sense to just reload the table view – no, we need to get the data from the Tierion API.

Next up, we’re decomposing the userInfo property of the notification parameter. Like this:

if let status = notification.userInfo?[“status”] as? String,
let habitat = notification.userInfo?[“habitat”] as? String,
let owner = notification.userInfo?[“owner”] as? String
{

}

Remember when we created that userInfo dictionary in createRecord(…), to send together with the .createRecordReceived notification? This is that exact same data.

In short, we’re using conditional binding to get the values from userInfo, while using conditional casting at the same time. When all three casts succeed, the conditional executes, and we can use the values from status, habitat and owner.

Don’t forget that when you’re casting the data doesn’t change, only the way you treat that data – with a different type. Casts fail when a value can’t be treated like the type you’re trying to cast it as. Don’t worry, casting used to confuse the beeswax out of me until I made the analogy with my uncle dressing up as Santa Claus…

Are we there yet? No! Let’s construct that alert dialog and show it to the user. Like this:

let message = “status = \(status), habitat = \(habitat), owner = \(owner)”

let alert = UIAlertController(title: “Purchase created in blockchain!”, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: “OK”, style: .cancel, handler: nil))

self.present(alert, animated: true)

That’s simple code. It creates a message that includes the purchase data, and then shows that to the user by using an alert controller.

You can learn more about alert controllers here.

3. Write A “purchase(habitat:)” Function To Initiate Purchases

If you’re getting the feeling that we are building this blockchain app backwards, then you’d be right. You first write a function, and only then you can use it!

  • You construct a scaffold before you paint a wall
  • You build the foundation of your house before you build the walls
  • You make your bed before you can sleep in it (at least, most of us do…)

Even though our blockchain app is primarly used to purchase habitats, we’ve written a whole lot of code to support the purchasing of habitats.

In fact, we’ve just written the code that responds to the purchase of habitats. It’s only just now that we’re ready to actually make those purchases.

OK, let’s do it.

First, the code that makes the purchase. Add the following function to the HabitatTableViewController class:

func purchase(habitat: String)
{

}

This function purchase(habitat:) has one parameter of type String, the name of the habitat.

Then, you’re going to create that alert dialog. We’re using the dialog to ask for the name of the new habitat owner.

Creating and displaying an alert dialog has three steps:

  1. Create the UIAlertController object
  2. Add actions and/or text fields to it
  3. Present it on screen

First, let’s create the UIAlertController object. Like this:

let alert = UIAlertController(title: “What is the name of the new owner of \(habitat)?”, message: nil, preferredStyle: .alert)

Then, we’re adding a simple “Cancel” button to it. It doesn’t do anything, except dismiss the alert dialog. Like this:

alert.addAction(UIAlertAction(title: “Cancel”, style: .cancel, handler: nil))

Then, add a text field to the alert controller. Like this:

alert.addTextField(configurationHandler: { textField in
textField.placeholder = “Input your name here…”
})

Text fields are added to a simple array that’s part of the alert controller, and you can use the closure, as seen above, to configure the text field.

Then, let’s add the “Purchase” button to the alert dialog. Like this:

alert.addAction(UIAlertAction(title: “Purchase”, style: .default, handler: { action in

if let owner = alert.textFields?.first?.text {
API.shared.createRecord(habitat: habitat, owner: owner)
}
}))

Easy, right? When the button is tapped, the closure is executed. In the closure we’re getting the text from the text field by using the alert.textFields array.

We then use that owner string to call createRecord(habitat:owner:) on the API. This effectively creates a new record in the Tierion blockchain!

And don’t forget to present the alert dialog:

self.present(alert, animated: true)

The entire purchase(habitat:) function now looks like this:

func purchase(habitat: String)
{
let alert = UIAlertController(title: “What is the name of the new owner of \(habitat)?”, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: “Cancel”, style: .cancel, handler: nil))

alert.addTextField(configurationHandler: { textField in
textField.placeholder = “Input your name here…”
})

alert.addAction(UIAlertAction(title: “Purchase”, style: .default, handler: { action in

if let owner = alert.textFields?.first?.text {
API.shared.createRecord(habitat: habitat, owner: owner)
}
}))

self.present(alert, animated: true)
}

Okay, NEXT!

4. Use The “purchase(habitat:)” Function In “tableView(_:didSelectRowAt:)”

Now that we’ve coded that purchase(habitat:) function, let’s put it to use. We’ll do so by calling it when the user taps a table view cell.

First, add the following function to the HabitatTableViewController class:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{

}

This is another overriden delegate function that’s part of the table view controller mechanism. The function is executed whenever the user taps a cell.

We’ll use it to give the user the option to purchase a habitat. Later on we’ll also hook into this function to let users verify habitat owners.

Next up, we need the name of the habitat that has been tapped. Like this:

if let habitat = habitats[indexPath.row][0] as? String
{

}

This code is similar to what you’ve used in tableView(_:cellForRowAt:). You’re using the indexPath to get the right habitat name from habitats.

Then, you’re going to create that alert dialog. Create the UIAlertController object like this:

let alert = UIAlertController(title: “What do you want to do with \(habitat)?”, message: nil, preferredStyle: .alert)

Then, add one “Purchase” and one “Cancel” button, and actions that are invoked when the alert button is pressed. Like this:

alert.addAction(UIAlertAction(title: “Purchase”, style: .default, handler: { [weak self] action in
self?.purchase(habitat: habitat)
}))

alert.addAction(UIAlertAction(title: “Cancel”, style: .cancel, handler: nil))

The closure is executed when the button is tapped. See how we’re using that purchase(habitat:) function we wrote earlier? This is where we finally get to use it!

Kind of an anti-climax, isn’t it? You’ll often find that you code abstractions in your app, layer by layer, until the only thing you need to do is call a simple function. It’s magical!

Finally, let’s present that alert dialog:

self.present(alert, animated: true)

The entire function now looks like this:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if let habitat = habitats[indexPath.row][0] as? String
{
let alert = UIAlertController(title: “What do you want to do with \(habitat)?”, message: nil, preferredStyle: .alert)

alert.addAction(UIAlertAction(title: “Purchase”, style: .default, handler: { [weak self] action in
self?.purchase(habitat: habitat)
}))

alert.addAction(UIAlertAction(title: “Cancel”, style: .cancel, handler: nil))

self.present(alert, animated: true)
}
}

Awesome! But… does it work? At this point you can run the app with Command-R or the Play button, and successfully purchase habitats.

You can now purchase a habitat by tapping the cell in the table view, and entering your name in the text field. The table view updates, and the record is queued to get stored in the blockchain.

When you go to your Tierion Dashboard, you can also see records that have been created in the blockchain, and see their attributes. You should be able to check that a blockchain record hasn’t been confirmed yet, too.

It can sometimes happen that purchases don’t immediately show up, even though you’ve just made them. That’s most likely because the responses from the Tierion API are cached for a short while, or simply not committed yet when you refresh your data.

One way to solve that is by implementing a simple pull-to-refresh interaction. Here’s how.

First, add this code to viewDidLoad() of the HabitatTableViewController class:

let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(onRefreshControlChanged(_:)), for: .valueChanged)

tableView.refreshControl = refreshControl
Then, add that onRefreshControlChanged(_:) function to the class, too. Like this:

@objc func onRefreshControlChanged(_ refreshControl:RefreshControl)
{
API.shared.getAllRecords()
}

Finally, make sure to add the following line of code to onRecordsUpdated(_:). Otherwise the refresh control keeps spinning, even though the data has been successfully refreshed!

tableView.refreshControl?.endRefreshing()

In short, the function getAllRecords() on the API is called whenever the user pulls on the refresh control, effectively refreshing the table view data. The refresh control resets when the data has been received.

Let’s continue with the next and last step of building the blockchain app.

Verify Ownership Of A Habitat With The Blockchain

Now that we’ve completed the biggest aspect of your blockchain app – storing records in the blockchain – we are going to build the feature that makes the blockchain unique: verifying data.

Earlier we discussed proof of work, and how blockchain miners get rewarded for constructing blocks of pending “facts”.

The Tieron blockchain platform takes care of all this for us. When you add a new record to your datastore, it automatically gets queued for storing in the blockchain.

Subsequently, miners pick up the work and incorporate your data in the blockchain. The chain of blocks, and its confirmations, guarantee that your data is irrevocably stored in the blockchain.

Blockchain-as-a-Service platforms don’t store the exact data in the blockchain, but instead store a cryptographic hash value of that data. This has the added benefit of privacy, because a blockchain is public. And because hashes transform data of arbitrary size to fixed values, they’re also smaller than the orginal data.

It can take a few minutes, up to a few hours, to confirm new data that has been added to the blockchain. Because of the way blockchain mining works, it’s uncertain when a new block is mined, and therefore it’s uncertain when a pending fact is successfully added to a newly mined block.

That’s where the status property of a datastore record, as stored in the Tierion blockchain, comes in. As discussed, it has three options: queued, unpublished and complete. A record has successfully been added to the blockchain, and has been confirmed by it, when the status is complete.

Said differently, the purchase of a habitat isn’t confirmed until it’s status is complete. Naturally, we want to code something that allows the users of your blockchain app to verify purchases!

Here’s what we’ll do:

  1. Write a getRecord(forID:) function to get the specifics of a record
  2. Observe and respond to .getRecordReceived notifications
  3. Adjust tableView(_:didSelectRowAt:) to verify habitat purchases

Let’s get started!

1. Write A “getRecord(forID:)” Function

The getRecord(forID:) function is very similar to the getAllRecords() function, except that it gets one specific record instead of all records.

Just as before, we’ll prepare an HTTP GET request with Alamofire, process the data once it comes in, and send a notification to the Notification Center.

First, make sure you’re editing the API class. Add the following function:

func getRecord(forID recordID:String)
{

}

Easy-peasy! The function getRecord(…) only has one parameter recordID, with an argument label forID. That’s convenient, because now we can use it as: getRecord(forID: “abcd”).

Next, let’s set up that HTTP GET request. Like this:

Alamofire.request(Constants.url + “/records/” + recordID, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON { response in

}

Again, there’s a couple of parameters:

  • First, the URL: https://api.tierion.com/v1/records/[recordID]
  • Then, the .get method to indicate that this is an HTTP GET request
  • Then, this request has no parameters: nil for parameters
  • Then, the default URL encoding for encoding
  • Then, the headers property, as usual, for authentication
  • Finally, we chain the call to responseJSON(…) and provide it a completion handler, that has a response parameter

Take a look at the response data for this request:

See how there’s one root object, with values for status, data and timestamp, and how that data object has values for habitat and owner?

Let’s write some code to get that data out. This code, to be exact:

if let json = response.result.value as? [String: Any],
let status = json[“status”] as? String,
let timestamp = json[“timestamp”] as? Double,
let data = json[“data”] as? [String: String],
let habitat = data[“habitat”],
let owner = data[“owner”]
{

}

The above conditional binding uses the JSON dictionaries, and optional casting, to get to the right data.

If you look closely, you’ll see that it first gets the root-level elements status, timestamp and data. Then, it goes deeper into data to get habitat and owner.

We’re using typical types like String, Any and Double to cast the values to the correct types.

Then, inside the conditional we only have to format the data and post it as a userInfo dictionary alongside a notification.

Here’s how. First, we’re turning that timestamp into an ordinary date string. Like this:

let formatter = DateFormatter()
formatter.dateStyle = .short

let date = formatter.string(from: Date(timeIntervalSince1970: timestamp))

Here’s what happens:

  • This code block sets up a DateFormatter object with a “short” date style, which is usually dd-mm-YY depending on your locale.
  • A Date object is constructed with the Unix timestamp we got from the JSON response data.
  • It then uses that formatter and the Date object to get a date string.

Then, we simply put the data we decomposed into a userInfo dictionary. Like this:

let userInfo = [
“status”: status,
“date”: date,
“habitat”: habitat,
“owner”: owner
]

And then we post the notification, like this:

NotificationCenter.default.post(name: .getRecordReceived, object: nil, userInfo: userInfo)

The entire function now looks like this:

func getRecord(forID recordID:String)
{
Alamofire.request(Constants.url + “/records/” + recordID, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON { response in

if let json = response.result.value as? [String: Any],
let status = json[“status”] as? String,
let timestamp = json[“timestamp”] as? Double,
let data = json[“data”] as? [String: String],
let habitat = data[“habitat”],
let owner = data[“owner”]
{
let formatter = DateFormatter()
formatter.dateStyle = .short

let date = formatter.string(from: Date(timeIntervalSince1970: timestamp))

let userInfo = [
“status”: status,
“date”: date,
“habitat”: habitat,
“owner”: owner
]

NotificationCenter.default.post(name: .getRecordReceived, object: nil, userInfo: userInfo)
}
}
}

Don’t forget to add that notification name to the Notification.Name extension! Here it is:

static let getRecordReceived = Notification.Name(“getRecordReceived”)

Awesome!

2. Observe And Respond To “.getRecordReceived” Notifications

Are we going to listen for those notification broadcasts? Of course we are! Just as before, we’re going to add an observer for .getRecordReceived and write the function that fires when the notification comes in.

First, make sure you’re editing the HabitatTableViewController class and locate the viewDidLoad() function. Add this line of code:

NotificationCenter.default.addObserver(self, selector: #selector(onGetRecordReceived(_:)), name: .getRecordReceived, object: nil)

See how the function onGetRecordReceived(_:) is called on self when the .getRecordReceived notification is observed?

Then, the function itself. Add it to the class:

@objc func onGetRecordReceived(_ notification: Notification)
{

}

In this function, you’re going to decompose the userInfo collection you constructed earlier, and show its values in a simple alert dialog.

First, let’s get to the right data. Like this:

if let status = notification.userInfo?[“status”] as? String,
let date = notification.userInfo?[“date”] as? String,
let habitat = notification.userInfo?[“habitat”] as? String,
let owner = notification.userInfo?[“owner”] as? String
{

}

Again, we’re doing our little conditional binding and optional casting dance to get to the correct values.

Don’t let it fool you, though! Remember that the type of userInfo is [AnyHashable: Any]? in this function, so you really need to cast the data to be able to treat it as strings.

Then, inside the conditional, we’re setting up the alert message. Like this:

let title = status == “complete” ? “Ownership verified!” : “Ownership not verified”
let message = “habitat = \(habitat), owner = \(owner), date = \(date), status = \(status)”

The title shows whether the ownership of this habitat is verified, or that we’re still waiting for confirmation. And the message shows a simple list of values.

Finally, we’re showing the data to the user with an alert dialog. Like this:

let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: “OK”, style: .cancel, handler: nil))

self.present(alert, animated: true)

The UIAlertController has no actions, except for a Cancel button, so it’s just really “FYI”.

3. Adjust “tableView(_:didSelectRowAt:)” To Verify Habitat Purchases

Can we now finally verify if Bob is the true owner of Bob’s Red Fantasy Land in the Schiaparelli crater on Mars? YES!

First, locate the tableView(_:didSelectRowAt:) function in the HabitatTableViewController class.

Inside it, you’ve coded an instance of UIAlertViewController that has two actions: Purchase and Cancel. You’re going to add a third action, Verify.

What it should do, is this:

  • Get the last Purchase object for this habitat
  • Use the recordID of that Purchase object to get the associated record

Yes, it’s that simple. Here’s the code:

if let purchase = API.shared.getLastPurchase(of: habitat)
{
alert.addAction(UIAlertAction(title: “Verify”, style: .default, handler: { action in
API.shared.getRecord(forID: purchase.recordID)
}))
}

You can add it between the let alert = … line, and the alert.addAction(… for Purchase.

How does the code work?

  • You’re using conditional binding to get the Purchase object from getLastPurchase(of:), if there is any
  • When the button is tapped, i.e. the alert button action, you use the recordID property of that Purchase object to call the getRecord(forID:) function in the API

And then this happens:

  • A HTTP GET request is sent to the Tierion API endpoint
  • When that returns, a notification is broadcast
  • That notification is picked up by the table view controller
  • An alert dialog with the blockchain data status is shown

BOOM!

Further Reading

And that, ladies and gentlemen, is how you code a blockchain app with Swift! Thank you for making it this far, and if you have questions, I’ll see you in the comments below.

If you ask questions that you could have Googled faster than you could write them in the comment box, you owe me an apple pie!

Want to learn more? Check out these external resources:

  • The Blockchain Explained series – a must-read!
  • Understanding Blockchain Proof-of-Work
  • Building Blockchain Web API Using Swift and Vapor shows you the “other” side, i.e. building the blockchain instead of the blockchain app


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts