All posts by Jeremy

Apple WatchConnectivity

While learning Swift, one of the new features I wanted to add to the Nutrition GPA app was an Apple Watch app/extension.  Getting into WatchKit was surprisingly easy, in part because development follows a similar paradigm to UIKit interfaces but the watch user interface features are somewhat limited.

To share data between devices, Apple makes it relatively easy to pass data back and forth from the host app (on the iPhone) to the watch app.  I started with reading some framework documentation and looking at some sample code.

For my purposes, I wanted users to be able to take the Nutrition GPA daily quiz on their watch.  At first, I thought I could define “Shared Groups” to write to a common file location, but that didn’t work.  Not needing bi-directional communication, I decided to send the quiz “answers” to the phone – which would then store them to the local database.

WCSessionDelegate

The first step is defining a class to handle the watch connectivity session messages.  This class will handle various state changes and posting notifications it received data from either the watch or the phone to the rest of your app.  You’ll instantiate an instance of this class in both your AppDelegate and ExtensionDelegate classes.

class WatchSessionDelegate: NSObject, WCSessionDelegate
{
   // Unpack the data and post a notification
   func session(_ session: WCSession, didReceiveMessageData messageData: Data)
   {
      var cmd = MessagePackage(.sendMessageData, .received)
      cmd.dataPackage = EncodedData(messageData)

      DispatchQueue.main.async { NotificationCenter.default.post(.didReceiveData, cmd) }
   }

   // Unpack the data, use callback for response
   func session(_ session: WCSession, didReceiveMessageData messageData: Data, replyHandler: @escaping(Data) -> Void)
}

There are a handful of session state methods that you may want to implement specifically for your application.   They provide hooks for connectivity, reachability,  and session state.  Refer to the Apple Docs.

AppDelegate

Configuring the app delegate simply requires creating a private WatchSessionDelegate instance and setting the WCSession properties appropriately.

private var sessionDelegate = WatchSessionDelegate()

func application(_ app: UIApplication, _ launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
   // Make sure WatchConnectivity is supported
   if WCSession.isSupported()
   {
      WCSession.default.delegate = sessionDelegate
      WCSession.default.activate()

      // Add a notification
      NotificationCenter.default.addObserver(
         self,
         selector: #selector(type(of: self).didReceiveData(_:)),
         name: .didReceiveData,
         object: nil)
   }
}

@objc func didReceiveData(_ notif: Notification)
{
   // unpack the notification
   guard let message = notification.object as? MessagePackage else { return }
   guard let quizData = message.dataPackage?.quizData else { return }

   // save the data
   UserData.shared().saveQuizData(quizData)
}
ExtensionDelegate

On the watch side, the ExtensionDelegate is similar.  There’s a bit more code to process and handle pending background tasks in the event connectivity disappears.

private lazy var sessionDelegate: WatchSessionDelegate = { return WatchSessionDelegate() }()

private var activeObservation: NSKeyValueObservation?

private var contentPending: NSKeyValueObservation?

private var wcBackgroundTasks = [WKWatchConnectivityRefreshBackgroundTask]()

