Monday, July 27, 2015

Housekeeping

This is actually the second post I have written this week.  I had intended on writing on a completely different topic this week, but after spending some time on it, I realized that it wasn't ready.  Maybe next week.

So this week I thought I would go over a few projects I have been working on.  Sorry, no code this week.

AngularJS - A few weeks ago I decide my next JavaScript framework to learn would be AngularJS.  So I spent some time gathering training resources and began my research into that topic this week.  It turns out it's not as daunting as I had expected.  I'm just in the infancy of my learning here but the concepts are comparable to other JavaScript frameworks I have worked with.

I guess, in my mind, I had put AngularJS up on a pedestal and I thought some great inspiration and productivity increase would come from spending time with it.  But to be honest I am having trouble finding the motivation since it looks so similar to other frameworks I have worked with.

Why I am I learning a JavaScript framework when, up till now, I have mostly talked about iOS development? The reality is, at my day job, I am currently responsible for a JavaScript based UI, so it is imperative that I keep my skill set in that area up to date.

The test app I am building to learn AngularJS is Bootstrap based.  Does anyone know of a good widget UI like jQueryUI is for jQuery?

View Playgrounds - I had on my training list a task to learn how to create and work with iOS views in a playground. I was specifically interested in designing custom views and poking around at the various nuances of UITableView.  I even walked through a tutorial on doing view animations in a playground and figured I could use that as a springboard into the areas I was most interested in.

I chose to look at UITableView first and I went as far as creating a playground that presented one.  From there I ran out of steam.  I'm not sure what I want to do with it now.  It's a lot easier to build a table view in a storyboard in a project than in code in a playground.  Yes, I'm I know I can pull a storyboard or a nib into a playground and use it there, but its unclear to me how that would result in the rapid prototyping that I want to do. Anyone ran across a good tutorial using UITableView in a playground?

Pain Logger New Features - I had a couple of requests for new features to my existing app, Pain Logger.  Essentially it was adding a new setting and adding CSV export for its internal data.  This app is built in Objective-C and, after working with Swift for the past few months, it felt awkward going back to Objective-C.  I made lots of typos but I got through it.

Upgrade XCode - I downloaded and installed the latest version of XCode 7, beta 4.  I decided that from now on I am only going to program in Swift 2 and above, so that meant I needed to convert my CloudKit test app to Swift 2.0.  I thought I could use XCode's conversion utility and it would go seamless.

I even reviewed all the changes that were going to be made prior to accepting them.  But it didn't work so well.  After the conversion, I ended up with 29 different errors from the ones I thought the conversion tool was going to do.

It seemed to make the changes it said it was going to make but because of these changes the new errors were then introduced. Most of the errors revolved around difficulties with callbacks that originally accepted optional NSError objects but now needed them to be explicitly unwrapped. Have others ran into these types of issues? Is it expected?

New App Progress - I made progress on my new app by adding CloudKit support to it.  This was a major milestone as before I was persisting nothing, and I was struggling with how the data should be stored locally before concentrating on cloud storage.

I made a strategic decision to invert that thinking by by-passing the local store cache and concentrating on the cloud storage first.  This turned out to be a really good idea. Now I can store my app's data in the cloud and I will be able to move forward.  I am a little nervous that I might have trouble when I get back to the local cache issue but for now any progress is better than nothing.

Meanwhile, I also read a compelling blog post by Soroush Khanlou titled "Cache Me If You Can" which pursued the idea that a lot of apps are using CoreData for local caching when they really don't need to.

I was leaning that way myself, but after reading Sorush's post I am even more compelled to believe my app won't use CoreData at all.  I think this will reduce the complexity of the persistence layer tremendously and allow me to make much needed progress.

It occurs to me now thinking about this, if you are already dealing with a REST api based web service, then if you interact with your local cache in the same way and assume the cloud store is always the source of ultimate truth then your API for both local and remote stores should look and act very similar and the complexity of the consuming app should be reduced.  I'll be interested in trying this concept out over the next several weeks.

Finally, after working on the updates to Pain Logger, I'm convinced that all my iOS projects from here on out will be strictly Swift based.  I'm even thinking I'll convert my existing apps to Swift as I add support for iOS 9.  Yes, that will be a huge undertaking.  But when I reach that goal I should have a good bit of reusable code between my two existing apps and the one I am working on now.

I think I'll stop here this week.  I hope to have something a little more interesting to talk about next week.  As always if you have any comments, suggestions or questions please don't hesitate to let me know.

Sunday, July 19, 2015

Self Sizing Static Cells

In my apps I use tables with static cells for things like settings.  One thing I have struggled with is how do you make the cells be the right sizes for the content, and more importantly how do you show and hide those cells?

For example I have one app where I have a cell that shows the current due date.  Clicking the cell opens up a cell below it that shows a date picker.  The user then chooses a new date and presses the row above it to close the date picker cell.

I had been overriding the tableView.heightForRowAtIndexPath method in my view controller and inside that providing explicit height values based on whether the cell is shown or not.

Obviously, returning zero for the height of the cell when it was hidden was easy, but hard coding the height when it wasn't hidden felt wrong.  Additionally, I had to capture a reference to the controls within the cell that was being hidden/shown and hide or show them as well otherwise they would still be visible even though the row's height was 0.

However after watching one of this year's WWDC videos (I don't remember which one, if you are interested let me know and I'll try to find it) I realized, that with auto layout, I should be using the self sizing cells feature implemented in iOS 8.

The WWDC video showed an example for a dynamic table using a text label.  Which makes all the sense in the world for cells that contain multiline labels where the total size isn't known till runtime.  But I was more interested in could this be done with static cell based tables where I had a good idea of the height but didn't want to hard code it.  

I googled this and found various answers from, "It can't be done" to "Here's a hack to make it work".  I didn't like either of those extremes nor any of the answers in between so I decided to build a sample app to test it out.  

