Scorebook Sync Log 03 - Schema Design

Building a schema on CloudKit is pretty straightforward. You get a CKRecord object and treat it like a dictionary. There are a few data types they can take (NSString, NSNumber, NSData to name a few) and it’s a key/value pair to get the data on the record. Once you have a record created, you save it to the database and your RecordType (i.e. a database table) is created. Simple. But there is a trick associated with related data.

One of the data types you can attach is a CKReference. This is how you glue 2 records together. CloudKit supports 1:Many relationships (not many:many though). The way you do the relating is through the child record, not the parent. This tripped me up a little bit because with my background in FileMaker I would typically go parent to child (like with a portal object on a layout).

Let me back up for a moment. I made a design decision early on to isolate my networking files as much as possible. It could be a hedge or just wanting to have all the syncing code in one place, it jut felt right. I put a new folder in my Model folder called Sync and in it I have categories on all my model objects called <objectName>+CloudKit.h/m. I want each entity to know how to turn itself into a CKRecord and how to turn a CKRecord into itself. That feels like the right way to go.

Inside each category I have a method called -cloudKitRecord that returns a CKRecord. Here’s a basic implementation:

- (CKRecord *)cloudKitRecord
{
    CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:self.ckRecordId];
    CKRecord *personRecord = [[CKRecord alloc] initWithRecordType:[SBPerson entityName]
                                                         recordID:recordID];

    personRecord[SBPersonFirstNameKEY] = self.firstName;
    personRecord[SBPersonLastNameKEY] = self.lastName;
    personRecord[SBPersonEmailAddressKEY] = self.emailAddress;

    if (self.imageURL) {
        CKAsset *imageAsset = [[CKAsset alloc] initWithFileURL:[self urlForImage]];
        personRecord[SBPersonAvatarKEY] = imageAsset;
    }

    return personRecord;
}

A few things to note here:

  • I’m overriding -awakeFromInsert on all of my managed objects to create a -ckRecordId string property (Just a random UUID) that I can use as my CloudKit record ID. This is the unique identifier across the system. I started out with something much more complicated but landed here for now.
  • I have used Accessorizer (MAS link) to create string constants for all of my managed object properties. See this talk from Paul Gorake for more Core Data amazingness.
  • You can see my new CKAsset code at work here. I’m creating a new image asset with the URL to the image on disk, then you just use the asset as a property on the CKRecord. CloudKit handles the rest, which is really nice.

On the flipside, I have methods in the category that will take a CKRecord and create the Core Data managed object out of it. I haven’t gotten to the downstream sync just yet (I just ran into the need for CKRecordZones that I’ll probably write about next). Once I start the downstream I’ll put up a post about it too.

I’m going to come back to the CKReference stuff in my next post, since this one got a bit too long. Stay tuned.

Scorebook Sync Log 02: CKAsset Resolution

After thinking through my options last time I landed on option 2. Instead of storing the binary data within Core Data, I'm putting it in the file system instead. Here's how it's working:

  • The user picks an image that they want to save
  • The object associating with the image determines its filename and is responsible for all saving and retrieving of file data

I put the saving methods in a category on the 3 entities that save images. I couldn't stomach the idea of importing UIKit into actual model classes. This is more of a style thing than not, so it could have gone either way.

When I was originally putting this together I stored the absolute filepath to each file. This would work until I relaunched and then it broke. Turns out the simulator's documents path isn't always guaranteed to be the same file path every time. The workaround is to store the file's path relative to my documents directory. When I need to retrieve the image, the object responsible grabs the current document directory and smashes it up with the relative file path.

I'm not super happy that this is something I had to do in the first place, but I am satisfied with how it turned out. Now I get to move on to the grittier details of the syncing process.

Scorebook Sync Log Episode 01: Binary data files & Core Data

I'm working on the CloudKit schema for Scorebook and am coming upon a question right away. Scorebook allows users to add pictures as avatars, or game thumbnails, and to attach to game plays. I'm storing these in Core Data as binary data. All cool on the device. I'm also using the option to allow external storage in the managed object model.

CloudKit offers to store these files as CKAssets attached to my CKRecord. The problem is the init method for a CKAsset is initWithFileURL:, and initWithData: is not an option. So, how do I get to the files in the database?

My first inclination is to export the NSData as a file and return its resulting file:/// URL. I don't like this because of the additional storage that is necessary. I also don't like this because of possible overhead in processing. This method could drain users' batteries much faster. I'll also have to account for cleanup of the files once the sync is complete.

The other option, that could be something of a rabbit hole, is to rewrite all of those binary data fields to point to a location on disk for the files. This is starting to sound like it might be the winner. The tricky part I think will be migrating all the current image assets out of the database and to their own files on disk, then initiating the first sync.

Which sounds better to you? Is there something I'm missing?

