Key Features

  • Swift Example App accessing a LoopBack Backend providing basic Create, Read, Update and Delete operations
  • Supports iOS 9, written in Swift 2
  • Uses the official Loopback iOS SDK
  • Source code includes LoopBack Server Test App

Source Code

Available on GitHub: https://github.com/kgoedecke/loopback-swift-crud-example

Screenshots

screenshot-loopback-swift-1@2x screenshot-loopback-swift-2@2x

Prerequisites

This article focuses on using the LoopBack iOS SDK in Swift and thus we won’t cover creating View Controllers and its Actions. You might want to check out this tutorial provided by Apple to get a started:
Start Developing iOS Apps (Swift): Implement Edit and Delete Behavior

Getting Started

Start the LoopBack Test App by running node .  in the main folder. The Test App will by default listen on http://localhost:3000 .

In order to access the LoopBack Backend from your Swift App we will add an instance of LBRESTAdapter to the applications AppDelegate.

Open AppDelegate.swift and insert the following right after var window: UIWindow?

static let adapter = LBRESTAdapter(URL: NSURL(string: "http://localhost:3000"))

This way we can access the adapter from everywhere in the project.

Subclassing LBPersistedModelRepository & LBPersistedModel

The LoopBack Test App provides a Model named “Widgets“, which has multiple attributes, as you can see in this JavaScript snippet from the source code of the LoopBack Test app.

var Widget = app.model('widget', {
  properties: {
    name: {
      type: String,
      required: true
    },
    bars: {
      type: Number,
      required: false
    }, ...

We are only going to use the name (type: String) and the bars (type: Number) attribute.

All data corresponding to a Model is handled by a Repository in the LoopBack iOS SDK. The Repository can then be used to create new models, update existing ones or simply retrieve all currently stored ones. As every model has individual attributes we need to create a Subclass of LBPersistedModelRepository and LBPersistedModel and declare those attributes as well as the custom model name.

Create a new Swift file, name it “WidgetRepository.swift” and insert the following code:

import Foundation

class WidgetRepository : LBPersistedModelRepository     {
    override init() {
        super.init(className: "widgets")
    }
    class func repository() -> WidgetRepository {
        return WidgetRepository()
    }
}

Line 3: Creates a subclass of LBPersistedModelRepository named WidgetRepository, which will later be used to perform the CRUD operations.

Line 4: Specify the initializer methods and simply call initWithClassName from the LBPersistedModelRepository class

Line 7: Provides a class function that returns an object of the WidgetRepository class

Afterwards create an other Swift file named “Widget.swift” with the following code:

class Widget : LBPersistedModel {
    var name: String!
    var bars: NSNumber!
    var date: NSDate!
    var data: NSObject!
}

This creates a Subclass of the LBPersistedModel and introduces the attributes that each Widget object contains.

WidgetRepository

To perform basic CRUD operations as simple as possible from everywhere in your app add an other static var of type WidgetRepository to your AppDelegate. The WidgetRepository object will provide functionality to create new models, updates existing models or simply retrieve data.

Right after your LBRESTAdapter in AppDelegate.swift create the instance of WidgetRepository:

Insert the following code:

static let widgetRepository = adapter.repositoryWithClass(WidgetRepository) as! WidgetRepository

Displaying all Widgets

By now you should already have a TableViewController in your project that will display all Widget Models in a TableView. Open this Table View Controller now.

Insert the following in your Class:

var widgets = [Widget]()

This Array of Widgets will hold the elements that will be displayed in the Table View.

In viewDidLoad()  we are going to call allWithSuccess() on our widgetRepository to retrieve all Models and in the Callback assign the local widgets Array (self.widgets ) to the fetched Widgets from the remote Backend:

AppDelegate.widgetRepository.allWithSuccess({ (fetchedWidgets: [AnyObject]!) -> Void in
		self.widgets = fetchedWidgets as! [Widget]
		self.tableView.reloadData()
	}, failure: { (error: NSError!) -> Void in
	    NSLog(error.description)
})

Next in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) dequeue a Reusable Cell and set its values to the ones of the appropriate Widget element (we assume that you created a custom UITableViewCell with two Outlets for the “name” and the “bars” numerical value):

let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath) as! WidgetTableViewCell
cell.nameLabel.text = widgets[indexPath.row].name
cell.valueLabel.text = String(widgets[indexPath.row].bars)
return cell

Checkpoint: If you run your app now, then you should be able to see all the Widgets in your TableViewController.

Deleting a Widget

Your Table View Controller should also contain the tableView(_:commitEditingStyle:forRowAtIndexPath:) delegate function if you enabled the editing mode. Add the following snippet:

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        // Delete the row from the data source
        widgets[indexPath.row].destroyWithSuccess({ () -> Void in
            self.widgets.removeAtIndex(indexPath.row)
            self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            }, failure: { (error: NSError!) -> Void in
                NSLog(error.description)
        })
    }
}

