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:
Get your
foo.fooDate
attributeTransform it using the code above (or similar) and assign the
NSDate
result tofoo.fooDateTransient
Use
fooDateTransient
as yoursectionNameKeyPath
when creating thefetchedResultsController
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
Weird Behaviour in Swiftui+Combine When Class -> Struct
Preparing for Swift 4 - Unsafemutablepointer Migration to Unsafemutablebufferpointer
How to Pull The Artist Value from Mpmediaitemcollection
Hit Fatal Error: Unexpectedly Found Nil While Unwrapping an Optional Value (Lldb)
How to Get User Nearby My Location in Geofire,Firebase
Error: Missing Return in a Closure Expected to Return 'Uiviewcontroller' (Xcode, Swift, iOS 13)
Populating Collection View from Array in Document Directory
Downloading PDF Using Nsurlsession (Http Post) with Parameters
Overlaying Image on Video Reduces Video Resolution
Is It a Good Way to Access Instance Variable with Self? If I Use a Lot
Gmsmarker Not Appearing on Map
Firebase Remove Snapshot Children Swift
How to Get Buffer from Avaudioengine's Installtap at High Frequency
Why I Can't Access My 3Rd Level Coredata Data in Swift
How to Determine Whether a Double Is an Integer
How to Keep Updating Cloud Kit Record in Swift