Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
639 views
in Technique[技术] by (71.8m points)

swift - sectionName in tableView with date

//date 
func sectionName() -> String{
    var shortDate: String
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "MMM yyyy"


 return dateFormatter.stringFromDate(NSDate())
}

//tableView 
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchedRequest, managedObjectContext: coreDataStack.managedObjectContext!, sectionNameKeyPath: "sectionName", cacheName: nil)

//error

is not key value coding-compliant for the key "sectionName".'

I am stuck the app is crashing at "sectionName" I don't know why.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You have probably defined sectionName() as a free function, and not as a property or method of your managed object subclass that is being fetched. Also formatting the "current date" as a string does not make much sense, as you probably want to group the objects according to some date property of your entity.


Apple's sample project Custom Section Titles with NSFetchedResultsController demonstrates how to group a table view into sections based on the month and year of a "timeStamp" property.

That project is written in Objective-C. Here is a short recipe how the same can be achieved in Swift. In the following, I assume that the entity and the managed object subclass is called Event, and that the events should be grouped according to the month and year of the timeStamp property.

First, add a transient property "sectionIdentifier" of type "String" to the "Event" entity.

Next, define the sectionIdentifier property of the Event class. This can be done directly in the class Event { ... } definition or as an extension:

extension Event {
    var sectionIdentifier : String? {
        // Create and cache the section identifier on demand.

        self.willAccessValueForKey("sectionIdentifier")
        var tmp = self.primitiveValueForKey("sectionIdentifier") as? String
        self.didAccessValueForKey("sectionIdentifier")

        if tmp == nil {
            if let timeStamp = self.valueForKey("timeStamp") as? NSDate {
                /*
                Sections are organized by month and year. Create the section
                identifier as a string representing the number (year * 1000) + month;
                this way they will be correctly ordered chronologically regardless
                of the actual name of the month.
                */
                let calendar  = NSCalendar.currentCalendar()
                let components = calendar.components(.CalendarUnitYear | .CalendarUnitMonth, fromDate: timeStamp)
                tmp = String(format: "%ld", components.year * 1000 + components.month)
                self.setPrimitiveValue(tmp, forKey: "sectionIdentifier")
            }
        }
        return tmp
    }
}

In the table view controller, you have to override the titleForHeaderInSection method to compute a proper title from the section identifier:

override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    /*
    * Creating a date formatter is "expensive". Use a static property so that
    * this is done only once.
    */
    struct Formatter {
        static let formatter : NSDateFormatter = {
            let fmt = NSDateFormatter()
            let dateFormat = NSDateFormatter.dateFormatFromTemplate("MMMM yyyy", options: 0,
                locale: NSLocale.currentLocale())
            fmt.dateFormat = dateFormat
            return fmt
            }()
    }

    /*
    Section information derives from an event's sectionIdentifier, which is a string
    representing the number (year * 1000) + month.
    To display the section title, convert the year and month components to a string representation.
    */

    if let theSection = fetchedResultsController.sections?[section] as? NSFetchedResultsSectionInfo,
        let numericSection = theSection.name?.toInt() {
            let components = NSDateComponents()
            components.year = numericSection / 1000
            components.month = numericSection % 1000
            if let date = NSCalendar.currentCalendar().dateFromComponents(components) {
                let titleString = Formatter.formatter.stringFromDate(date)
                return titleString
            }
    }
    return nil
}

Finally, create the fetched results controller with "timeStamp" as first sort descriptor and "sectionIdentifier" as sectionNameKeyPath:

let fetchRequest = NSFetchRequest(entityName: "Event")
let timeStampSort = NSSortDescriptor(key: "timeStamp", ascending: false)
fetchRequest.sortDescriptors = [timeStampSort]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, 
        managedObjectContext: context,
        sectionNameKeyPath: "sectionIdentifier",
        cacheName: nil)

If the time stamp of an object can be modified then things become slightly more complicated. The corresponding "sectionIdentifier" must be invalidated so that it is computed again on demand. In Objective-C, that is fairly simple by overriding the getter method of the "timeStamp" property only (see DateSectionTitles/APLEvent.m). In Swift this seems to require that you define "timeStamp" as a normal computed property (without @NSManaged), as discussed in https://devforums.apple.com/thread/262118?tstart=0:

class Event: NSManagedObject {

    //@NSManaged var timeStamp : NSDate
    var timeStamp: NSDate? {
        get {
            self.willAccessValueForKey("timeStamp")
            let tmp = self.primitiveValueForKey("timeStamp") as? NSDate
            self.didAccessValueForKey("timeStamp")
            return tmp
        }
        set {
            self.willChangeValueForKey("timeStamp")
            self.setPrimitiveValue(newValue, forKey: "timeStamp")
            self.didChangeValueForKey("timeStamp")
            // If the time stamp changes, the section identifier become invalid.
            self.setPrimitiveValue(nil, forKey: "sectionIdentifier")
        }
    }

    override class func keyPathsForValuesAffectingValueForKey(key: String) -> Set<NSObject> {
        var keyPaths = super.keyPathsForValuesAffectingValueForKey(key)
        if key == "sectionIdentifier" {
            keyPaths.insert("timeStamp")
        }
        return keyPaths
    }
}

Update: As of Swift 4 you have to make the custom Core Data properties explicitly visible to the Objective-C runtime by adding the @objc attribute, e.g.

@objc var sectionIdentifier : String? { ... }

@objc var timeStamp: NSDate? { ... }

For more information about this change, see


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...