Swift Coredata: Unable to Section Tableview Using Sectionnamekeypath with Custom Function

TableView section update depend on CoreData transient property change

I have found a solution for this problem. It is definitely not the best but it's working nicely. (Swift 2.3)

First, I have a transient property called "sectionIdentifier".This could be "NOW", "NEXT" and "PAST" depend on the current time.I added a new persistent property next to this transient called "sectionID". This property's purpose is to tell if the lesson status (NOW, NEXT, PAST) changed. I changed my original fetch to this:

let fetchRequest = NSFetchRequest(entityName: "Lessons")
let predicate = NSPredicate(format: "startsday = '\(dateFormatter.stringFromDate(NSDate()))'", NSDate())
fetchRequest.predicate = predicate
let sortDescriptorStarts = NSSortDescriptor(key: "starts", ascending: true)
let sortDescriptorTitle = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptorStarts, sortDescriptorTitle]
fetchRequest.fetchBatchSize = 30
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "sectionIdentifier", cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch _ {
//Handle error
}
if let lessons = fetchedResultsController.fetchedObjects as? [Lessons] {
for lesson in lessons {
if lesson.sectionID != lesson.sectionIdentifier! {
lesson.setValue(lesson.sectionIdentifier!, forKey: "sectionID")
moc.refreshObject(lesson, mergeChanges: true)
}
}
}

After the fetch I set this persistent property to the transient's value. (last 8 line above)

In viewDidLoad() I added this line.

timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TodayViewController.update), userInfo: nil, repeats: true)

I update my transient property every second.
My update method looks like this:

func update() {
for lesson in fetchedResultsController.fetchedObjects as! [Lessons] {
if lesson.sectionID != lesson.sectionIdentifier! {
lesson.setValue(lesson.sectionIdentifier!, forKey: "sectionID")
moc.refreshObject(lesson, mergeChanges: true)
do {
try moc.save()
}
catch _ {
//Handle error
}
}
}
}

So my transient property will always give the lessons current status and the persistent one will alway give the tableviewcell's status. If there is difference I put my transient value into the persistent property and with moc.refreshobject() i will get a the proper FetchedResultControllerDelegate method and i can handle the tableview changes there.

How can I set NSFetchedResultsController's section sectionNameKeyPath to be the first letter of a attribute not just the attribute in Swift, NOT ObjC

Add a function to your DiveSite class to return the first letter of the country:

@objc func countryFirstCharacter() -> String {
let startIndex = country.startIndex
let first = country[...startIndex]
return String(first)
}

Then use that function name (without the ()) as the sectionNameKeyPath:

let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: (dataModel?.container.viewContext)!,
sectionNameKeyPath: "countryFirstCharacter",
cacheName: nil)

Note that the @objc is necessary here in order to make the (Swift) function visible to the (Objective-C) FRC. (Sadly you can't just add @objc to your extension of String.)

CoreData: Unable to load class for entity

Referring to my own answer, maybe you should also make sure you cast any fetch result to the appropriate class. E.g.

let result = context.executeFetchRequest(request, error:nil) as [User]

In response to your code update, you should perhaps try to insert new instances as follows.

var user = NSEntityDescription.insertNewObjectForEntityForName( "User", 
inManagedObjectContext: context) as User

Sectioning TableView and rows with Core Data Swift

Just a hint: If you're using CoreData and UiTableView use NSFetchedResultsController to make things much, much easier. If you're searching for a starting point & sample code - just create a new master-detail-application project in Xcode and turn on "Use Core Data" in the dialog.

Now, straight to your question: an example "implementation" of the NSFetchResultsController:

var fetchedResultsController: NSFetchedResultsController {

  if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity

// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20

// Edit the sort key as appropriate.
let sectionSortDescriptor = NSSortDescriptor(key: "startDate", ascending: true)
let secondSortDescriptor = NSSortDescriptor(key: "title", ascending: true)

let sortDescriptors = [sectionSortDescriptor, secondSortDescriptor]

fetchRequest.sortDescriptors = sortDescriptors

// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: "startDate", cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController

var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}

return _fetchedResultsController!
}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}

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

Adding new object to CoreData with one to many relation

The crash is occurring because the FRC is creating a new section, but your code does not currently create a corresponding tableView section. If you implement the NSFetchedResultsControllerDelegate method:

controller:didChangeSection:atIndex:forChangeType:

that should fix the crash.

But I'm afraid I can't see why the FRC is creating a new section.

A NSFetchedResultsController with date as sectionNameKeyPath

This should do the trick for you:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSString *rawDateStr = [[[self.fetchedResultsController sections] objectAtIndex:section] name];
// Convert rawDateStr string to NSDate...
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss ZZ"];
NSDate *date = [formatter dateFromString:rawDateStr];

// Convert NSDate to format we want...
[formatter setDateFormat:@"d MMMM yyyy"];
NSString *formattedDateStr = [formatter stringFromDate:date];
return formattedDateStr;
}

[EDIT]

Jus saw your comment and for what you are trying to achieve, you could create a transient NSDate attribute (non persistent) that is formatted in a similar way to the above code (i.e. without H:mm:ss ZZZZ) and use that attribute as your sectionNameKeyPath value.

So in a nutshell for a foo object, with fooDate and fooDateTransient attributes, you would:

  1. Get your foo.fooDate attribute

  2. Transform it using the code above (or similar) and assign the NSDate result to foo.fooDateTransient

  3. Use fooDateTransient as your sectionNameKeyPath when creating the fetchedResultsController object.

PS: I haven't tested this myself but should be worth a shot!

Good luck,
Rog

NSFetchedResultsController with sections created by first letter of a string

I think I've got yet another option, this one uses a category on NSString...

@implementation NSString (FetchedGroupByString)
- (NSString *)stringGroupByFirstInitial {
if (!self.length || self.length == 1)
return self;
return [self substringToIndex:1];
}
@end

Now a little bit later on, while constructing your FRC:

- (NSFetchedResultsController *)newFRC {
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:awesomeRequest
managedObjectContext:coolManagedObjectContext
sectionNameKeyPath:@"lastName.stringGroupByFirstInitial"
cacheName:@"CoolCat"];
return frc;
}

This is now my favorite approach. Much cleaner/easier to implement. Moreover, you don't have to make any changes to your object model class to support it. This means that it'll work on any object model, provided the section name points to a property based on NSString



Related Topics



Leave a reply



Submit