Caching Web Service Response Data in iOS

11/13/2018 Updated for Xcode 10/Swift 4.2

Many iOS applications obtain data via web APIs that return JSON documents. For example, the following table view controller uses the Kilo WebServiceProxy class to invoke a simple web service that returns a simulated list of users as JSON. The controller requests the user list when the view first appears, and reloads the table view once the data has been retrieved:

class ViewController: UITableViewController {
    var users: [User]?

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    
        // Load user data
        if (users == nil) {
            let serviceProxy = WebServiceProxy(session: URLSession.shared, serverURL: URL(string: "https://jsonplaceholder.typicode.com")!)
    
            serviceProxy.invoke(.get, path: "/users") { (result: [User]?, error: Error?) in
                if (error == nil) {
                    self.users = result ?? []
    
                    self.tableView.reloadData()
                }        
            }
        }
    }
    
    ...
}

User records are represented by instances of the following structure:

struct User: Codable {
    struct Address: Codable {
        let street: String
        let suite: String
        let city: String
        let zipcode: String

        struct Geo: Codable {
            let lat: String
            let lng: String
        }

        let geo: Geo
    }

    struct Company: Codable {
        let name: String
        let catchPhrase: String
        let bs: String
    }

    let id: Int
    let name: String
    let username: String
    let email: String
    let address: Address
    let phone: String
    let website: String
    let company: Company
}

The results are shown below:

This works fine when both the device and the service are online, but it fails if either one is not. In some cases this may be acceptable, but other times it might be preferable to show the user the most recent response when more current data is not available.

To facilitate offline support, the response data must be cached. However, since writing to the file system is a potentially time-consuming operation, it should be done in the background to avoid blocking the main (UI) thread. Here, the data is written using an operation queue to ensure that access to it is serialized:

class ViewController: UITableViewController {
    var userCacheURL: URL?
    let userCacheQueue = OperationQueue()

    var users: [User]?

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Response Data Cache"

        tableView.estimatedRowHeight = 2
        tableView.register(UserCell.self, forCellReuseIdentifier: UserCell.description())

        if let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
            userCacheURL = cacheURL.appendingPathComponent("users.json")
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // Load user data
        if (users == nil) {
            let serviceProxy = WebServiceProxy(session: URLSession.shared, serverURL: URL(string: "https://jsonplaceholder.typicode.com")!)

            serviceProxy.invoke(.get, path: "/users") { (result: [User]?, error: Error?) in
                if (error == nil) {
                    self.users = result ?? []

                    self.tableView.reloadData()

                    // Write the response to the cache
                    if let userCacheURL = self.userCacheURL {
                        self.userCacheQueue.addOperation() {
                            let jsonEncoder = JSONEncoder()

                            if let data = try? jsonEncoder.encode(self.users) {
                                try? data.write(to: userCacheURL)
                            }
                        }
                    }
                } else {
                    ...
                }
            }
        }
    }
    
    ...
}

Finally, the data can be retrieved from the cache if the web service call fails. The data is read from the cache in the background, and the UI is updated by reloading the table view on the main thread:

