Couchbase Tutorial for iOS: Getting Started

In this Couchbase tutorial for iOS, you’ll learn how to use Couchbase and Sync Gateway to persist data across apps while making Ray’s Pizzeria App. By .

Leave a rating/review
Download materials
Save for later
Share
Note: While you can make this tutorial work with Xcode 10, you cannot use Swift 4.2. Couchbase is installed as a pre-compiled framework built with Swift 4.1, thereby requiring the whole project to be built with Swift 4.1. Until Couchbase is updated, we recommend you use Xcode 9 for this tutorial.

Introduction

Couchbase is a NoSQL data platform that extends from servers down to mobile devices. It’s a document-based data platform in use worldwide by a large variety of companies and organizations.

Couchbase Mobile is the portion of their platform built for mobile devices. It provides:

  • Couchbase Lite 2.0, an embedded database, which incorporates:
    • N1QL, (“nickel”, not “n-one-q-l”) a new Query API
    • Full-text search
    • Automatic conflict resolution
    • Database replication using the WebSocket protocol
  • Sync Gateway, a secure web gateway for connecting to Couchbase Server.
  • N1QL, (“nickel”, not “n-one-q-l”) a new Query API
  • Full-text search
  • Automatic conflict resolution
  • Database replication using the WebSocket protocol

In this Couchbase tutorial, you’ll develop an app to run Ray’s new pizzeria. The app will allow customers to place orders and will also let Chef Ray monitor pizza production and update his fabulous menu.

Couchbase Tutorial - Pizzeria demo

By the end of this tutorial, you’ll know how to:

  • Use a prebuilt Couchbase Lite database
  • Model and query data
  • Sync data using Sync Gateway
  • Simulate a multi-user environment using multiple simulators

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project.

Note: Because this project uses Couchbase and Fakery CocoaPods, you need to open RaysPizzeria.xcworkspace, not RaysPizzeria.xcproject.

Build and run the project in Xcode to see the basic UI.

Couchbase Tutorial - Starter Build and Run

The UI has two tabs: Menu and Kitchen, but not much functionality.

Select a pizza from the Menu tab and you’ll see an empty customer screen. Tap + to generate a customer, which then gets added to the UITableView. When you select the customer cell, a UIAlertController pops up asking you to confirm the order.

Note: The customer data is supplied using Fakery, a framework intended for unit testing.

Go back to the menu and select another pizza. Notice that your previous order and customer are no longer there. Oh no!

Now look at the Kitchen tab. Ray and his kitchen staff have no idea what pizza to make or who placed the order. To solve this problem, Ray asks you to implement a database-driven app.

With a database-driven app, you can:

  • Simplify order entry
  • Track order processing
  • Easily modify the menu

So what are you waiting for? It’s time to get cookin’.

Using a Pre-loaded Database

Open MenuViewController.swift and take a look around. It’s a basic UIViewController with a UITableView that displays the menu. In viewWillAppear(_:), the menu is loaded with menu = DataManager.shared.getMenu().

Open DataManager.swift and locate getMenu(). This loops thru the Pizza.PizzaType enum, creates a Pizza object and then adds it to the menu. This is fine until you need to change the menu items and their prices. To solve this problem, you’ll load the menu from a database instead.

Near the top of the file, replace:

import Foundation

With:

import CouchbaseLiteSwift

Add the following to the properties section, just under static var shared = DataManager():

private let database: Database
private let databaseName = "pizza-db"

To load a pre-built menu database, add the following to init():

// 1
if let path = Bundle.main.path(forResource: databaseName, ofType: "cblite2"), 
   !Database.exists(withName: databaseName) {
  do {
    try Database.copy(fromPath: path, toDatabase: databaseName, withConfig: nil)
  } catch {
    fatalError("Could not load pre-built database")
  }
}

// 2
do {
  database = try Database(name: databaseName)
} catch {
  fatalError("Error opening database")
}

Here’s the code breakdown:

  1. Get the path to the pre-loaded database from the app bundle, a read-only location, and copy it to a read-write location. Database.copy(fromPath:toDatabase:withConfig:) copies it to the app’s Application Support directory.
  2. Open the pre-loaded database.

Now, replace the contents of getMenu() with:

var menu: [Pizza] = []