In short my results were really good.  I think I have a better understanding of how to make this work and in the end I got rid of a lot of code that I had been using in the past to do this.  

You can divide the process of setting this up into 6 steps.

Step 1 - Set the table view properties

In my viewDidLoad method for the owning controller (a UITableViewController) I had to set two properties:

     tableView.estimatedRowHeight = 68.0  
     tableView.rowHeight = UITableViewAutomaticDimension  

The first one gives an estimated height for each row in the table. The documentation says this is a performance optimization that gives the table "a hint" as to how to size the table's cells.

The second attribute "rowHeight" has to be set to the constant value "UITableViewAutomaticDimension" which means the table view should use the default value for the given dimension.  Yes, I know, that is confusing, but I got that straight from the documentation itself.  I actually learned I should (to support self sizing cells) set this from the WWDC video not from the documentation, go figure.

Step 2 - Track hidden rows

For this step I used similar code to what I had used in past implementations.  For any cell I wanted to hide or show I created a boolean to represent the cell's current state (i.e. hidden or shown):

   var datePickerHidden = true {  
     didSet {  
       if (!datePickerHidden) {  
         datePickerButton.setTitle("Hide Date Picker", forState: UIControlState.Normal)  
       }  
       else {  
         datePickerButton.setTitle("Show Date Picker", forState: UIControlState.Normal)  
       }  
     }  
   }  
   
   var pickerHidden = false {  
     didSet {  
       if (!pickerHidden) {  
         pickerButton.setTitle("Hide Picker", forState: UIControlState.Normal)  
       }  
       else {  
         pickerButton.setTitle("Show Picker", forState: UIControlState.Normal)  
       }  
     }  
   }  
   

In my test app I also created a few buttons to toggle these cells, so I added a "didSet" function to each of these properties to change the title of the button to represent the current state of the cell.

Step 3 - Override tableView.heightForRowAtIndexPath

In my old code I did this as well, but by using the "UITableViewAutomaticDimension" constant for the tableView's rowHeight property, all you are concerned with is returning zero when the cell should be hidden:

   override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {  
     if (indexPath.row == 2 && datePickerHidden) {  
       return 0  
     }  
     if (indexPath.row == 3 && pickerHidden) {  
       return 0  
     }  
     return super.tableView(tableView, heightForRowAtIndexPath: indexPath)  
   }  
   

This cleaned up my old code considerably.  Note the final return statement that calls the super class method should the row at the given index path not be not hidden.  This works for the cells I want to show and hide as well as cells that are always visible.  

In my test app I only had two "hide-able" cells.

One other thing to note is that when you create a static cell base table view using a UITableViewController, you do not have to implement any of the UITableViewDelegate or UITableViewDataSource methods.  If you think about it, it makes sense as you are specifying everything in the storyboard, but I thought that was interesting as I had never thought of it that way.  

But in the case of hiding/showing cells you still have to implement this method.

Step 5 - Create method to toggle a cell's visibility

This is where the meat and potatoes of the code is.  You could code it to just set the values that are needed and update the table but I wanted to animate it a bit.  The animation I chose was as follows:

For the showing animation, the cell is first unhidden and the table is updated which causes the cell to animate to it's correct height when visible.  Then the content of the cell is animated from an alpha of 0 to 1.

For the hiding animation, I basically reverse the animation of showing.  First the content's alpha is changed from 1 to 0 and then the cell's height is animated to 0.

Note: You could choose not to change the alpha of the contained controls, but, in the hiding case, if you choose that option, then the cell will animate it's height to zero while the control stays visible and takes up it's normal intrinsic size.  Only when the animation is over will the control be resize to zero height and disappear from the view.  It looks weird.

I do all of this in the following method:

   func toggleRow(hidden:Bool, view:UIView, cell:UITableViewCell) {  
     if (hidden) {  
       UIView.animateWithDuration(0.25, animations: { () -> Void in  
         // Hide the picker  
         view.alpha = 0  
         }, completion: { (success) -> Void in  
           UIView.animateWithDuration(0.5, animations: { () -> Void in  
             // Now collapse the cell  
             cell.frame.size.height = 0  
             self.tableView.beginUpdates()  
             self.tableView.endUpdates()  
             }, completion: { (success) -> Void in  
               cell.hidden = true  
           })  
       })  
     }  
     else {  
       UIView.animateWithDuration(0.5, animations: { () -> Void in  
         // unhide the cell  
         cell.hidden = false  
         self.tableView.beginUpdates()  
         self.tableView.endUpdates()  
         }, completion: { (success) -> Void in  
           UIView.animateWithDuration(0.25, animations: { () -> Void in  
             // show the cell's contents  
             view.alpha = 1  
           })  
       })  
     }  
   }   
I really wanted to do these animations together (i.e. without cascading) but I found in practice cascading the animations and playing with the timings worked and looked best.

Step 6 - Wire everything up

The final step was to add @IBActions connections to my buttons to toggle the cells.  

Note: One thing (and this is an important thing) I ran into with the pickers, using auto layout, was their constraints. For example I had to pin the leading, trailing and top constraints for the date picker to it's superview as normal.  But for the bottom constraint I had to set it to be greater than or equal to 0.  

Also I had to change the Vertical content compression resistance priority to 740 from it's default of 750 so as to allow the picker to be resized when the cell was resized.

That's basically it.  I have posted the code for the test app on my Github repository here.

As always please feel free to leave me any questions or comments.

Monday, July 13, 2015

Swifty Ensembles (Part 2)



In my last post I laid out the infrastructure for how I setup Ensembles to work with a local CoreData store in a test app I had created.  What I left out (it was late and I ran out of time) was how it was consumed.  So in part two I'll walk through that.

Also recall, my test app had one view which was a UITable view that showed all the entries that had been stored.  See the previous post for a more detailed description.

