DataSource object for UITableView / UICollectionView in Swift with Core Data

It’s amazing how quickly I now have become a part of the Swift Fan Club.  I recently worked on some old Objective-C code of mine and was amazed how quickly one learns to stop typing semi-colons.  🙂

Today’s post is all about a pattern I use more often in my projects, and it’s one that prefers composition over inheritance.  All that really means in this case is that on any UITableViewController (or similarly, UICollectionViewController), I prefer to create separate Data Source objects that keep all that code separate from the View Controller itself.  (I personally don’t find that MVC stands for Massive View Controller if you don’t let it.)

The issue here is that I pretty much don’t do projects any more without using Core Data.  It is the best solution in my opinion because of the code you oftentimes *don’t* have to write.  Also, with NSFetchedResultsController, I like how you can further separate your data layer (think Networking and importing) from you View Controllers.  View Controllers concern themselves with *what* they want to display, and not with how it is acquired.

Anyway, the strictly typed language of Swift sometimes makes old approaches not straightforward, and I would like to share what I determined today.  It will become a staple in my future Swift projects.

I create a DataSource class that takes a generic type, so that this generic type can be used for Core Data related activities.  By default, a NSFetchedResultsController also takes a generic type of NSFetchRequestResult. But sometimes that is simply not enough. More on this later. To even make a Generic Data source, we have:

class BasicFetchedResultsDataSource: NSObject, NSFetchedResultsControllerDelegate where T:NSManagedObject {
    
    let managedObjectContext: NSManagedObjectContext!
    let tableView: UITableView!
    
    init(context: NSManagedObjectContext!, tableView: UITableView!) {
        
        self.managedObjectContext = context
        self.tableView = tableView
    }
    
    private var _fetchedResultsController: NSFetchedResultsController? = nil
    var fetchedResultsController: NSFetchedResultsController {
        
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
        }
        
        let request = T.fetchRequest()
        request.predicate = self.searchPredicateForFetchRequest
        request.sortDescriptors = self.sortDescriptorsForFetchRequest
        let controller = NSFetchedResultsController(fetchRequest: request as! NSFetchRequest,
                                                    managedObjectContext: self.managedObjectContext,
                                                    sectionNameKeyPath: self.sectionNameKeyPath,
                                                    cacheName: self.resultsControllerCacheName)
        
        controller.delegate = self
        _fetchedResultsController = controller
        return _fetchedResultsController!
    }
    
    
    func updateRequestAndFetch() throws {
        
        self.fetchedResultsController.fetchRequest.predicate = self.searchPredicateForFetchRequest
        self.fetchedResultsController.fetchRequest.sortDescriptors = self.sortDescriptorsForFetchRequest
        
        do {
            try self.fetchedResultsController.performFetch()
            
            self.tableView.reloadData()
        }
        catch {
            throw error
        }
    }
    
    // allows your subclass to override and change this
    var sectionNameKeyPath: String? {
        return nil
    }
    
    // allows your subclass to override and change this
    var resultsControllerCacheName: String? {
        return nil
    }
    
    // allows your subclass to override and change this according to state
    var sortDescriptorsForFetchRequest: [NSSortDescriptor]! {
        return []
    }
    
    // allows your subclass to override and change this according to state
    var searchPredicateForFetchRequest: NSPredicate? {
        return nil
    }
    
    // ... Typical NSFetchedResultsController and UITableViewDataSource code here.
}

That’s it for the basics, but what if my data model is a bit more interesting? In my current project I want my data to be sortable, filterable, searchable, and possibly groupable.

So I define the following:

import CoreData
@objc protocol Sortable: NSFetchRequestResult {
    static func defaultSortDescriptors() -> [NSSortDescriptor]!
}

@objc protocol Groupable: NSFetchRequestResult {
    var groupIndex: String! { get }
}

@objc protocol RelationshipFilterable: NSFetchRequestResult {
    static func relationshipFilterPredicate(for constraintObject:NSManagedObject?) -> NSPredicate?
}

@objc protocol TextSearchable: NSFetchRequestResult {
    static func searchPredicate(for searchTerm:String?) -> NSPredicate?
}

