Useful “Finder” methods on NSManagedObject in Swift

I don’t know about you, but CoreData seemed insane to me before I discovered MagicalRecord, back in the days of Mogenerator and Objective-C.

But since Swift 3 has come out, and the tools have improved to support Swift development (remember poor compiler warnings, if not just a “segmentation fault 11” error), I’m finding that I like to work with the Xcode tools again, and forego these old approaches.

My old way of doing things worked very well, and in some ways I miss some aspects of that, but ultimately I quickly (swiftly… cough cough) became a lover of Swift and simply prefer developing in that language.

What I miss most were the “MagicalFinders” categories present in MagicalRecord.  I found quite a concise way to do that however in Swift, and the code looks like this:

import Foundation
import CoreData

@objc public protocol CoreDataFinders {

    /// Because we are doing fetch requests on a data model,
    /// Fetch requests require sort descriptors.
    static func defaultSortDescriptors() -> [NSSortDescriptor]
}

extension CoreDataFinders where Self: NSManagedObject {
    
    public static func findAll(with predicate: NSPredicate?, context: NSManagedObjectContext) -> [Self] {
        
        let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: Self.entity().name!)
        
        let predicate = predicate
        
        fetchRequest.predicate = predicate
        fetchRequest.sortDescriptors = self.defaultSortDescriptors()
        
        do {
            let results = try context.fetch(fetchRequest)
            return results
        } catch {
            if predicate != nil {
                print("Failed to fetch objects with predicate:\(predicate!.description) error:\(error)")
            } else {
                print("Failed to fetch objects with no predicate.  error:\(error)")
            }
        }
        return []
    }
    
    public static func findFirst(with predicate: NSPredicate?, context: NSManagedObjectContext) -> Self? {
        
        let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: Self.entity().name!)
        
        let predicate = predicate
        
        fetchRequest.predicate = predicate
        fetchRequest.sortDescriptors = self.defaultSortDescriptors()
        fetchRequest.fetchLimit = 1
        
        do {
            let results = try context.fetch(fetchRequest)
            return results.first
        } catch {
            if predicate != nil {
                print("Failed to fetch objects with predicate:\(predicate!.description) error:\(error)")
            } else {
                print("Failed to fetch objects with no predicate.  error:\(error)")
            }
            
        }
        return nil
    }
}

And then you can add these to any object by either making a new baseclass in your app that subclasses NSManagedObject, or you just declare protocol support in your class definition and then these methods are added:

import Foundation
import CoreData

@objc(RecentItem)
class RecentItem: NSManagedObject, CoreDataFinders {
  static func defaultSortDescriptors() -> [NSSortDescriptor] {
    return [NSSortDescriptor(key: #keyPath(RecentItem.lastViewed), ascending: false)]
  }
  // ...
}

You can see here that you can extend this approach to pass in your own sort descriptors or limit fetch sizes, etc. This should be enough to get you started! The idea is that you can write the code once and have it apply to all instances of NSManagedObject on an opt-in basis.

Autolayout and Self-sizing UITableViewCell

I’m not going to lie.  Autolayout is a massive pain.  But.  Ultimately it’s very powerful and you’re best to just go through the pain and learn it.

Even so, you should also get a bit familiar with it, then learn about the concept of self-sizing table view cells.  It’s quite important.  Basically, as long as there is a clearly defined way for a UITableViewCell to determine its own height via the auto-layout constraints, dynamic table view cells are pretty easy.

Unfortunately, there is a lot to learn:

This series

Then this

or even Apple

Quick Reference:

How to make sure a UITableViewCell autosizes when you have a label that you want to wrap around onto multiple lines.

1. Pin the Label at top left, bottom, right.

2. Edit the right constraint to be “Greater than or Equal to”, and then set the constant to be the right-most you want to allow that (probably view margin).

3. Set that right-most constraint’s priority to 750 (high), then set (on the Label!) it’s content compression resistance priority to 749.

Should be fine now.