The name of my view controller is MainVC:

 import UIKit  
 import CoreData 

 @objc class MainVC: UITableViewController, NSFetchedResultsControllerDelegate { 
   @IBOutlet weak var storeSyncStatus:UIBarButtonItem? 
   @IBOutlet weak var nextIDButton:UIBarButtonItem? 
   @IBOutlet weak var actionSheetButton:UIBarButtonItem? 
   private var _coreDataStack:CDEStack? 
   var fetchedResultsController:NSFetchedResultsController? 
 
Starting out, I declare the controller to be a NSFetchedResultsControllerDelegate.  I also declare references to some of the UI elements as defined here:
  • storeSyncStatus - This is a label to indicate whether syncing is on or off
  • nextIDButton - This will be used as an indicator of the name of the next Entity object that will be created.  I just give each successive Entity object a name that is one more than the last one.
  • actionSheetButton - The button to bring up the action list
Note also that I declare the class with "@objc" I'm not sure this is required but I think it may be as I remember running into weird errors with NSManagedObjects if I didn't do that.  If someone knows whether this is required or not please let me know.

Also note there is the "_coreDataStack" attribute which is a reference to the CDEStack that was stood up in the ApplicationDelegate class.  

I am using a pattern of injecting the CDEStack instance into this class because I read an article online about the pitfalls of using singletons and in it it recommended you should use injection instead.  I'm not sure I am convinced on the author's argument for this case, so I'll have to think about this some more.   Right now I tend to land in the singleton camp, but I could be swayed. Also note this is a private variable as I don't want it messed with by outsiders :-)

Next is the "fetchedResultsController" which is the instance of NSFetchedResultsController for the table view.

Also note I declared both "_coreDataStack" and "fetchedResultsController" as optionals as the AppDelegate will inject them after the class is instantiated. 

Next up is a very vanilla viewDidLoad method:

   override func viewDidLoad() {  
     super.viewDidLoad() 
     tableView.rowHeight = 44 // Fixes bug where we get a warning in the output 
     refreshUI() 
   } 

The setting for the tableView.rowHeight just fixes a warning I was seeing in the log at runtime.  I think I should be setting estimatedRowHeight here instead.  More investigation is needed on this issue. At the end of this method I call my "refreshUI" method which updates the UI.

Next is a simple implementation of "viewWillDisappear":

   override func viewWillDisappear(animated: Bool) {  
     super.viewWillDisappear(animated) 
     NSNotificationCenter.defaultCenter().removeObserver(self) 
   } 
 
This just removes the view controller from observing notifications from the NSNotificationCenter when it disappears.

Next is the public setter method for the CoreData stack (CDEStack):

   func setCoreDataStack(coreDataStack:CDEStack) {  
     _coreDataStack = coreDataStack  
     setupFetchedResultsController()  
     refreshUI()  
   }

Here I set the private variable, call a method to setup the NSFetchedResultsController for the tableView and call our now familiar "refreshUI" method.

Next is a life cycle method that gets called when the store is changed.  I just print out a message to the console so I can see what happened:

   func handleStoreChanged(notification:NSNotification) {  
     println("Handling Store changed: \(notification)")  
   }  

Next is the method to setup the NSFetchedResultsController, "setupFetchedResultsController":

   func setupFetchedResultsController() {  
     if (_coreDataStack == nil) {  
       return  
     }  
     let fetchRequest = NSFetchRequest(entityName: "Entity")  
     let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)  
     fetchRequest.sortDescriptors = [sortDescriptor]  
     fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: _coreDataStack!.context, sectionNameKeyPath: nil, cacheName: nil)  
     fetchedResultsController!.delegate = self  
   
     var error:NSError? = nil  
     let success = fetchedResultsController!.performFetch(&error)  
     if (!success) {  
       println("Error: \(error?.localizedDescription)")  
     }  
     else {  
       self.tableView.reloadData()  
     }  
   }  

This first checks to see if the _coreDataStack has been set and if not bails out.  If it is set, I create a simple NSFetchedResultsController that retrieves all the Entity's from the local store and sorts them by name.  I set the delegate of the fetched results controller to the MainVC UITableViewController. Finally I perform the fetch and if successful I reload the tableView.

Next up is the "refreshUI" method:

   func refreshUI() {  
     storeSyncStatus?.title = LocalStoreService.sharedInstance.syncToCloud ? "Cloud On" : "Cloud Off"  
     nextIDButton?.title = "Next ID: \(LocalStoreService.sharedInstance.lastCount!+1)"  
   
     var entitiesCount = 0  
     if let _frc = fetchedResultsController {  
       let sectionInfo = _frc.sections![0] as! NSFetchedResultsSectionInfo  
       entitiesCount = sectionInfo.numberOfObjects  
     }  
   
     title = "Entities (\(entitiesCount))"  
   }  
   

This method just updates the different ui components.  Note, I keep a count of the total number of entities stored.  The nextID is used as the name for the next Entity created. Yes, this is probably badly named.

When the user presses the "+" navigation bar button the "addEntity" method is called:

   @IBAction func addEntity() {  
     if let coreDataStack = _coreDataStack {  
       let entityEntity = NSEntityDescription.entityForName("Entity", inManagedObjectContext: coreDataStack.context)  
       let entity = Entity(entity:entityEntity!, insertIntoManagedObjectContext: coreDataStack.context)  
       let newCount = LocalStoreService.sharedInstance.lastCount!+1  
       entity.name = "\(newCount)"  
       let deviceName = UIDevice.currentDevice().name  
       entity.desctext = "\(deviceName): \(NSDate())"  
       var error: NSError? = nil  
       coreDataStack.save() {  
         LocalStoreService.sharedInstance.lastCount = newCount  
       }  
     }  
     else {  
       println("Unable to add entity, there is no coreDataStack")  
     }  
   }  
   

Essentially here I just create the Entity and update it's various fields.  Then the coreDataStack.save() method is called where all the work is done.  If the save is successful then we update the lastCount variable in the local store, so we'll know what to name the next Entity we create.