class ViewController: UITableViewController {
    ...

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // Load user data
        if (users == nil) {
            let serviceProxy = WebServiceProxy(session: URLSession.shared, serverURL: URL(string: "https://jsonplaceholder.typicode.com")!)

            serviceProxy.invoke(.get, path: "/users") { (result: [User]?, error: Error?) in
                if (error == nil) {
                    ...
                } else {
                    // Read the data from the cache
                    if let userCacheURL = self.userCacheURL {
                        self.userCacheQueue.addOperation() {
                            let jsonDecoder = JSONDecoder()

                            if let data = try? Data(contentsOf: userCacheURL) {
                                self.users = (try? jsonDecoder.decode([User].self, from: data)) ?? []

                                // Update the UI
                                OperationQueue.main.addOperation() {
                                    self.tableView.reloadData()
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    ...
}

Now, as long as the application has been able to connect to the server at least once, it can function either online or offline, using the cached response data.

Complete source code for this example can be found here.

Applying Style Sheets Client-Side in iOS

11/13/2018 Updated for Xcode 10/Swift 4.2

While native mobile applications can often provide a more seamless and engaging user experience than a browser-based app, it is occasionally convenient to present certain types of content using a web view. Specifically, any content that is primarily text-based and requires minimal user interaction may be a good candidate for presentation as HTML; for example, product descriptions, user reviews, or instructional content.

However, browser-based content often tends to look out of place within a native app. For example, consider the following simple HTML document:

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="initial-scale=1.0"/>
</head>
<body>
    <h1>Lorem Ipsum</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    <ul>
    <li>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</li> 
    <li>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</li>
    <li>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</li>
    </ul>
</body>
</html>

Rendered by WKWebView, the result looks like this:

Because the text is displayed using the default browser font rather than the system font, it is immediately obvious that the content is not native. To make it appear more visually consistent with other elements of the user interface, a stylesheet could be used to render the document using the system font:

<head>
    ...
    <style>
    body {
        font-family: '-apple-system';
        font-size: 10pt;
    }
    </style>
</head>

The result is shown below. The styling of the text now matches the rest of the UI:

However, while this approach may work for this simple example, it does not scale well. Different app (or OS) versions may have different styling requirements.

By applying the stylesheet on the client, the presentation can be completely separated from the content. This can be accomplished by linking to the stylesheet rather than embedding it inline:

<head>
    ...
    <link rel="stylesheet" type="text/css" href="example.css"/>
</head>

However, instead of downloading the stylesheet along with the HTML document, it is distributed with the application itself and applied using the load(_:mimeType:characterEncodingName:baseURL:) method of the WKWebView class. The first argument to this method contains the (unstlyed) HTML content, and the last contains the base URL against which relative URLs in the document (such as stylesheets) will be resolved:

class ViewController: UIViewController {
    var webView: WKWebView!

    override func loadView() {
        webView = WKWebView()

        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Client-Side CSS"

        if let url = Bundle.main.url(forResource: "example", withExtension: "html"),
            let data = try? Data(contentsOf: url),
            let resourceURL = Bundle.main.resourceURL {
            webView.load(data, mimeType: "text/html", characterEncodingName: "UTF-8", baseURL: resourceURL)
        }
    }
}

In this example, the document, example.html, is loaded from the main bundle. In a real application, it would most likely be loaded from an actual web server.

The stylesheet, example.css, is stored in the resource folder of the application's main bundle:

body {
    font-family: '-apple-system';
    font-size: 10pt;
}

The results are identical to the previous example. However, the content and visual design are no longer tightly coupled and can vary independently:

Complete source code for this example can be found here.

Named Parameters in JDBC Queries

Prepared statements are a common way to execute parameterized queries in JDBC. For example, the following SQL might be used to retrieve a list of all users whose first or last name matches a particular character sequence:

SELECT * FROM user WHERE first_name LIKE ? or last_name LIKE ?

Parameter values are supplied at runtime via indexed setter methods defined by the PreparedStatement class:

statement.setString(1, pattern);
statement.setString(2, pattern);

This works fine for simple queries, but it becomes increasingly difficult to manage as the number of parameters grows. It is also redundant – although this query only requires a single argument, two parameter values must be supplied.

The Java Persistence API (JPA) provides a more convenient alternative using named parameters. For example, the above query might be written as follows in JPQL:

SELECT u FROM User u WHERE u.firstName LIKE :pattern or u.lastName LIKE :pattern

This is more readable and less verbose, as the caller only needs to provide the value of the "pattern" parameter once. It is also more resilient to changes, as the arguments are not dependent on ordinal position. Unfortunately, it requires a JPA-compliant object-relational mapping (ORM) framework such as Hibernate, a dependency that may not be satisfiable in all situations.

The Parameters class provided by the HTTP-RPC framework brings named parameter support to JDBC. The parse() method of this class is used to create a Parameters instance from a JPA-like SQL query; for example:

SELECT * FROM user WHERE first_name LIKE :pattern or last_name LIKE :pattern

It takes a string or reader containing the query text as an argument:

Parameters parameters = Parameters.parse(query);

The getSQL() method of the Parameters class returns the processed query in standard JDBC syntax. This value can be used in a call to Connection#prepareStatement():

PreparedStatement statement = connection.prepareStatement(parameters.getSQL());

Parameter values are specified and applied as follows:

parameters.put("pattern", pattern);
    
parameters.apply(statement);

Once applied, the query can be executed:

ResultSet resultSet = statement.executeQuery();

Note that Parameters is not limited to queries; it can also be used for updates.

A complete example using the Parameters class can be found here. It is a simple REST service that allows a caller to search a database of pets by owner name.

See the project README for more information.

Building a Simple Barcode Scanner in iOS

11/13/2018 Updated for Xcode 10/Swift 4.2

Although near-field communication (NFC) technologies such as Apple Pay are continuing to gain traction as a means of inter-device communication, optical mechanisms such as barcodes (both 1D and 2D) are still widely used across a broad range of industries.

This tutorial demonstrates how to easily incorporate barcode scanning functionality into an iOS application. The sample application will use the AVFoundation framework to capture and analyze barcode images using the device's camera.

Create the Xcode Project

The first step is to create the Xcode project we'll be using to build the example app.

  • Open Xcode and select File | New | Project from the menu.
  • In the project template dialog, select iOS > Single View Application and click "Next".
  • Name the product "BarcodeScanner" and fill in the remaining fields as appropriate for your team and organization. Ensure that Swift is selected as the development language and click "Next".
  • Save the project to an appropriate location on your system.

Although it doesn't actually do anything yet, you should now be able to run the application by selecting your device in the toolbar and clicking the "Run" button or by pressing Command-R. Note that, since the application will use the camera, it needs to be run on an actual device and must be signed. Make sure that an appropriate development team is selected in the Signing section of the General tab for the "BarcodeScanner" target before attempting to run the app.

Add the CameraView Class

Before we can display a camera preview to the user, we need to create a class to represent the camera view:

  • Select ViewController.swift in the Project Navigator.
  • Add the following line to the imports section:
import AVFoundation
  • Add the following class declaration immediately before the ViewController class that was automatically generated by Xcode:
class CameraView: UIView {
    override class var layerClass: AnyClass {
        get {
            return AVCaptureVideoPreviewLayer.self
        }
    }

    override var layer: AVCaptureVideoPreviewLayer {
        get {
            return super.layer as! AVCaptureVideoPreviewLayer
        }
    }

    func updateOrientation() {
        let videoOrientation: AVCaptureVideoOrientation
        switch UIDevice.current.orientation {
        case .portrait:
            videoOrientation = .portrait

        case .portraitUpsideDown:
            videoOrientation = .portraitUpsideDown

        case .landscapeLeft:
            videoOrientation = .landscapeRight

        case .landscapeRight:
            videoOrientation = .landscapeLeft

        default:
            videoOrientation = .portrait
        }

        layer.connection?.videoOrientation = videoOrientation
    }
}

This class extends UIView and overrides the layerClass property to specify that it should be backed by an instance of AVCaptureVideoPreviewLayer. It also overrides the layer property to cast the return value to AVCaptureVideoPreviewLayer, which will make it easier to access the layer's properties later.

Finally, the class declares an updateOrientation() method that synchronizes the video orientation of the layer's capture connection with the device orientation. This method will be called by the view controller to initialize the view and to update it when the device orientation changes.

Add the Camera View to the View Controller

Next, we'll add the camera view to the view controller:

  • In the ViewController class, declare a member variable to contain the camera view. Since we'll be creating the view instance programmatically, we don't need to tag it as an outlet:
var cameraView: CameraView!
  • Override the loadView() method to initialize the view:
override func loadView() {
    cameraView = CameraView()

    view = cameraView
}

Although the camera view will now be visible when we run the app, it won't yet show anything but a black rectangle. We'll fix this in the next section.

Configure the Capture Session

In order to get the camera view to actually reflect what the camera is seeing, we need to connect it to an AV capture session. We'll use a dispatch queue to execute the more expensive session operations so the UI isn't blocked while waiting for them to complete:

  • Add member variables for the capture session and dispatch queue to ViewController:
let session = AVCaptureSession()
let sessionQueue = DispatchQueue(label: "Session Queue")
  • Add the AVCaptureMetadataOutputObjectsDelegate protocol to the view controller class:
class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    ...
} 
  • Update the viewDidLoad() method to initialize the capture session and initialize the camera orientation:
override func viewDidLoad() {
    super.viewDidLoad()

    title = "Barcode Scanner"
    
    session.beginConfiguration()

    if let videoDevice = AVCaptureDevice.default(for: .video) {
        if let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice),
            session.canAddInput(videoDeviceInput) {
            session.addInput(videoDeviceInput)
        }

        let metadataOutput = AVCaptureMetadataOutput()

        if (session.canAddOutput(metadataOutput)) {
            session.addOutput(metadataOutput)

            metadataOutput.metadataObjectTypes = [
                .code128,
                .code39,
                .code93,
                .ean13,
                .ean8,
                .qr,
                .upce
            ]

            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
        }
    }

    session.commitConfiguration()

    cameraView.layer.session = session
    cameraView.layer.videoGravity = .resizeAspectFill

    cameraView.updateOrientation()
}

Add Camera Usage Description to Info.plist

Access to the camera in an iOS application requires the user's permission. In order for iOS to ask for permission, we need to provide a string explaining what the application plans to do with the camera:

  • Add the camera usage description to Info.plist:
<key>NSCameraUsageDescription</key>
<string>to scan barcodes</string>

The application still doesn't do much, but it will now at least prompt the user for permission to access the camera:

Start and Stop the Capture Session

In order for the application to actually display what the camera is seeing, we need to start the capture session. We'll do this when the view appears. We'll also stop the session when the view disappears:

  • Add the following methods to ViewController to start and stop session capture:
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    sessionQueue.async {
        self.session.startRunning()
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    sessionQueue.async {
        self.session.stopRunning()
    }
}

While it isn't capable of scanning barcodes yet, the application will now at least correctly show the camera preview:

Handle Orientation Changes

Although it now displays the preview, the application doesn't yet respond to changes in orientation. Next, we'll add code to update the camera orientation when the device is rotated:

  • Add the following method to ViewController to update the preview orientation when the device orientation changes:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    cameraView.updateOrientation()
}

Now, when the device is rotated, the preview will reflect the correct orientation.

Capture Barcode Values

Finally, we're ready to add the code that actually captures barcode values. We'll do this using the metadataOutput(_:didOutput:from:) method of the AVCaptureMetadataOutputObjectsDelegate protocol:

  • First, add the following property to ViewController:
var isShowingAlert = false
  • Next, add this method:
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
    if !isShowingAlert,
        metadataObjects.count > 0,
        metadataObjects.first is AVMetadataMachineReadableCodeObject,
        let scan = metadataObjects.first as? AVMetadataMachineReadableCodeObject {
        let alertController = UIAlertController(title: "Barcode Scanned", message: scan.stringValue, preferredStyle: .alert)

        isShowingAlert = true

        alertController.addAction(UIAlertAction(title: "OK", style: .default) { action in
            self.isShowingAlert = false
        })

        present(alertController, animated: true)
    }
}

When a barcode is recognized, the application will now extract the associated value and present it to the user in an alert view:

Summary

This tutorial demonstrated how to incorporate barcode scanning functionality into an iOS application using the AVFoundation framework. Complete source code for this example can be found here.

Regular Expression to Validate a Comma-Separated List of Email Addresses

Recently, I needed to create a regular expression to validate the format of a comma-separated list of email addresses. Just thought I’d share the result in case it is of use to anyone. The pattern I came up with for matching a single address is as follows:

[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+

Note that I didn’t attempt to handle every possible combination of valid characters – only what I’d consider the most common ones.

The pattern is simply duplicated for the comma-separated list. Here’s an example of applying the pattern in Java:

Pattern addressListPattern = Pattern.compile(String.format("%1$s(,\\s*%1$s)*",
    "[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+"),
    Pattern.UNICODE_CHARACTER_CLASS);

System.out.println(addressListPattern.matcher("xyz").matches()); // false
System.out.println(addressListPattern.matcher("foo@bar.com").matches()); // true
System.out.println(addressListPattern.matcher("foo@bar.com, xyz").matches()); // false
System.out.println(addressListPattern.matcher("foo@b-ar.com, f.oo@b-ar.co.uk").matches()); // true
System.out.println(addressListPattern.matcher("føo@b-ar.com, f-oo@b-ar.co.uk").matches()); // true

Note that Pattern.UNICODE_CHARACTER_CLASS is not necessary (or supported) in Android, which supports Unicode character matching by default.

Hope it helps!

Installing MongoDB on 32-bit Ubuntu 15.10

I recently decided to install MongoDB on an old Mac Mini I use for testing. This particular device is too old to run the latest version of OS X, so I'm currently running the 32-bit version of Ubuntu 15.10 on it. Unfortunately, MongoDB no longer provides installation packages for 32-bit Linux distributions, so I had to set it up manually. Since this was a fairly cumbersome process, I thought I would share the steps I took in case they are of use to anyone:

  1. Download 32-bit MongoDB binaries:
    curl -O https://fastdl.mongodb.org/linux/mongodb-linux-i686-3.2.4.tgz
    tar -zxvf mongodb-linux-i686-3.2.4.tgz
  2. Copy MongoDB binaries to /usr/bin:
    sudo cp mongodb-linux-i686-3.2.4/bin/* /usr/bin/
  3. Download startup script:
    curl https://raw.githubusercontent.com/mongodb/mongo/master/debian/init.d > init.d
  4. Move startup script to /etc/init.d directory:
    sudo mv init.d /etc/init.d/mongod
    sudo chmod 755 /etc/init.d/mongod        
  5. Create configuration script /etc/mongod.conf:
    storage:
      dbPath: /var/lib/mongo
      journal:
        enabled: true
      engine: mmapv1
    
    systemLog:
      destination: file
      logAppend: true
      path: /var/log/mongodb/mongod.log
    
    processManagement:
      fork: true
    
    net:
      port: 27017
      bindIp: 0.0.0.0
  6. Add "mongodb" user:
    sudo useradd --home-dir /var/lib/mongo --shell /bin/false mongodb
    sudo passwd mongodb
  7. Create /var/lib/mongo directory:
    sudo mkdir /var/lib/mongo
    sudo chown -R mongodb /var/lib/mongo
    sudo chgrp -R mongodb /var/lib/mongo
  8. Create /var/log/mongodb directory:
    sudo mkdir /var/log/mongodb
    sudo chown -R mongodb /var/log/mongodb
    sudo chgrp -R mongodb /var/log/mongodb
  9. Create /var/run/mongod.pid file:
    sudo touch /var/run/mongod.pid
    sudo chown mongodb /var/run/mongod.pid
    sudo chgrp mongodb /var/run/mongod.pid
  10. Initialize service:
    sudo update-rc.d mongod defaults
  11. Restart server:
    sudo shutdown -r now

Displaying an Activity Indicator while Loading Table View Data in the Background

11/13/2018 Updated for Xcode 10/Swift 4.2

iOS applications often need to retrieve data from a remote source such as a web service. This data is commonly loaded in the background to avoid blocking the main UI thread, causing the application to appear unresponsive.

While the data is being loaded, an application typically displays an activity indicator view to inform the user that something is happening. Often, this is done by dynamically adding an instance of UIActivityIndicatorView as a subview of either the current view or one of the view's ancestors and making the indicator active. However, if the view is a table or collection view, there is another option: the activity indicator can be set as the view's background view and shown or hidden as needed.

For example, the following code snippet shows a partial implementation of a simple table view controller. The table's data is provided by an array of strings stored in the rows property. The activity indicator is created and assigned as the table’s background view in loadView():

class ViewController: UITableViewController {
    var activityIndicatorView: UIActivityIndicatorView!
    
    var rows: [String]?
    
    let dispatchQueue = DispatchQueue(label: "Example Queue")
    
    override func loadView() {
        super.loadView()
    
        activityIndicatorView = UIActivityIndicatorView(style: .gray)
    
        tableView.backgroundView = activityIndicatorView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        title = "Activity Indicator"
    }
    
    ...
}

The data is "loaded" in viewWillAppear(). The controller simulates a web service call by sleeping for three seconds in the background, then populating the rows array and reloading the table data on the main thread. The activity indicator is shown while the background operation is executing, and is hidden when the operation is complete. Because table views in the default "plain" style show separator lines even when the table is empty, the controller also sets the table view's separator style to .none so the lines don't interfere with the activity indicator when it is visible:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    if (rows == nil) {
        activityIndicatorView.startAnimating()

        tableView.separatorStyle = .none

        dispatchQueue.async {
            Thread.sleep(forTimeInterval: 3)

            OperationQueue.main.addOperation() {
                self.rows = ["One", "Two", "Three", "Four", "Five"]

                self.activityIndicatorView.stopAnimating()

                self.tableView.separatorStyle = .singleLine
                self.tableView.reloadData()
            }
        }
    }
}

Finally, the controller overrides the numberOfSections(in:), tableView(_:numberOfRowsInSection:), and tableView(_:cellForRowAt:) methods to provide the table content:

override func numberOfSections(in tableView: UITableView) -> Int {
    return (rows == nil) ? 0 : 1
}

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellIdentifier = "cell"
    let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) ?? UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)

    cell.textLabel?.text = rows?[indexPath.row]

    return cell
}

The complete source code for this example can be found here.