@objc protocol MyGenericDataObject: Sortable, Groupable, RelationshipFilterable, TextSearchable {
    // combines them
}

Then the cool stuff. Subclass the Basic view controller above:

class GenericFetchedResultsDataSource: BasicFetchedResultsDataSource where T:NSManagedObject {
    
    let allowsGrouping: Bool
    let allowsTextSearching: Bool
    
    override init(context: NSManagedObjectContext!, tableView: UITableView!) {
        self.allowsGrouping = true
        self.allowsTextSearching = true
        super.init(context: context, tableView: tableView)
    }
    
    var currentSearchTerm: String? {
        didSet {
            if self.allowsTextSearching {
                do {
                    try self.updateRequestAndFetch()
                }
                catch {
                    print("Fetch Error: \(error)")
                }
            }
        }
    }
    
    override var sectionNameKeyPath: String? {
        return self.allowsGrouping ? #keyPath(MyGenericDataObject.groupIndex) : nil
    }
    
    override var resultsControllerCacheName: String? {
        return nil
    }
    
    override var sortDescriptorsForFetchRequest: [NSSortDescriptor]! {
        return T.defaultSortDescriptors()
    }
    
    override var searchPredicateForFetchRequest: NSPredicate? {
        return self.allowsTextSearching ? T.searchPredicate(for: self.currentSearchTerm) : nil
    }
}

And that’s how you can work with generics and their subclasses. It’s why protocol oriented programming and swift go together nicely!

How Swiftly I began to love Swift…

So, my last post was a little nasty.  Written like a conservative populist, which politically I’m not, but I admit I had my reservations about learning Swift then, and I stand by that opinion at that time.  I’ll be brief this time.   I’ve now had a chance to really do some work with Swift and I have to say overall I quite like it, and never expected that to happen so quickly.  Why the change of heart, you might ask?

  • I guess I don’t like learning from books, but I like learning by doing.  I find a lot of tutorials out there on the internet kind of superficial and boring.  I joined a project where there was already enough Swift code but not too much.  I had the opportunity to contribute based on some code I could already work from.
  • I really do like a lot of the features, such as enums (and being able to use string types)
  • The syntax is pretty good.  It does make for readable, type-safe code.
  • I’m surprised at how quickly one can just start writing useful code
  • Since Swift 3, there really is no reason to say no to it.  Previously it all felt “too new” and not finished.  The compiler warnings were a total disaster. For a strictly typed language, having poor compiler warnings was the most discouraging aspect to it. (Hence the rant about German bureaucrats who aren’t helpful but just say no)
  • I’m sure other reasons here as well

Now, it’s not like I’ve become blindly religious.  Sometimes I don’t like to the strictly typed language, but I suspect my frustration comes from the typical approach of “well, in Objective-C, I could just…” and not yet knowing the equivalent approach in Swift.

But all in all, I think it’s pretty easy to get up and running and to start writing Swift code that is useful and readable.  I’ve seen some library code that tells me I still have a lot to learn, but for now I think it’s kind of a “no turning back” situation.

I’ve already basically done all the types of things I’ve done in objective-C, and find that I really didn’t use KVO that much anymore anyway, so I don’t miss it.

I thought I’d just round off that last angry post with something nice.

Why I Still Don’t Use Swift in Real Projects

(UPDATE:   My opinion has changed)

So, every so often, when I’m not busy making production apps for clients that have to work, be reliable, be easy to read, be easy to extend, be easy to debug, and so on, I have a look at Swift.  I know I’m admitting something that perhaps one best not admit; I don’t use Swift yet, really.  I haven’t fully embraced it.  And I’ll tell you why.

I’m a creative developer.  Also, I live in Germany.  I therefore hate bureaucracy.  There’s a connection.  Germany for me has at times been a near death experience…. death by 1000 paper cuts.    With Objective-C, and all those Cocoa Frameworks, I know these tools very well.  As a creative developer, I am as interested in the beauty of the project on the outside as I am with the beauty of it on the inside.  I would reckon I’m pretty good at writing Objective-C code.  Obviously because of experience and a commitment to quality.