Next up is the showActionSheet method which shows (when the action button on the toolbar is pressed) the other actions that can be accomplished:

   @IBAction func showActionSheet(sender: AnyObject) {  
       
     let optionMenu = UIAlertController(title: nil, message: "Choose Option", preferredStyle: .ActionSheet)  
       
     let cloudActionTitle = LocalStoreService.sharedInstance.syncToCloud ? "Disable Cloud" : "Enable Cloud"  
     let cloudAction = UIAlertAction(title: cloudActionTitle, style: .Default, handler: {  
       (alert: UIAlertAction!) -> Void in  
       println("toggling cloud access")  
       
       if let coreDataStack = self._coreDataStack {  
         // Whatever the sync flag was before invert it so we call the right method.  
         let syncToCloud = !LocalStoreService.sharedInstance.syncToCloud  
         if (syncToCloud) {  
           println("enabling sync manager")  
           coreDataStack.enableSyncManager() {  
             self.tableView.reloadData()  
             self.refreshUI()  
           }  
         }  
         else {  
           println("disabling sync manager")  
           coreDataStack.disableSyncManager() {  
             self.tableView.reloadData()  
             self.refreshUI()  
           }  
         }  
       }  
     })  
       
     let clearAction = UIAlertAction(title: "Delete Local & Remote", style: .Default, handler: {  
       (alert: UIAlertAction!) -> Void in  
       self.deleteAllLocalEntities()  
     })  
       
     //  
     let resyncAction = UIAlertAction(title: "Delete Local & Resync", style: .Default, handler: {  
       (alert: UIAlertAction!) -> Void in  
       println("Resyncing entities")  
       if let coreDataStack = self._coreDataStack {  
         let syncToCloud = LocalStoreService.sharedInstance.syncToCloud  
         if (syncToCloud) {  
           coreDataStack.disableSyncManager() {  
             self.deleteAllLocalEntities()  
             coreDataStack.enableSyncManager() {  
               self.tableView.reloadData()  
               self.refreshUI()  
             }  
           }  
         }  
         else {  
           self.deleteAllLocalEntities()  
           coreDataStack.enableSyncManager() {  
             self.tableView.reloadData()  
             self.refreshUI()  
           }  
         }  
       }  
     })  
       
     let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: {  
       (alert: UIAlertAction!) -> Void in  
       println("Cancelled")  
     })  
       
       
     // 4  
     optionMenu.addAction(cloudAction)  
     optionMenu.addAction(clearAction)  
     optionMenu.addAction(resyncAction)  
     optionMenu.addAction(cancelAction)  
       
     // Required for iPad presentation  
     optionMenu.popoverPresentationController?.barButtonItem = actionSheetButton  
       
     // 5  
     self.presentViewController(optionMenu, animated: true, completion: nil)  
   }  
   

The actions are as follows:
  • cloudAction - Turns on or off cloud access, depending on the state of the "syncToCloud" flag in the NSUserDefaults store.
  • clearAction - Deletes all the entries in the local store.
  • resyncAction - Deletes all the entries stored in the local store and re-syncs the data from the Cloud
  • cancelAction - Cancels the action sheet menu

Next up is a method to delete all the local entities (this is used in several places by the "showActionSheet" method's actions shown above):

   private func deleteAllLocalEntities() {  
     println("Deleting all local entities")  
     // TODO: Loop through all entities and delete them all  
     if let coreDataStack = self._coreDataStack {  
       let fetchRequest = NSFetchRequest(entityName: "Entity")  
       var error: NSError?  
       let entities = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as! [Entity]?  
       if let entities = entities {  
         for (idx, entity) in enumerate(entities) {  
           coreDataStack.context.deleteObject(entity)  
         }  
         coreDataStack.save() {  
           self.tableView.reloadData()  
           self.refreshUI()  
         }  
       }  
     }  
   }  
   

This method loads the entities from the local store then walks through them, deleting each one in the context.  I recognize it isn't correct to do this on the main thread and loading all the entities into memory is also not a good practice, but this was a test app.  There are better, more safe ways, of dealing with memory and threading issues that should be used in a production quality app.

Once the context is saved the UI is then refreshed.

This next method is a method to return a "generic-ized" device name.  I was testing with two devices so to use this you would want to replace the two String checks with your own device names.

   func genericDeviceName() -> String {  
     let deviceName = UIDevice.currentDevice().name  
       
     if (deviceName == "{test device one name here}") {  
       return "Device 3"  
     }  
     else if (deviceName == "{test device two name here}") {  
       return "Device 2"  
     }  
     return deviceName  
   }  
   

Next up we have table view source and delegate methods

   // MARK: - Table view data source  
   override func numberOfSectionsInTableView(tableView: UITableView) -> Int {  
     if let _frc = fetchedResultsController {  
       return _frc.sections!.count  
     }  
     return 1;  
   }  
   

   override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {  
     if let _frc = fetchedResultsController {  
       let sectionInfo = _frc.sections![section] as! NSFetchedResultsSectionInfo  
       return sectionInfo.numberOfObjects  
     }  
     return 0  
   }  
   

In both of these methods we just lean on the fetchedResultsController to answer the "count" questions.

   override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {  
     let cell = tableView.dequeueReusableCellWithIdentifier("MainTVCell", forIndexPath: indexPath) as! MainTVCell  
     if let _frc = fetchedResultsController {  
       let entity = _frc.objectAtIndexPath(indexPath) as! Entity  
       cell.configureCell(entity)  
     }  
     return cell  
   }  
   

For the "cellForRowAtIndexPath" method I have been following a pattern of adding a "configureCell" method on my subclass of UITableViewCell.  This method isolates the "setting" code on the cell's UI attributes to the cell subclass itself.  This is a carry-over of a pattern I used with Objective-C but with Swift I think I will change this pattern to use the "didSet" method pattern instead so as to hide even more internals of the cell subclass.

   // Override to support editing the table view.  
   override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {  
     if (editingStyle == .Delete) {  
       if let frc = fetchedResultsController, coreDataStack = _coreDataStack {  
         let entity = frc.objectAtIndexPath(indexPath) as! Entity  
         coreDataStack.context.deleteObject(entity)  
   
         coreDataStack.save(nil)  
       }  
     }  
   }  
   

The "commitEditingStyle" implementation adds "swipe to delete" capability to the tableView.

   override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {  
     if let frc = fetchedResultsController, coreDataStack = _coreDataStack {  
       let entity = frc.objectAtIndexPath(indexPath) as! Entity  
       // TODO: Here is where we do a model migration  
       // entity.dateUpdated = NSDate()  
       entity.desctext = "\(genericDeviceName()): \(NSDate())"  
       coreDataStack.save(nil)  
     }  
   }  
   

The "didSelectRowAtIndexPath" implementation just updates the date in the selected Entity's desctext attribute.  This was intended as a "poor-man's" edit function to prove that editing existing records worked as well.

   // MARK: NSFetchedResultsControllerDelegate methods  
   func controllerDidChangeContent(controller: NSFetchedResultsController) {  
     tableView.reloadData()  
     refreshUI()  
   }  
   
 }  