// 1
let query = QueryBuilder
   .select(SelectResult.all(), SelectResult.expression(Meta.id))
   .from(DataSource.database(database))
   .where(Expression.property("type")
     .equalTo(Expression.string(DocTypes.pizza.rawValue)))
   .orderBy(Ordering.property(PizzaKeys.price.rawValue))

// 2
do {
  for result in try query.execute() {
    guard let dict = result.dictionary(forKey: databaseName),
      let name = dict.string(forKey: PizzaKeys.name.rawValue),
      let id = result.string(forKey: PizzaKeys.id.rawValue) else {
        continue
      }

      let pizza = Pizza(id: id, name: name, 
                        price: dict.double(forKey: PizzaKeys.price.rawValue))
      menu.append(pizza)
    }
  } catch {
    fatalError("Error running the query")
  }

  return menu

Here’s what this does:

  1. Build a query that retrieves all pizza documents from the database and orders them by price. SelectResult.all() returns all property data from a pizza document. The document id (used in future queries) is not part of the property data, but rather is part of the document metadata, so you use SelectResult.expression(Meta.id) to retrieve it.
  2. Execute the query and loop thru the results. Get the name, price and id from the database and create a Pizza object. Append each Pizza object to menu and return menu.

Build and run.

Couchbase Tutorial - Build and Run with pre-loaded db

Perfect! The menu loads from the database, but customers and their orders are not getting saved. You’ll fix that next.

Storing Customers and Orders

Still in DataManager.swift, add the following to the extension for Customer data:

func getCustomers() -> [Customer] {
  var customers: [Customer] = []

  // 1
  let query = QueryBuilder
    .select(SelectResult.all(), SelectResult.expression(Meta.id))
    .from(DataSource.database(database))
    .where(Expression.property("type")
      .equalTo(Expression.string(DocTypes.customer.rawValue)))

  // 2
  do {
    for result in try query.execute() {
      guard let dict = result.dictionary(forKey: databaseName),
        let name = dict.string(forKey: CustomerKeys.name.rawValue),
        let street = dict.string(forKey: CustomerKeys.street.rawValue),
        let city = dict.string(forKey: CustomerKeys.city.rawValue),
        let state = dict.string(forKey: CustomerKeys.state.rawValue),
        let postalCode = dict.string(forKey: CustomerKeys.postalCode.rawValue),
        let phoneNumber = dict.string(forKey: CustomerKeys.phoneNumber.rawValue),
        let id = result.string(forKey: CustomerKeys.id.rawValue) else {
          continue
      }

      // 3
      let customer = Customer(
        id: id, 
        name: name, 
        street: street, 
        city: city, 
        state: state, 
        postalCode: postalCode, 
        phoneNumber: phoneNumber)
      customers.append(customer)
    }
  } catch {
    fatalError("Error running the query")
  }

  return customers
}

Here’s what’s happening:

  1. Similar to getMenu(), you build a query to get all the customer data from the database.
  2. Execute the query and get the data values from the result dictionary.
  3. Create a Customer object and add it to customers.

Add the following to same section:

func add(customer: Customer) {
  // 1
  let mutableDoc = MutableDocument()
    .setString(customer.name, forKey: CustomerKeys.name.rawValue)
    .setString(customer.street, forKey: CustomerKeys.street.rawValue)
    .setString(customer.city, forKey: CustomerKeys.city.rawValue)
    .setString(customer.state, forKey: CustomerKeys.state.rawValue)
    .setString(customer.postalCode, forKey: CustomerKeys.postalCode.rawValue)
    .setString(customer.phoneNumber, forKey: CustomerKeys.phoneNumber.rawValue)
    .setString(DocTypes.customer.rawValue, forKey: "type")

  do {
    // 2
    try database.saveDocument(mutableDoc)
  } catch {
    fatalError("Error saving document")
  }
}

Here’s what this does:

  1. Create a MutableDocument and store the Customer properties in it.
  2. Save the document to the database, creating the document’s metadata: id.

Open CustomerViewController.swift, and in loadData(_:), add:

customers = DataManager.shared.getCustomers()

In addCustomer(), replace:

customers?.append(customer)

With:

DataManager.shared.add(customer: customer)
customers = DataManager.shared.getCustomers()

Build and run.

Select a pizza and add a few customers. Then, go back to the Menu tab, and select the pizza again; notice your previously added customers are now there.

Couchbase Tutorial - Customer build and run

Contributors

Michael Gazdich

Tech Editor

Vladyslav Mytskaniuk

Illustrator

Essan Parto

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.