Here we trigger the deletion of a Widget. If the user deletes a Widget, we first remove it from the Backend by calling destroyWithSuccess on the repository and then from the local Array as well as from the Table View.

Creating and Updating Widgets

Open the View Controller for the detail view of the Widget.

In the Class declare a widget variable holding the current Widget object:

var widget: Widget?

In case the View Controller is loaded to update an existing widget we need to fill the textfields with the appropriate data.

In viewDidLoad() we first check if the local widget object is set and if it is we load the data into the textfield and the slider.

Your viewDidLoad() should contain the following now:

super.viewDidLoad()
if let widget = widget  {
	nameTextField.text = widget.name
	numberValueSlider.value = widget.bars as Float
}

At this point we assume that you already created two Segues from the TableViewController in your Storyboard – One for creating a new Widget and one for updating an existing one.

So your Storyboard should look at this point similar to this:

loopback-swift-storyboard

Switch back to your Table View Controller and insert the prepareForSegue function. Add the following code:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "ShowDetail" {
        let widgetDetailViewController = segue.destinationViewController as! WidgetViewController
        if let selectedWidgetCell = sender as? WidgetTableViewCell {
            let indexPath = tableView.indexPathForCell(selectedWidgetCell)!
            let selectedWidget = widgets[indexPath.row]
            widgetDetailViewController.widget = selectedWidget
        }
    }
    else if segue.identifier == "AddItem" {
        NSLog("Adding new widget")
    }
    
}

All that’s pretty much happening here is that if the segue.identifier is equal to “ShowDetail” we set the destinationViewControllers widget object to the currently selected Widget of the Table View Controller.

Next we will add an IBAction to the Table View Controller, which will be triggered when the user hits the Save button in the detail view.

@IBAction func unwindToWidgetList(sender: UIStoryboardSegue) {
    if let sourceViewController = sender.sourceViewController as? WidgetViewController, widget = sourceViewController.widget {
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            widgets[selectedIndexPath.row] = widget
            tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
        }
        else    {
            let newIndexPath = NSIndexPath(forRow: widgets.count, inSection: 0)
            self.widgets.append(widget)
            self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
        }
    }
}

Line 3: By checking if a row in the table view was selected we determine if the user updated an existing Widget or if he created a new one.

Line 4-5: In case the user updated an existing Widget we simply reload the row, as the update is being handled in the detail view controller.

Line 8-10: In case the user added a new Widget, we take the sourceViewControllers widget object and append it to the local array of Widgets. Afterwards we insert a new row into the TableView. The actual creation of the widget is being handled within the Detail View Controller.

Next open the Storyboard and add an Action Segue from the Save button to trigger the unwindToWidgetList as shown in the screenshot below:

loopback-swift-storyboard-2

Now open your Detail View Controller and override the prepareForSegue function:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if saveButton === sender {
        if let _ = widget {
            widget!.name = nameTextField.text ?? ""
            widget!.bars = Int(numberValueSlider.value)
            widget?.saveWithSuccess({ () -> Void in
                NSLog("Successfully updated Widget")
                }, failure: { (error: NSError!) -> Void in
                    NSLog(error.description)
            })
        }
        else    {
            if let name = nameTextField.text where name != "" {
                widget = AppDelegate.widgetRepository.modelWithDictionary(nil) as? Widget
                widget!.name = name
                widget!.bars = Int(self.numberValueSlider.value)
                widget?.saveWithSuccess({ () -> Void in
                    NSLog("Successfully created new Widget")
                    }, failure: { (error: NSError!) -> Void in
                        NSLog(error.description)
                })
            }
        }
    }
}

If the SaveButton was clicked we determine if the user is creating a new Widget or updating an exiting one.

Line 4-5: Sets the attributes of the local widget object to the updated values

Line 6-10: Then we call saveWithSuccess to save the changes to the LoopBack backend

Line 14-16: If the user is creating a new Widget then we instantiate a new Widget with modelWithDictionary(nil) and set its values accordingly

Line 17: Save the newly created Widget. By finally executing saveWithSuccess the object gets assigned its internal id value and gets created on the LoopBack Backend.

Done

That’s it now! By now you should have a Swift app using the LoopBack iOS SDK that provides CRUD functionality for the Widget model.

If you have any questions or feedback, just post a comment 😉