Finally anytime the NSFetchedResultsController fires a "didChangeContent" event the controller catches this, reloads the table and refreshes the UI.

That's it.  So in summary, what I did was create a simple app that uses CoreData as it's local store and then leans on the excellent Ensembles framework to sync the objects into the cloud and thus across multiple devices.

What I didn't cover was various scenarios dealing with network connectivity or errors nor multi-threading.  I think it wouldn't be too hard to add these capabilities as you stand up a full production ready app.

My final conclusion is this feels like a workable solution to the CoreData-Cloud sync problem.  For my current development work, I intend to still continue exploring CloudKit.  However, after writing this post I'm wondering if I should rethink that position.

I am just learning Ensembles, so if you see areas where I have totally blown it, or things I could have done better, please don't hesitate to let me know.

Finally, as a side note, I want to give a shout out to a free video series I have been watching recently.

Even though I have been a professional programmer for over 15 years, using various languages and IDEs, I have struggled at times learning best practices for XCode.  I come from an Eclipse/Java background and let's just say XCode and I ,haven't always got along very well.  But recently I found this Stanford online course CS193P - developing iOS 8 Apps with Swift, via iTunesU.

Having worked with iOS app development for two and half years now, I already knew most of the material and concepts (even if they are in Swift).  But what I have found very useful was the demos in the videos.  The professor (Paul Hegarty) does a great job of explaining what he is doing and giving great little tidbits of information on how he uses Xcode.

I particularly found his workflow for AutoLayout to be very useful.  I think this course would be a great start for a new iOS developer and for us "seasoned" programmers I have found it quiet useful.  Thanks Stanford and professor Hegarty for producing this and making it available to all.

Tuesday, July 7, 2015

Swifty Ensembles (Part 1)

I had originally thought I was going to write about my new design with my CloudKit support I'm using in my new app, but I was reminded that I had not written about integrating with Ensembles as I had promised a while back. So I'll fulfill that promise with this post.


Before I get started, I need to give the standard “legal” disclaimer.  This is what worked for me, it may not work for others but hopefully this will be of some help. Second, I am by no means a Swift expert so if the code seems weird, I’m still learning.  Finally, this was a test app that I put together to “play” around with Ensembles to see what it could do and make sure it would work for my needs.  So it may not be the best design. Buyer be ware!

Ok, so what is Ensembles.  I will refer you to the website for most of the information which you can find here: http://www.ensembles.io/.  Basically it allows you to sync your CoreData database from your app to the iCloud container (in a sane way).  


I’ve written about my experiences with using the vanilla support for CoreData syncing provided by Apple.  For whatever reason I continue to run into problems. WHY DOESN"T IT JUST WORK!!! I won’t rehash those issues.


So a while back I tried out using Ensembles to do the syncing part of CoreData and I found it to work pretty well.  As the site explains there are two versions, a free version and a paid version.  The cool thing is the paid version is a drop-in replacement for the free version. Up till now, I’ve only setup the free version.


At one point I was considering buying the paid version, but I got caught up in learning about CloudKit and right now my thinking is my next app will use that instead (at least for cloud storage).  But for my existing CoreData app, Ensembles is a strong contender I'm hoping to add in when I get back to working on it.

It’s a good idea to have read through the Ensembles website to understand the technology it is using as I won’t be going into that either.


So let’s get started. 

The test app I built is very simple, it consists of a single view controller which is a UITableViewController called MainVC (wrapped in a UINavigationController).  It has an “add” button in the navigation bar to create a new Entity. I didn't create a UI to edit Entity objects so clicking "add" just adds a new one to the local database. 

There is a toolbar at the bottom of the view which has two items in it. A label that indicates whether syncing is enabled or not and an action button which opens up a UIActionSheet to do the following actions: 
  • Enable/Disable syncing to the cloud store
  • Delete both the local and remote stores
  • Delete the local store and resync with the remote store
I figured these actions were enough to simulate the different sync scenarios I wanted to try out.

Architecturally I abstracted all the CoreData and Ensembles “glue” code in a class named CDEStack. so as to keep the rest of the code clean (as much as possible) of all the boilerplate stuff.

To set all this up I used Cocoapods to pull in the Ensembles dependency.  My podfile looks like this:

platform :ios, '8.0'  
pod "Ensembles", "~> 1.0"  

Since Ensembles is written in Objective-C you need to have a bridging header.  Here are the only two entries in mine that pull in the Ensembles headers:


#import <Ensembles/Ensembles.h>  
#import <Ensembles/CDEDefines.h>  

The CoreData model is very simple. It consists of one object which I called “Entity” and it has four fields:


creationDate: NSDate
descText: String
name: String
uniqueIdentifier: String  

The database name is CDSyncENS (for CoreData Sync with ENSembles, get it?).  