override init()
{
   if WCSession.isSupported()
   {
      activeObservation = WCSession.default.observe(\.activationState) { _, _ in DispatchQueue.main.async {
         self.completeBackgroundTasks()
       }

      contentPending = WCSession.default.observe(\.hasContentPending) { _, _ in DispatchQueue.main.async {
         self.completeBackgroundTasks()
      }

      WCSession.default.delegate = sessionDelegate
      WCSession.default.activate()
   }
}

func completeBackgroundTasks()
{
   guard WCSession.ddefault.activationState == .activated, WCSession.default.hasContentPending == false else { return }

   wcBackgroundTasks.forEach { $0.setTaskCompletedWithSnapshot(false) }

   let date = Date(timeIntervalSinceNow: 1)
   WKExtension.shared().scheduleSnapshotRefresh(withPreferredDate: date, userInfo: nil) { error in if let error = error { 
      print("scheduledSnapshotRefresh: Error \(error)")
      }
   }
   
   wcBackgroundTasks.removeAll()
}

With the appropriate handlers added to the two application delegates, we need to consider the packaging of the shared data.   In the app delegate code (above), I’ve used a MessagePackage object without defining it.

MessagePackage & EncodedData

These two structures provide a handy transport wrapper around the actual data we want to send.  Having these classes allows us to also include some state information (sending, receiving, replied, failed) and provide some helper serialization methods through the use of dynamic properties.

In my case, I opted to use NSKeyValueEncoding/Decoding to perform the object serialization.  Key value coding is a common pattern on iOS and provides a binary Data package.

Because Swift wraps the phone and watch in unique namespaces, the class name is specific to the platform when serializing or deserializing (e.g. NutritionGPA.QuizData… and NutritionGPAExtension.QuizData…).

Early on, this was causing the data to serialize on one device and silently fail to decode on the other platform when it was received.  There are a couple ways to resolve this issue but I opted to explicitly declare each object with @objc to avoid the name changes.

@objc(QuizData) class QuizData: NSObject, NSCoding

You’ll need to do this for any custom code that the instance also serializes.

Alternatively, NSKeyedArchiver can be configured to look for specific classes and store/retrieve custom code.  It just requires setting the appropriate class name before any store/retrieve operation – which felt a little clumsy to me.  You can read about both choices on StackOverflow and decide what best works for your application.

struct MessagePackage
{
   enum Command: String {
      case sendMessageData = "SendMessageData"
   }
 
   enum Status: String {
      case sent = "Sent"
      case received = "Received"
      case replied = "Replied"
      case failed = "Failed"
   }

   var command: Command
   var status: Status
   var dataPackage: EncodedData?
   var errorMessage: String?

   init(command: Command, status: Status)
   {
      self.command = command
      self.status = status
   }
}

struct EncodedData
{
   var encodedData: Data
   var quizData: QuizData?
   {
      let optional = NSKeyedUnarchiver.unarchiveObject(with: encodedData)
      guard let quiz = optional as? QuizData else
      {
         print("Error: unable to unarchive")
         return nil
      }
      
      return quiz
   }

   init(_ encodedData: Data)
   {
      let data = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(encodedData)
      guard let dictionary = data as? [String: Any] else {
         fatalError("Failed to unarchive")
      }

      self.init(dictionary)
   }

   init(_ encodedDict: [String: Any])
   {
      guard let encodedData = encodedDict[Payload.dataKey] as? Data else 
      {
         fatalError("Error: incorrect key")
      }
      
      self.encodedData = encodedData
   }
}

Now that we have the app delegates setup and some handy wrappers around serializing our data – how do we send it?

SessionCommands & DataProvider

Both of these protocols provide some helper methods that are adopted by the classes that transmit data.

SessionCommands provides a single method that creates a MessageData object and sends it via Watch Connectivity.

DataProvider handles archiving both the raw quiz data and creating the encoded MessageData transparently to adopting classes.

Both protocols are implemented by the results view controller on the Watch app.  When the user presses “Finish”, the quiz data is encoded and sent to the phone for storage.

protocol DataProvider
{
   func messageData(_ obj: AnyObject) -> Data
}

extension DataProvider
{
   func messageData(_ obj: AnyObject) -> Data
   {
      let data = try? NSKeyedArchiver.archiveData(withRootObject: obj, requiringSecureCoding: false)
      guard let rawQuizData = data else {
         fatalError("Unable to archive")
      }
      
      let payload = try? NSKeyedArchiver.archivedData(withRootObject: [Payload.dataKey: rawQuizData], requiringSecureCoding: false)
      guard let payloadQuiz = payload else {
         fatalError("Unable to archive")
      }

      return payloadQuiz
   }
}

protocol SessionCommands
{
   func sendMessageData(_ messageData: Data)
}

extension SessionCommands
{
   func sendMessageData(_ messageData: Data)
   {
      var command = MessagePackage(command: .sendMessageData, status: .sent)
      command.dataPackage = EncodedData(messageData)

      guard WCSession.default.activationState == .activated else { return }

      WCSession.default.sendMessageData(messageData, replyHandler: { replayData in
      command.status = .replied
      command.dataPackage = EncodedData(replyData)
       }, errorHandler: { error in
         command.status = .failed
         command.errorMessage = error.localizedDescription
       })
   }
}
Debugging

There are also a number of test scenarios to consider: multiple watches paired to a single iPhone, connectivity lost (phone and watch too far apart), or handling background tasks.

Finally, it’s worth adding a bunch of unit tests around the serialization / deserialization to make sure the data can properly flow from device to device.

While it certainly helps to write unit tests to help isolate potential problems, it’s also useful to debug both the watch and phone on the simulator simultaneously.  If you’re not seeing data from one device to the other, consider debugging both app and extension at the same time in Xcode.

To do this, start debugging either the app or extension, then launch the other app on the device and select “Debug | Attach to Process” in Xcode to attach to both at the same time.

Finishing Up

I omitted some details in the code above – notably sending and handling the various notifications in both applications.

Generally, the app delegate will receive the quiz taken on the watch, then save it to the local database.  This is great – but some additional thought is required to determine how the application should respond if the user is interacting with it.  For example, if the user is on the start screen – which displays the total number of quizzes taken – the display should update to reflect new data.  Or, if the user is looking at their quiz history – the list should update to include this latest entry.

These aren’t difficult problems to solve – largely they involve having each view controller subscribe to a data notification (or redefine them in the phone’s app delegate specifically for these view controllers) and recalculating their display.

Last, there are a few areas where – now that things are working – I’d consider refactoring or a slightly more flexible approach.  Specifically, the places where “fatalError” is called create the potential for a poor end-user experience.  This code would probably be better to fail more gracefully!

Nutrition GPA and Swift

Introduction

Originally written in 2014, the Nutrition GPA (GPA) app was authored in Objective-C and XIB files.  While the app was designed for phones, there were some screen size assumptions baked into the code that made it somewhat fragile to the Plus and X devices that have been introduced in the past few years.

Having experimented with Swift Playgrounds and done some reading, I wanted to try to faithfully port/convert the Objective-C code to Swift while modernizing other aspects of the app as I went.

Motivations

  • Swift has been around since 2014 and is becoming increasingly necessary to iOS development.
  • When the GPA app was written, it didn’t use Storyboards, so layout has suffered with newer devices like the iPhone X.
  • While a relatively simple app – it has just a handful of views – it’d be fun to experiment with WatchKit and add some new capabilities.

Database

The app uses a local SQLite store for quiz results and backing data.  Over the years, I’ve worked quite a bit with SQLite.  Generally, the pattern I’ve followed has been to create an object that represents an individual table row.  This object knows how to insert, update, and delete itself from the SQLite database.  The downside of this approach is that the object needs to know a lot about the underlying database representation.  And, there’s potentially a lot of duplicated code for boiler-plate operations to manage the interactions with the backing store.

Previously to bootstrap a record, each object would have a method similar to this:

+ (id)addObjectToTable(sqlite3*) db
{
   intsuccess = SQLITE_OK;
   if(insert_statement== nil)
   {
      const char*sql = "INSERT INTO some_table 
                 (tID) VALUES ('0')";

      success = sqlite3_prepare_v2(db, sql, -1,
                    &insert_statement, NULL);

      NSAssert1(success == SQLITE_OK, @"add
           object: failed to prepare with message 
           '%s'", sqlite3_errmsg(db));
   }

   success = sqlite3_step(insert_statement);
   sqlite3_reset(insert_statement);
}

With the GPA app, I’ve moved to FMDB, which abstracts much of this repetitive code and provides an opaque interface to the underlying data store by representing rows in a table as dictionaries.

Now, I can generalize the insert, update, and delete operations to three different APIs.

private func insertRecord(_record:
   Dictionary<String, Int>, tableName: String) -> Int

private func updateRecord(_record: 
   Dictionary<String, Int>, tableName: String) -> Int

private func deleteExistingRecords(_table: 
   String, _field: String, value: Any)

Each object that can store data to the database implements the DatabaseObject protocol, which has a single function to provide a dictionary of key/value pairs that the object wants to save or retrieve.

protocolDatabaseObject
{
   func asDictionary() -> Dictionary<String, Int>
}

And, the database insert becomes much more generic:

private func insertRecord(_record: 
    Dictionary<String, Int>, 
    tableName: String) -> Int
{
   var insertedRecordId =   
         Int64(Constants.VALUE_UNSET)
   var insertParams = record;

   insertParams.removeValue(forKey: FIELD_PK)

   var fieldNames = Array<String>()
   var namedParams = Array<String>()

   for key in insertParams.keys
   {
      fieldNames.append(key)
      namedParams.append(
         String.init(format:":%@", key))
   }

   let sql = String.init(format:
     "INSERT INTO %@ (%@) VALUES (%@)", 
        tableName, 
        fieldNames.joined(separator: ","), 
        namedParams.joined(separator: ","))

   databaseQueue.inDatabase{ (database) in
      database?.executeUpdate(sql,   
          withParameterDictionary: insertParams)
   }

   return Int(insertedRecordId)
}

extension Database
{
   func save(_databaseObject: DatabaseObject) 
      -> Int?
   {
      switch databaseObject
      {
      case is ObjA: return 
      insertRecord(databaseObject.asDictionary(), 
         tableName: TABLE_A)

      case is ObjB: return 
      insertRecord(databaseObject.asDictionary(), 
         tableName: TABLE_B)

      default: fatalError("Database: attempt to 
         store an unsupported database object")
   }

   return nil
}

Overall, I’m pleased with decoupling the database functionality from the objects that hold the data.  I experimented with cloud-based storage, like Parse, and having a logical separation of the backing storage from the object was very important.  Rather than use SQLite, I could repurpose the database class to use CloudKit, Parse, or another service without having to also refactor all of the storage objects to “know” about this change.

Gotchas

For the most part, the app port went very smoothly.  I opted to try to use all Swift collection classes, rather than mix-and-match the Objective-C types, like NSMutableArray, etc.  This made things much easier.

Still, there were a few moments where I had to do some Google searches and slight refactoring.

Exclusive Access to Memory

The app provides a 7-day, 30-day, and lifetime average score.  When the view loads, it looks at the recorded history and does some simple math.  To represent these values, I created an array to store the results:

private var avgScore: Array = [0, 0, 0]

During initialization, the app passes references (via inout parameter) to retrieve the respective values:

AppDelegate.pastAverages(avgAll: &avgScore[0], avgWeek: &avgScore[1], avgMonth: &avgScore[2])

Swift didn’t like this and gave me a run-time error.  There’s a build setting to enforce exclusive access to memory at both run-time and compile-time.  Disabling the build option solved the problem but really refactoring the code to use tuples or returning an array would be a better option.

Forced Unwrapping

During the project, my thinking on forced unwrapping (!) evolved – to a stance of: use with caution.  If the forced unwrap fails, the app will crash which is obviously undesirable.

There were times – like with controls, resources, or casting known variables that I felt safe applying the exclamation point – but it wasn’t without some thought of perhaps writing the same piece of code in a different way.

Extensions to Objective-C functions

For whatever reason, some of my favorite Objective-C methods don’t have exact Swift equivalents.  So, extensions to the rescue.  I added a handful for String and FileManager.

And, found myself considering how to use extensions to separate implementation groups more cleanly.  Having a single implementation file (and no-preprocessor) also made me consider applying Swift access controls – just to give myself hints on how I expected various classes to be used.

rawValue for Enums

Swift offers a lot more language flexibility than Objective-C – particularly with enums, tuples, and generics.  In my older code, I relied on ordinal rank of enum members for access to arrays (see the average score example above) – and found myself declaring Swift arrays and using the “raw value” more frequently than I’d expected.

Conclusion

The GPA app port to Swift went very smoothly.  I’m certainly glad to have implemented the UI with a Storyboard – it means a lot less CGMakeRect() and “magic” numbers floating through the codebase. The hiccups were all in all pretty minor.

There are some nice things provided by the language that I still need to come to grips with.  I felt my coding style evolving to fit the language and don’t think it’s finished yet!

IF Tracker: FAQ

Why aren’t more foods included in the IF Tracker?

We are working to add additional foods to the database. However, in order to calculate the IF Rating of a food, we need extremely detailed nutrient data about the food. For example, the formula takes into account the amounts of individual fatty acids such as arachidonic acid or docosahexaenoic acid—not just the amount of saturated or monounsaturated fat. For many commercially available (i.e., processed) foods, the manufacturers do not provide enough nutritional detail to allow us to calculate a rating.  Calculating a rating in the absence of these details could produce inaccurate or misleading ratings. Although we sympathize with your desire for a larger list of foods, we feel that it’s important to provide ratings only when we have enough nutrient data to calculate an accurate rating.

Why can’t I add new foods?

We’d love to include a tool that would allow you to calculate the IF Ratings for foods that are not in the database. However, in order to get an accurate rating, you’d have to enter extremely detailed nutrient data about the food. For example, the formula takes into account the amounts of individual fatty acids such as arachidonic acid or docosahexaenoic acid—not just the amount of saturated or monounsaturated fat.  Because this information is not generally available to consumers we determined that a custom entry tool would be of little use.

What is the IF Rating for baking powder or baking soda?

Baking powder and baking soda are both neutral (0).   The addition of these ingredients does not change the IF Rating of a recipe. Simply calculate your recipes without them.

Is there a way to substitute or create equivalent flours from millet, amaranth, or almonds?

To the extent that the flours are simply ground up grains or nuts, the rating for 1 ounce of flour would be approximately the same as the rating for 1 ounce of the corresponding grain.  For reference, a cup of flour is about 5 ounces.

Why do the IF Ratings in the app differ from “The Inflammation Free Diet”?

Since the book was originally published in 2006, additional research prompted the author, Monica Reinagel, to make some updates to the rating formula, particularly in the way that the glycemic load impacts the ratings.  The result was some minor changes in the IF Ratings for certain fruits, legumes, and dairy products, which are reflected in the ratings on NutritionData.com.

In addition, there have been some dramatic changes in the way salmon are farmed.  The latest data from the USDA indicates that changes in the food given to farmed salmon have resulted in a product that is now comparable to wild salmon in terms of the anti-inflammatory properties.

Unfortunately, the USDA only updated the data for raw salmon, not the cooked versions.  Because the listings on NutritionData.com are automatically generated based on the USDA database, they are only as up-to-date as the underlying data.  In the IF Tracker, Monica Reinagel was able to interpolate more accurate ratings for cooked salmon as well.

While it may be confusing to have multiple sources which don’t all line up, this is a dynamic area of research and the information doesn’t “stand still.”  Unfortunately, it’s not always possible to keep all the published instances perfectly synchronized with the latest information.

In those cases where there are discrepancies between the ratings in the book, NutritionData.com, and/or the IF Tracker app, the ratings used calculated in the IF Tracker represent the latest most up-to-date calculations.  But, with the exception of the dramatic change in farmed salmon, the changes are basically nuances on the original ratings.  Although it may seem that something changing from negative to positive or the reverse is a big deal, it usually represents a minor shift on a large continuum.  Assigning foods individual ratings inevitably encourages people to think of each food as being inflammatory or anti-inflammatory.  But as we hope you’ve learned from the book, it’s the sum of all foods eaten that really matters.

Updated Site

Just a quick note that we’ve updated our website. Sorry for any bookmarks or links that got confused in the process, but the new layout and organization is not only a lot cleaner, but allows for greater flexibility as we move forward.

Adding a TextField to an AlertView

Today, I was looking at how to add password protection to an app.  The basic design was the user could set a password via the options, then when they relaunched the app, would be asked to re-enter the numeric sequence.  If they got it right, the app would proceed as normal otherwise, terminate.

Ultimately, I went with the solution proposed by Jeff LaMarche on his blog.  But, many of the other solutions I found via Google appeared to use private APIs (such as addTextFieldWithValue:) which is a sure fire way to get rejected these days.

If you haven’t read the blog, or even better – his book, they are both great.  The book offers a very easy introduction to the iPhone SDK using practical examples.  It’s a great starting place for developers new to the SDK.

Managing Assets & Viewing Bundle Contents

Managing all of the assets that go into an iPhone app via Xcode can sometimes be tedious.  It seems somewhat counter intuitive to me to have the IDE manage these resources too.  Maybe I’m in the minority, but I’d prefer to just work with source code and NIBs in Xcode and keep track of the “other stuff” on my own.

The other day I was looking around for a solution to remove the non-Interface Builder assets from Xcode, organize these files into folders within my bundle, and remove the tons of Groups or Folder References from the IDE.  Happily, the answer wasn’t too bad.

Xcode Options

When you drag a resource into Xcode, it presents you with a dialog asking how to treat it:

asset_dialog

Starting from the top, the checkbox will copy the assets (and preserve the folder hierarchy, if present) while placing the resources at the root folder of the destination group.  The default radio box is to “Recursively create groups for any added folders” which is generally what you’ll want, especially if you’re using Interface Builder too.  In your bundle, these resources are placed at the “root” meaning to access them in your code, you’ll just need to specify a name.

The other radio option, “Create Folder References…”, creates a folder tree both inside Xcode and in your bundle.  However, the resources cannot be seen by Interface Builder and you’ll need to use a path if you load them at run-time.

Build Phase Copy

If you like the folder hierarchy in your bundle, or just want to move this management largely out of Xcode, it’s pretty easy to do it on your own by adding a new build phase, as shown below.  Right click on your Target and follow the cascading menus.

build_phase

In the build phase, add the following script

PBXCP=${DEVELOPER_DIR}/Library/PrivateFrameworks/DevToolsCore.framework/Resources/pbxcp
BASE=${PROJECT_DIR}/assets
DST=${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}
${PBXCP} -exclude .svn ${BASE} "${DST}"

The script calls an iPhone tool to recreate your folder structure inside your build directory before the final bundle is created.  With the script, I can remove any links or asset associations within Xcode except those I want to use in IB.  I maintain a folder “assets” off my product directory where I place any files that I’d like bundled in my app.

Checking Bundle Contents

Perhaps a trick most folks know, but I thought it was worth mentioning, is you can inspect and check the contents of your app bundle by following either navigating there via the Terminal or right clicking the app and selecting “Show Package Contents”.

I had a question about the database format of iGive Blood that shipped in version 1.0 and managed to find an old build from Apple.  For products that you’ve shipped, it’s almost the same.

  1. In iTunes, right click the app and select “Show in Finder”
  2. Copy the IPA file to your desktop
  3. Rename the extension to ZIP
  4. Double click the ZIP to create an uncompressed folder.
  5. Open the Payload folder which has the app.

It’s actually a bit disappointing that it’s so easy to get to the package contents especially as the contents aren’t obscured.  Maybe that’s a subject for another post…