How to make a RESTful app with Siesta
Learn how you can use the Siesta framework to improve your API interactions, networking, model transformations, caching, loading indications and more. By Sanket Firodiya.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to make a RESTful app with Siesta
20 mins
- Getting Started
- Yelp API
- Making a Network Request in Siesta
- Siesta Service
- Resource and ResourceObserver
- Adding the Spinner
- Siesta Transformers
- Restaurant Model
- Model Mapping
- RestaurantListViewController
- Building the Restaurant Details Screen
- RestaurantDetails Model
- Model Mapping
- Setting up Siesta in RestaurantDetailsViewController
- Handling the Navigation to RestaurantDetailsViewController
- Where to Go From Here?
Adding the Spinner
There isn’t a loading indicator to inform the user that the restaurant list for a location is being downloaded.
Siesta comes with ResourceStatusOverlay
, a built-in spinner UI that automatically displays when your app is loading data from the network.
To use ResourceStatusOverlay
, first add it as an instance variable of RestaurantListViewController
:
private var statusOverlay = ResourceStatusOverlay()
Now add it to the view hierarchy by adding the following code at the bottom of viewDidLoad()
:
statusOverlay.embed(in: self)
The spinner must be placed correctly every time the view lays out its subviews. To ensure this happens, add the following method under viewDidLoad()
:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
statusOverlay.positionToCoverParent()
}
Finally, you can make Siesta automatically show and hide statusOverlay
by adding it as an observer of restaurantListResource
. To do so, add the following line between .addObserver(self)
and .loadIfNeeded()
in the restaurantListResource
‘s didSet
:
.addObserver(statusOverlay, owner: self)
Build and run to see your beautiful spinner in action:
You’ll also notice that selecting the same city a second time shows the results almost instantly. This is because the first time the restaurant list for a city loads, it’s fetched from the API. But Siesta caches the responses and returns responses for subsequent requests for the same city from its in-memory cache:
Siesta Transformers
For any non-trivial app, it’s better to represent the response from an API with well-defined object models instead of untyped dictionaries and arrays. Siesta provides hooks that make it easy to transform a raw JSON response into an object model.
Restaurant Model
PizzaHunter stores the id
, name
and url
for each Restaurant
. Right now, it does this by manually picking that data from the JSON returned by Yelp. Improve on this by making Restaurant
conform to Codable
so that you get clean, type-safe JSON decoding for free.
To do this, open Restaurant.swift and replace the struct
with the following:
struct Restaurant: Codable {
let id: String
let name: String
let imageUrl: String
enum CodingKeys: String, CodingKey {
case id
case name
case imageUrl = "image_url"
}
}
Codable
and CodingKey
, check out our Encoding, Decoding and Serialization in Swift 4 tutorial.
If you look back at the JSON you get from the API, your list of restaurants is wrapped inside a dictionary named businesses
:
{ "businesses": [ { "id": "tonys-pizza-napoletana-san-francisco", "name": "Tony's Pizza Napoletana", "image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/d8tM3JkgYW0roXBygLoSKg/o.jpg", "review_count": 3837, "rating": 4, ... },
You’ll need a struct
to unwrap the API response that contains a list of businesses. Add this to the bottom of Restaurant.swift:
struct SearchResults<T: Decodable>: Decodable {
let businesses: [T]
}
Model Mapping
Open YelpAPI.swift and add the following code at the end of init()
:
let jsonDecoder = JSONDecoder()
service.configureTransformer("/businesses/search") {
try jsonDecoder.decode(SearchResults<Restaurant>.self, from: $0.content).businesses
}
This transformer will take any resource that hits the /business/search
endpoint of the API and pass the response JSON to SearchResults
‘s initializer. This means you can create a Resource
that returns a list of Restaurant
instances.
Another small but crucial step is to remove .json
from the standard transformers of your Service
. Replace the service
property with the following:
private let service = Service(baseURL: "https://api.yelp.com/v3", standardTransformers: [.text, .image])
This is how Siesta knows to not apply its standard transformer to any response that is of type JSON, but instead use the custom transformer that you have provided.
RestaurantListViewController
Now update RestaurantListViewController
so that it can handle object models from Siesta, instead of raw JSON.
Open RestaurantListViewController.swift and update restaurants
to be an array of type Restaurant
:
private var restaurants: [Restaurant] = [] {
didSet {
tableView.reloadData()
}
}
And update tableView(_:cellForRowAt:)
to use the Restaurant
model. Do this by replacing:
cell.nameLabel.text = restaurant["name"] as? String
cell.iconImageView.imageURL = restaurant["image_url"] as? String
with
cell.nameLabel.text = restaurant.name
cell.iconImageView.imageURL = restaurant.imageUrl
Finally, update the implementation of resourceChanged(_:event:)
to extract a typed model from the resource instead of a JSON dictionary:
// MARK: - ResourceObserver
extension RestaurantListViewController: ResourceObserver {
func resourceChanged(_ resource: Resource, event: ResourceEvent) {
restaurants = resource.typedContent() ?? []
}
}
typedContent()
is a convenience method that returns a type-cast value for the latest result for the Resource
if available or nil
if it’s not.
Build and run, and you’ll see nothing has changed. However, your code is a lot more robust and safe due to the strong typing!
Building the Restaurant Details Screen
If you’ve followed along until now, the next part should be a breeze. You’ll follow similar steps to fetch details for a restaurant and display it in RestaurantDetailsViewController
.
RestaurantDetails Model
First, you need the RestaurantDetails
and Location
structs to be codable so that you can use strongly-typed models going forward.
Open RestaurantDetails.swift and make both RestaurantDetails
and Location
conform to Codable
like so:
struct RestaurantDetails: Codable {
struct Location: Codable {
Next, implement the following CodingKeys
for RestaurantDetails
just like you did with Restaurant
earlier. Add the following inside RestaurantDetails
:
enum CodingKeys: String, CodingKey {
case name
case imageUrl = "image_url"
case rating
case reviewCount = "review_count"
case price
case displayPhone = "display_phone"
case photos
case location
}
And, finally, add the following CodingKeys
to Location
:
enum CodingKeys: String, CodingKey {
case displayAddress = "display_address"
}
Model Mapping
In YelpAPI
‘s init()
, you can reuse the previously created jsonDecoder
to add the transformer that tells Siesta to convert restaurant details JSON to RestaurantDetails
. To do this, open YelpAPI.swift and add the following line above the previous call to service.configureTransformer
:
service.configureTransformer("/businesses/*") {
try jsonDecoder.decode(RestaurantDetails.self, from: $0.content)
}
Also, add another helper function to the YelpAPI
class, that creates a Resource
object to query restaurant details:
func restaurantDetails(_ id: String) -> Resource {
return service
.resource("/businesses")
.child(id)
}
So far, so good. You’re now ready to move on to the view controller to see your models in action.
Setting up Siesta in RestaurantDetailsViewController
RestaurantDetailsViewController
is the view controller displayed whenever the user taps on a restaurant in the list. Open RestaurantDetailsViewController.swift and add the following code below restaurantDetail
:
// 1
private var statusOverlay = ResourceStatusOverlay()
override func viewDidLoad() {
super.viewDidLoad()
// 2
YelpAPI.sharedInstance.restaurantDetails(restaurantId)
.addObserver(self)
.addObserver(statusOverlay, owner: self)
.loadIfNeeded()
// 3
statusOverlay.embed(in: self)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// 4
statusOverlay.positionToCoverParent()
}
- Like before, you setup a status overlay which is shown when content is being fetched.
- Next, you request restaurant details for a given
restaurantId
when the view loads. You also addself
and the spinner as observers to the network request so you can act when a response is returned. - Like before, you add the spinner to the view controller.
- Finally, you make sure the spinner is placed correctly on the screen after any layout updates.