I don’t remember if I had to do this or if I got this for free, but when creating the Entity object in the CoreData model editor I had to make sure it’s class was properly namespaced.  So in the “Data Model Inspector” for the class attribute it has "CDSyncENS.Entity" instead of, what I think it did in Objective-C, of just being "Entity".


Here is the code for the Entity class:


 import Foundation  
 import CoreData  
   
 class Entity: NSManagedObject {  
   
   @NSManaged var name: String  
   @NSManaged var desctext: String  
   @NSManaged var uniqueIdentifier: String?  
   @NSManaged var creationDate: NSDate?  
   
   override func awakeFromInsert() {  
     super.awakeFromInsert()  
     if let uniqueIdentifier = uniqueIdentifier {  
         
     }  
     else {  
       self.uniqueIdentifier = NSProcessInfo.processInfo().globallyUniqueString  
       self.creationDate = NSDate()  
     }  
   }  
 }  
   
The key thing to note here is when the object is first inserted into CoreData it gets a uniqueIdentifier and creationDate added to it.  The uniqueIdentifier will be used to help Ensembles not create duplicate objects when syncing.  The creationDate, seemed like a good thing to do.


Next up is the AppDelegate:


 class AppDelegate: UIResponder, UIApplicationDelegate {  
   
   var window: UIWindow?  
   var coreDataStack: CDEStack?  
   
   func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {  
     // Override point for customization after application launch.  
     coreDataStack = CDEStack(dbName: "CDSyncENS", syncToCloud: true, completion: nil)  
     let navigationController = self.window!.rootViewController as! UINavigationController  
     let mainVC = navigationController.topViewController as! MainVC  
     mainVC.setCoreDataStack(self.coreDataStack!)  
              
     return true  
   }  
   
   func applicationWillResignActive(application: UIApplication) {  
     if let coreDataStack = coreDataStack {  
       coreDataStack.save(nil)  
     }  
   }  
   
   func applicationDidEnterBackground(application: UIApplication) {  
     if let coreDataStack = coreDataStack {  
       coreDataStack.save(nil)  
     }  
   }  
   
   func applicationWillTerminate(application: UIApplication) {  
     if let coreDataStack = coreDataStack {  
       coreDataStack.save(nil)  
     }  
   }  
 }  
   

This is a pretty vanilla app delegate class.  In the "didFinishLaunchingWithOptions" method the CDEStack object is created, assigned to the local variable "coreDataStack" and passed into the main view controller (which is the UITableViewController I mentioned earlier).  


The rest of the methods "applicationWillResignActive", "applicationDidEnterBackground", and "applicationWillTerminate" ensure that a save operation is called on the stack should one of those events occur.


So, the obvious next question is, what does CDEStack look like.  I’m glad you asked.  This one I’ll take a little slower in the walk through.


 import CoreData  
 import UIKit  
   
 let SYNC_ACTIVITY_DID_BEGIN = "SyncActivityDidBegin"  
 let SYNC_ACTIVITY_DID_END = "SyncActivityDidEnd"  
First we setup some names for some notifications that will be fired by the lifecycle methods. 

Next is the class declaration. I don't remember why I annotated it with "@objc" but I think it is required to make it work better with CoreData and Ensembles since they are both Objective-C based frameworks.


 @objc class CDEStack : NSObject, CDEPersistentStoreEnsembleDelegate {  
   private var mainQueueMOC:NSManagedObjectContext?  
   private var psc:NSPersistentStoreCoordinator?  
   private var model:NSManagedObjectModel?  
   private var modelURL:NSURL?  
   private let modelName:String  
   
   private var store:NSPersistentStore?  
   private var ensemble:CDEPersistentStoreEnsemble?  
     
   private var activeMergeCount:Int = 0 
This is a list of some private fields that the stack will hold.  In here we have the main NSManagedObjectContext (mainQueueMOC), the NSPersistentStoreCoordinator (psc), the NSManagedObjectModel (model), the url for the model (modelURL), the name of the model (modelName), the NSPersistentStore (store), the CDEPersistentStoreEnsemble (ensemble) and a counter for the number of active merges (activeMergeCount). I found this last one in some Ensembles documentation I was reading and it is there to determine when the network activity indicator should be shown or not. These should mostly be familiar with anyone who has done any CoreData work.


Next are some computed properties:


 var context:NSManagedObjectContext {  
     get {  
       return mainQueueMOC!  
     }  
   }  
   
This first one exposes the main context.  I was trying to limit access to this by the using components (MainVC in this case) so that they could only read the context but not replace it (which would obviously be a very bad thing).


   private var storeOptions : [NSObject: AnyObject] {  
     return [  
       NSMigratePersistentStoresAutomaticallyOption: true,  
       NSInferMappingModelAutomaticallyOption: true  
     ]  
     // TODO: May need to add NSPersistentStoreRemoveUbiquitousMetadataOption  
   }  
   


This is just an accessor to put creation of the store options array in one place so I could find it later on should I need to change it.  Note the TODO item.

   private var storeDirectoryURL : NSURL {  
     let fileManager = NSFileManager.defaultManager()  
     let directoryURL = fileManager.URLForDirectory(.ApplicationSupportDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true, error: nil)  
     return directoryURL!  
   }  
   


This one I remember being awkward to setup.  I think I got this from the Ensembles documentation.  Basically it abstracts out the creation of the store directory when setting up the store. I remember bouncing about between several different examples but this one seemed to do the trick.


   private var storeURL : NSURL {  
     return storeDirectoryURL.URLByAppendingPathComponent("\(modelName).sqlite")  
   }  
   
   private var ubiquityContainerIdentifier: String {  
     let fileManager = NSFileManager.defaultManager()  
     let teamId = "iCloud"  
     let bundleID = NSBundle.mainBundle().bundleIdentifier  
     let cloudRoot = "\(teamId).\(bundleID!)"  
     return cloudRoot  
   }  
   
Again here we just pull out some of the boiler plate code to make the code cleaner. These just create the store URL and the ubiquity container identifier.


Now onto the init method:

   init(dbName:String, syncToCloud:Bool, completion: ((Void) -> Void)? ) {  
     modelName = dbName  
     super.init()  
     CDESetCurrentLoggingLevel(CDELoggingLevel.Verbose.rawValue)  
   
     // Setup Core Data Stack  
     setupCoreData() {  
       self.setupEnsemble(completion)  
     }  
   }  
   
First we capture the database name (which is also the name of the model).  We then set the logging level on Ensembles to be verbose. I found this very useful to see what was going on in the console.


Finally we call the setupCoreData() method to setup the CoreData stack and we also pass a completion method to setup Ensembles after CoreData is successfully stood up.


The setupCoreData() method is very standard, in fact I think I just copied most of it from the boiler plate code you get when you choose to build a CoreData based app in the Project wizard.


private func setupCoreData(completion: ((Void) -> Void)?) {  
   
     println("Setting up CD Stack")  
     NSFileManager.defaultManager().createDirectoryAtURL(storeDirectoryURL, withIntermediateDirectories:true, attributes:nil, error:nil)  
       
     // Create the model  
     modelURL = NSBundle.mainBundle().URLForResource(modelName, withExtension: "momd")!  
     let _model = NSManagedObjectModel(contentsOfURL: modelURL!)  
     assert(_model != nil, "Could not retrieve model at URL \(modelURL!)")  
     model = _model  
   
     // Build the persistent store coordinator  
     psc = NSPersistentStoreCoordinator(managedObjectModel: model!)  
     var storeURL = self.storeURL  
     var options = self.storeOptions  
     var error: NSError? = nil  
     self.store = self.psc!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error: &error)  
     assert(self.store != nil, "Could not add store URL \(storeURL)")  
       
     // Set up the main MOC  
     mainQueueMOC = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)  
     mainQueueMOC?.persistentStoreCoordinator = psc  
     mainQueueMOC?.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy  
     println("Finished setting up CD Stack")  
     if let completion = completion {  
       completion()  
     }  
   }  
   