Update I just filed rdar://19915193 asking for an initializer to work with NSData objects for CKAssets.

Scorebook Sync Log – Episode 00

Before I start writing sync code for Scorebook, I figured I should learn about how CloudKit works. Here are some resources that I'm using.

Required Viewing:

  • WWDC 208 – Introducing CloudKit
  • WWDC 231 – Advanced CloudKit

And also for sync systems, I'm thinking through the best way to handle keys and trigger events within the app. Brent Simmons wrote an (in)essential diary as he was adding sync to Vesper.

I'm going to look for some more resources as I go along, and anything that is of use I'll be sure to link to. I've been going through the process of how it should work over and over in my head. I'm looking forward to putting it out and seeing if my thinking is correct. Hopefully the result of this all will be the best possible product I can ship.

Scorebook – A look back and forward

I’ve been quite slow in developing Scorebook for the past month. Mostly due to personal reasons, but I’ve also been trying to think of what to do next on it. I’ve gotten some good feedback from users and now have a list of feature enhancements that should make Scorebook more interesting to more people.

Before I talk about some future plans, let’s look back a bit.

While Scorebook is my first shipping app that I’ve brought to market, it was long in development. I got the hankering to make it almost 3 years ago – when I didn’t know how to write really any code. Looking back at my Day One journal I got the idea back in early March, 2012. At that point I had never seen a line of code in an iOS app. Fast forward to June 2014. I just completed my certificate at UW and set out to build Scorebook. The project file was created on June 26, 2014.

I never spent more than a few hours at a time writing any code, and mostly the time was chunked up into 30 minutes here, or an hour there. I regularly attend NSCoders and got some good work done in the few hours at the coffee house and talking with the other folks who attend there. Side note: if you’re a developer and not in a community with other folks who work on similar things, you’re doing it wrong. Don’t be an island.

6208 lines of code later and Scorebook 1.0 was ready for the App Store. I had a few beta testers who provided useful feedback and found some crashing bugs that I was able to squash, and I’m so happy to report that there have been 0 crashes on the shipping versions, 1.0 and 1.0.1. I could not be more pleased with this. There are 2 things that I dread: losing someone’s data, and crashing. Neither have happened. Praise the Lord.

On to the future!

I think I’ve figured out the next course to take. I’ve begun working on Scorebook 1.1 and that will have 2 main new features:

  • It will be universal, to work with iPads natively
  • It will sync

