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!

My Most Used Code Snippet in Xcode

It’s good to get in the habit of having good habits. 🙂

A lot of people write custom UIView code but then have trouble debugging a few things. Mostly pertaining to writing it to be initialized in code, then they initialize it in Interface Builder. As such, I just use the following snippet and it saves me a lot of boilerplate.

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self configureView];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self configureView];
    }
    return self;
}

- (void)configureView
{
  // common initialization
}

That’s it for today.