I’m not going to go into much detail here.  Essentially we create the store directory, then we create the model, then we create the NSPersistentStoreCoordinator, then we create the NSPersistentStore, and finally we create the NSManagedObjectContext.  Note: I did not create a private/public MOC relationship as is becoming a defacto best practice.  I’m sure you could, I just didn’t want to complicate my test app.  I was really focussing on getting Ensembles integrated.  


Once all this is done the completion handler (which sets up Ensembles) is ran.


Here is the "setupEnsemble" method:


   func setupEnsemble(completion:((Void) -> Void)?) {  
     println("Setting up sync stack")  
     if (!canSynchronize()) {  
       println("Cannot set up sync stack, disabled")  
       return  
     }  
   
     let cloudFileSystem = CDEICloudFileSystem(ubiquityContainerIdentifier: nil)  
     assert(cloudFileSystem != nil, "Cloud file system could not be created")  
   
     ensemble = CDEPersistentStoreEnsemble(ensembleIdentifier: modelName, persistentStoreURL: storeURL, managedObjectModelURL: modelURL, cloudFileSystem: cloudFileSystem)  
     ensemble!.delegate = self  
       
     // Listen for local saves, and trigger merges  
     NSNotificationCenter.defaultCenter().addObserver(self, selector: "localSaveOccurred:", name: CDEMonitoredManagedObjectContextDidSaveNotification, object: nil)  
     NSNotificationCenter.defaultCenter().addObserver(self, selector: "cloudDataDidDownload:", name: CDEICloudFileSystemDidDownloadFilesNotification, object: nil)  
     println("Finished setting up sync stack")  
     synchronizeWithCompletion(completion)  
   }  
   
We first check to see if we can sync and bail out if we can’t.  More on this later. Next the cloud file system is setup, followed by setting up the Ensemble. We then attach the CDEStack instance as a delegate to the ensemble and start listening for local saves, and downloads from the cloud store.


Finally we call the "synchronizeWithCompletion" method with any completion handler that might have been passed in.  In this case there is none.


Here is the "synchronizeWithCompletion" method. 


 private func synchronizeWithCompletion(completion:((Void) -> Void)?) {  
     if (!canSynchronize()) {  
       return  
     }  
       
     incrementMergeCount()  
     if let ensemble = ensemble {  
       if (!ensemble.leeched) {  
         println("Leeching the ensemble")  
         ensemble.leechPersistentStoreWithCompletion(){  
           (error:NSError?) in  
             println("Leeching complete")  
             self.decrementMergeCount()  
             if (error != nil) {  
               println("Could not leech to ensemble: \(error)")  
               if (!ensemble.leeched) {  
                 self.disconnectFromSyncServiceWithCompletion(completion)  
                 return  
               }  
             }  
             println("Leeching successful")  
             if let completion = completion {  
               completion()  
             }  
           }  
       }  
       else {  
         println("Merging with the ensemble")  
         // Initiate sync operations  
         ensemble.mergeWithCompletion() {  
           error in  
           println("Merging complete")  
           self.decrementMergeCount()  
   
           if (error != nil) {  
             println("Could not merge ensemble: \(error)")  
           }  
           println("Merging successful")  
           if let completion = completion {  
             completion()  
           }  
         }  
       }  
         
     }  
   }  
   

I won’t go into this much as I believe I copied it from the Ensembles sample code.  Basically, it ensures that “leeching” has occurred and if the ensemble is “leeched” it accomplishes the merge operation.