Both of these are massive things to undertake, with the iPad version made a little bit easier due to the new adaptive layout tools Apple has provided with iOS 8. The main thing that I will need to do is move a couple of layouts from table views (a vertically scrolling list of things to collection views (where you can do arbitrary layouts). The main reason is that just blowing it up to fit a screen isn’t good for anyone and if I’m going to utilize all that space I should do it right.

Data sync will be more intense. Like I said before, losing someone’s data is a prospect that keeps me up at night; I’m not going to ship anything that will jeopardize user data. My plan is to use CloudKit and users’ iCloud accounts for syncing their data. I’ve heard about enough successes with CloudKit that I’m satisfied it will work well. I’ve switched myself over to iCloud sync on 1Password and it’s been great so far.

So that is what to be expecting out of Scorebook for the next big update. I don’t have any prediction on how long it will take, but I do plan on writing about progress, what I’m learning and a little bit of how things work behind the scenes. I’m starting with sync, so look for some progress updates in the coming days/weeks/months.

Goodbye Twins

Our sweet babies

I've been writing this post in my head for the last few days. It's the hardest, saddest thing I've ever done. I'm putting this out there because telling everyone individually is going to be too hard. Too devastating.

These last couple of weeks have been excruciating. Just after we found out we were having twins, Emily's and my thoughts went from fear of wondering how we are going to do this, to absolute excitement at the thought of 2 babies at once and all the planning that goes into becoming a family of 5, to the absolute horror and crushing reality that we're not going to meet our babies the way we should have.

On Sunday night Emily started bleeding more heavily than she had (it had started a couple weeks earlier, and we were told that it was normal) and we went to the ER. An ultrasound revealed that her uterus had openend up and Baby A's sac had ruptured, emptying out the amniotic fluid.

The doctors wanted to induce labor and then do a D&C, but both babies had heartbeats and while there was no chance that he or she (I can't stomach calling my baby "it") would survive, if she delivered the baby and placenta there could be a miniscule chance for B to make it. We prayed so hard for that miracle and gave B every shot to make it.

Tragically that didn't happen, and B was delivered yesterday morning.

We have had so much love and support from friends, family, and my coworkers through this. There have been hundreds of prayers prayed. We don't get it. I wish we could get it. I wish this didn't happen.

Tuesday night we were home and reading the last story from the Jesus Storybook Bible to Atticus, Revelation 21:4 was a big theme:

"He will wipe away every tear from their eyes, and death shall be no more. Neither shall there be mourning, nor crying, nor pain anymore, for the former things have passed away."

I've never wanted anything to be more true, and to come more urgently than that verse. Our faith isn't in our kids. Our faith isn't in things of this world. This world is infected with sin. This world is dying. This world will be replaced with one that will never die.

Come Lord Jesus. Come.

Storyboards and UIViewController Containment

When I built Scorebook I used Storyboards for all the UI. I'm a big fan of them overall, especially for navigation. One thing that I'd used a little but not much are the container views, and embedding another view controller into my view via that container.

Before I continue, I need to give huge props to Tim Ekl for helping me get this figured out. He did a talk on UIViewController containment for Seattle Xcoders that goes over this in code rather than Storyboards. Thanks Tim!

One more thing before getting started. You can find the sample project here on Github.

The reason why you would want to use view controller containment really boils down to keeping components separated and reusable. A side benefit is you can avoid the Massive View Controller problem that comes about easily when building an iOS app. It’s also quite straightforward once you figure out some of the quirks.

Firs thing you’ll do in your storyboard is to drag a Container View our of the Xcode Object Library and onto your view controller. With the container will come a view controller connected by an Embed segue. You can delete this if you want to and attach whatever other view controller you like. I’ve used this with regular view controllers as well as UITableViewControllers and UICollectionViewControllers.

Pay attention to the segue. You’ll want to give it an identifier because it will be important in your code. Once you have your storyboard setup it should look something like this:

For my demo I embedded a UITableViewController, and for kicks I put my own custom header view at the top of my parent view controller, overlaying the container view. This will give us a nice effect of the table view scrolling behind the header view.

Make sure that your constraints are configured on the layout as well. I pinned the container to the parent’s topLayoutGuide, the bottom, and both sides.

Let’s flip over to code.

In your parent view controller, implement prepareForSegue:sender:, like you would for a typical navigation action. Remember the identifier you gave to the segue in the storyboard? That’s where you’ll use it. The segue fires when your parent view controller loads, so this method is where you’ll pass any data to the child view controller(s).

The standard containment patterns apply here as well. You can send data objects to the children, and have your parent become a delegate of the child if you need to pass data back and forth. More info on the containment concept can be found over at objc.io.

The wrinkle that I threw in was the header view. In my storyboard, had I pinned the container to the bottom of my header I wouldn’t see any rows behind the header. But as it is my child view controller will start at the top of the screen, since that is where my topLayoutGuide says that it is.

To get around this is where Tim was a huge help. He came up with the idea of a protocol that could be implemented on the child view controller, which passes back the root scroll view. Using this view, and my parent’s topLayoutGuide I can apply new content insets to the child. If the child doesn’t conform to the protocol, I can go on.

The trick is to override topLayoutGuide and return an object that conforms to the UILayoutSupport protocol with my custom height. I just created an object that implements only that protocol for the purpose of the demo, as well as Scorebook.

Once you’ve applied the content inset, it will work just like you expect it to. Below is the beginning without the new contentInset on the left, and with it applied on the right.

I encourage you to look over the sample code. Let me know if you have any thoughts or questions.

Scorebook Now Available!

It's been a crazy last week for me, and in all the hubbub I neglected to post that Scorebook got approved and is available for sale!

I'm really excited to have it in the world, and I've gotten some great feedback thus far. I'd love it if you checked it out.

The Taphouse site is still being built out, so in the meantime here's the App Store link.

Twins!

Twins!

A little over a month ago, we found out Emily is pregnant. This week we had our first ultrasound, and the tech discovered something we weren't expecting: 2 babies. Twins!

We are slowly coming out of the shock of it all, and trying to figure out what this means for our family going forward. We weren't expecting to go from 3 to 5 so quickly. There are lots of logistics and things to figure out in the coming months, but I'm really excited for the road ahead.

They are due June 5 (baby A), and June 9 (baby B). Yes, different due dates are possible but that's not going to happen. Just one hospital trip for us, thank you.

Introducing Scorebook

This is a big day for me. Huge, even. Today I am announcing my first app. I call it Scorebook.

My grandparents were married for 60 years and loved playing games with each other. Cribbage, dominoes, and others. Over the years they kept record of winner and loser in a little book. After my wife and I got married we started playing games too. I remembered my grandparents and suggested that we start keeping score like they did.

And then I wanted an app to help us out, so Scorebook was born.

Scorebook gives you a place to remember the games you play. It's great for capturing your memories playing games with friends or family. You can attach pictures, notes, and location too.

I'm so excited to get Scorebook out into the world. My wife and I have been using it and it's great. Soon I'll be able to start playing games with my kids and I can't wait to capture those memories too.

Scorebook will be available for iPhones running iOS 8 or later. It's very late in development and my plan is to release on November 25 for $2.99.

For some screenshots to see what it looks like, check out my new company, Taphouse Software.