I understand there’s a learning curve with anything new.  I get that.  I clash with Swift when I have a creative idea and I just want to build the thing I want to try out.  App development is a strange voodoo; you can have a good idea on paper, then once it’s in your hand and you interact with it, it makes that idea perhaps less awesome or less useful than you thought.  So, the ability to do rapid prototyping is of great interest to me.

Enter the strictness of Swift and its compiler.  For me it’s like going to any German ministry to get something done.    You turn up with your form (well, forms) with the hope of getting it stamped and approved today.  They turn you away for some reason.  It’s always some reason.  Sometimes they invent reasons because they don’t like the look of you. The civil servants don’t think creatively nor solution-oriented.   You can’t ask for help.  You only have to state what function you would like them to carry out today.  The number of times they give you that stare as if to say “Invalid input.  Invalid input. Invalid input.” and you expect smoke to start billowing out their ears at any second.  Similarly, the Swift compiler annoys the hell out of me because the error messages are still kind of cryptic, not always referring to the actual error and potential solution to the problem (comparing to Objective-C here… which also loses its way from time to time). Basically “sorry, can’t do it.  Go bother someone else.”

No, I’m a free spirit.  I like the dynamic nature of Objective-C.  I like being able to say “I know this is bending the rules a bit, but please just do it, because I know what I’m doing here.”  and the compiler will do it.  Maybe your code crashes at runtime, but maybe not.  In the end, experience will often have a strong say here.

Say I actually did get my Swift code running.   I’m not an early adopter.  I’m just not.  With any software product, I prefer to sit back and wait, let them iron all the kinks out, then I’ll be interested.  I have too many other hobbies and interests that also take my time, so to want to update my old, running code, because now the language itself has changed and is no longer compatible and I am forced to update it, just so it will run, does not sound like a desirable use of my time.

Then I hear just around 10% of apps in the app store actually have Swift code in them.   People make it seem like if you can’t code in Swift you are massively behind the times and are unemployable because it’s *all Swift* now….

I don’t know, for all these reasons, I still sit back and wait.  I try to keep up on the language in general, but for me I’d just like to say “sort yourselves out, and let us know when it’s ready for general consumption, because right now I don’t like make-work projects.  I just want to build great things, quickly.”  Sitting in front of a cryptic compiler error message because of typing or unwind errors is not my idea of swiftly creating anything at all.

iCloud Drive – What a disaster!

So, I’ve been working on a new version of my app Songbook Simple and I want to improve some of the file sync code.  Up until now I’ve been supporting Dropbox, with the original idea that a user can edit and modify all his/her songs from the convenience of their desktop, and it would just sync on the device.  This *has* worked, and *still* works, though it always seemed like a quick and dirty solution to me.

Enter Apple TV.  I thought it would be fun to make Songbook Simple Apple Tv compatible, but in order to do that, one basically has to support iCloud for syncing data.  As the Apple TV won’t be an editor, but just a viewer, we need to get data from somewhere, and I’m not so sure Apple TV is entirely Dropbox friendly yet.

So, I’m trying to implement iCloud support in Songbook.  I followed the excellent 4 part tutorial series written by Ray Wenderlich himself.   It all seemed to work brilliantly.  Files were syncing across iOS devices.

But wait.  Now what about using my Mac to edit files?  Well, turns out this is where iCloud Drive is a complete and utter disaster.  On icloud.com, there is no folder associated with Songbook.  On iOS, the iCloud Drive app DOES show my app folder and its contents.  On OSX, in the iCloud Drive folder, you don’t see anything associated with Songbook.  So my user workflow, namely, editing songs on your Desktop, is broken.

I’m all for “Automagic” for the sake of better User Experience, but if it’s “Autobroken”, then get rid of your magic because you’re killing us.

In order to restore the ability to edit the contents of my SongbookSimple folder, I had to open a terminal window, and do the following:

cd ~/Desktop
ln -s ~/Library/Mobile\ Documents/iCloud~com~softwarebarn~SongbookSimple/Documents/

Then, from Finder, navigate to the Desktop, then either move that Alias that just got made (called Documents) to somewhere better, or simply drag it into my Finder’s sidebar.

Great feature, Apple!  All these years to mimic Dropbox and it’s still a failure.  Yes, I followed ALL the instructions, and spent 3 hours this morning making sure I didn’t miss any.