Next we implement some of the optional Ensemble delegate methods:


  // MARK: Ensemble Delegate Methods  
   func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, didSaveMergeChangesWithNotification notification: NSNotification!) {  
     if let mainQueueMOC = mainQueueMOC {  
       // merge the changes in the notification into the main MOC  
       mainQueueMOC.performBlockAndWait() {  
         mainQueueMOC.mergeChangesFromContextDidSaveNotification(notification)  
       }  
     }  
   }  
   
   func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [AnyObject]!) -> [AnyObject]! {  
     var returnArray = NSMutableArray()  
     for (idx, object) in enumerate(objects) {  
       var value: AnyObject? = object.valueForKeyPath("uniqueIdentifier")  
       returnArray.addObject(value!)  
     }  
     return returnArray as [AnyObject]  
   }  
   
It's best to look at the Ensemble documentation (in the code) as it is pretty self-explanitory. Suffice it to say the first one ("didSaveMergeChangesWithNotication") gets called when the ensemble saves changes into the local persistent store. Here we just merge those changes into the main NSManagedObjectContext.

The second method, "globalIdentifiersForManagedObjects", hung me up for a while. By implementing this method we leverage our locally generated unique identifiers we calculated when an Entity was created so that Ensembles can be smart enough to not insert duplicate objects. Again see the documentation in the delegate declaration for this method.

Next we implement the two increment and decrement methods so we can keep track of when to start and stop the network activity indicator in the UI. To be honest, I don't think this is really needed, but it is a nice touch to give the user subtle feedback.


  private func decrementMergeCount() {  
     activeMergeCount--  
     if (activeMergeCount == 0) {  
       NSNotificationCenter.defaultCenter().postNotificationName(SYNC_ACTIVITY_DID_END, object: nil)  
       UIApplication.sharedApplication().networkActivityIndicatorVisible = false  
     }  
   }  
     
   private func incrementMergeCount() {  
     activeMergeCount++;  
     if (activeMergeCount == 1) {  
       NSNotificationCenter.defaultCenter().postNotificationName(SYNC_ACTIVITY_DID_BEGIN, object: nil)  
       UIApplication.sharedApplication().networkActivityIndicatorVisible = true  
     }  
   }  
   
Next we setup methods to be called to disable syncing and to clean up when the instance is removed from memory.


   func disconnectFromSyncServiceWithCompletion(completion:((Void) -> Void)?) {  
     if let ensemble = ensemble {  
       ensemble.deleechPersistentStoreWithCompletion() {  
         error in  
         if (error != nil) {  
           NSLog("Could not disconnect from sync service: \(error)")  
         }  
         else {  
           self.reset()  
           if let completion = completion {  
             completion()  
           }  
         }  
       }  
     }  
   }  
     
   func reset() {  
     if (ensemble != nil) {  
       ensemble!.delegate = nil  
       ensemble = nil  
     }  
   }  
     
   deinit {  
     println("Deallocating CoreDataStack")  
     NSNotificationCenter.defaultCenter().removeObserver(self)  
   }  
   

The "disconnectFromSyncServiceWithCompletion" method is used in the app to disable syncing (one of the requirements of the app).

Next is the implementation of the selector methods that get called when the NSNotifications that were setup in "setupEnsembles" method are fired. These both call into the "synchronizeWithCompletion" method. If you relook at that you can see that (since the ensemble has already been leeched) these both cause the ensemble to merge the changes.


   func localSaveOccurred(notification:NSNotification) {  
     synchronizeWithCompletion(nil)  
   }  
   
   func cloudDataDidDownload(notification:NSNotification) {  
     synchronizeWithCompletion(nil)  
   }  
   

What's left in CDEStack are methods to service the UI actions. Hopefully these will be self explanatory:

 func fetchEntities() -> [Entity]?{  
     let fetchRequest = NSFetchRequest(entityName: "Entity")  
     var error: NSError?  
     let result = mainQueueMOC?.executeFetchRequest(fetchRequest, error: &error) as! [Entity]?  
     return result  
   }  
          
   func enableSyncManager(completion:((Void) -> Void)?) {  
     LocalStoreService.sharedInstance.syncToCloud = true  
     setupEnsemble() {  
       if let completion = completion {  
         completion()  
       }  
     }  
   }  
     
   func disableSyncManager(completion:((Void) -> Void)?) {  
     disconnectFromSyncServiceWithCompletion() {  
       LocalStoreService.sharedInstance.syncToCloud = false  
       if let completion = completion {  
         completion()  
       }  
     }  
   }  
     
   private func canSynchronize() -> Bool {  
     return LocalStoreService.sharedInstance.syncToCloud  
   }  
     
Here's the breakdown of the methods:
  • fetchEntities - Retrieves all the entities from the database
  • enableSyncManager - Turns on syncing
  • disableSyncManager - Turns off syncing
  • canSynchronize - Returns whether synchronizing is turned on or off
One thing to note about these methods is they use a singleton class called LocalStoreService which is just a facade I created over NSUserDefaults. Again I am a big fan of abstracting out boilerplate code, so you are seeing this pattern in play a again.

Finally we have the save method, which I pulled out separately here. I'm not going to walk through this one as it should be self explanatory.


   func save(completion:((Void) -> Void)?) {  
     if let mainMOC = self.mainQueueMOC {  
       // If we have nothing to save just run the completion listener  
       if (!mainMOC.hasChanges) {  
         println("No changes to be saved")  
         if let completion = completion {  
           completion()  
         }  
       }  
       else {  
         mainMOC.performBlockAndWait() {  
           
           // Save main MOC on the main queue  
           var error: NSError? = nil  
           println("Saving mainQueueMOC")  
           if !mainMOC.save(&error) {  
             println("Error saving main MOC: \(error?.localizedDescription), \(error?.userInfo)")  
           }  
           if let completion = completion {  
             println("Running completion handler")  
             completion()  
             println("Finished completion handler")  
           }  
         }  
         
       }  
     }  
   }  
   

As you can probably tell I wasn't too concerned about threading. Again this is where the public/private NSManagedObject design pattern would come into play.

The only thing left to go over is the design of MainVC. Since this post has gotten EXTREMELY LONG I'll stop here. In my next post I'll go over MainVC, give my final impressions, and I think I'll try to include a screen shot or two as well.