【问题标题】:A NSFetchedResultsController with date as sectionNameKeyPath日期为 sectionNameKeyPath 的 NSFetchedResultsController
【发布时间】:2011-05-24 01:16:33
【问题描述】:

我开发了一个使用 Core Data 的应用程序。在一个 UITableView 中,我想显示我的实体列表,按对象的保存日期排序。当我这样做时:

fetchedResultsController = [[NSFetchedResultsController alloc]
                            initWithFetchRequest:fetchRequest
                            managedObjectContext:managedObjectContext
                              sectionNameKeyPath:@"date"
                                       cacheName:nil];

我为每个对象获得一个新部分,因为此代码也根据秒对日期进行分组。但我想要一个按日期分组的对象列表,但只能按日、月和年分组。有可能吗?怎么做?

非常感谢您的帮助!! ;)

【问题讨论】:

    标签: iphone sorting core-data nsfetchedresultscontroller


    【解决方案1】:

    以下是按日期排序的 Swift 3 解决方案,但部分标题对应于各个日期。

    1. 在 Core Data 中向您的实体添加一个名为 daySectionIdentifier 的瞬态属性。
    2. 重新生成NSManagedObject 子类。删除daySectionIdentifier 可能在Entity+CoreDataProperties.swift 中生成的属性。
    3. Entity+CoreDataClass.swift 文件中,为daySectionIdentifier 添加以下getter:

      // Transient property for grouping a table into sections based
      // on day of entity's date. Allows an NSFetchedResultsController
      // to sort by date, but also display the day as the section title.
      //   - Constructs a string of format "YYYYMMDD", where YYYY is the year,
      //     MM is the month, and DD is the day (all integers).
      
      public var daySectionIdentifier: String? {
          let currentCalendar = Calendar.current
          self.willAccessValue(forKey: "daySectionIdentifier")
          var sectionIdentifier = ""
          if let date = self.date as? Date {
              let day = currentCalendar.component(.day, from: date)
              let month = currentCalendar.component(.month, from: date)
              let year = currentCalendar.component(.year, from: date)
      
              // Construct integer from year, month, day. Convert to string.
              sectionIdentifier = "\(year * 10000 + month * 100 + day)"
          }
          self.didAccessValue(forKey: "daySectionIdentifier")
      
          return sectionIdentfier
      }
      
    4. 在您的UITableViewController 实现中,添加以下方法:

      override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
          var sectionTitle: String?
          if let sectionIdentifier = fetchedResultsController.sections?[section].name {
              if let numericSection = Int(sectionIdentifier) {
                  // Parse the numericSection into its year/month/day components.
                  let year = numericSection / 10000
                  let month = (numericSection / 100) % 100
                  let day = numericSection % 100
      
                  // Reconstruct the date from these components.
                  var components = DateComponents()
                  components.calendar = Calendar.current
                  components.day = day
                  components.month = month
                  components.year = year
      
                  // Set the section title with this date
                  if let date = components.date {
                      sectionTitle = DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .none)
                  }
              }
          }
      
          return sectionTitle
      }
      
    5. 在构造 NSFetchedResultsController 时,使用 "daySectionIdentifier" 作为 sectionNameKeyPath 参数调用初始化程序。
    6. NSFetchedResultsController 的排序描述符设置为实体的普通旧"date" 属性。重要的是,基于"date" 的排序顺序将与基于我们刚刚构建的部分标识符的排序顺序一致。

    您现在应该将表格视图按天分组(例如,“2017 年 2 月 6 日”),并按细粒度日期排序。

    【讨论】:

    • 看起来是一个非常好的解决方案,但在 swift 4/Xcode 9 中不起作用。根本没有调用 getter。
    • 哇!按computed 属性划分项目是个好主意。它很好用,但如果您将属性声明为 @objc public var @alionthego
    【解决方案2】:

    遇到同样问题时,我使用了@BoltClock 的独角兽和@Rog 的anwser。 只需向我的托管对象添加一个瞬态 NSString *sectionTitle,使用 @"sectionTitle" 作为 sectionNameKeyPath 并创建一个自定义 getter,如下所示:

    -(NSString *)sectionTitle
    {
        NSDate *_now = [NSDate date];
        NSDate *_today = [_now dateByAddingTimeInterval: -86400.0];
        NSDate *_yesterday = [_now dateByAddingTimeInterval: -172800.0];
        NSDate *_thisWeek  = [_now dateByAddingTimeInterval: -604800.0];
        NSDate *_lastWeek  = [_now dateByAddingTimeInterval: -1209600.0];
        NSDate *_thisMonth = [_now dateByAddingTimeInterval: -2629743.0]; 
       // if better precision required use something more sophisticated for month...
    
       double today = [_today timeIntervalSince1970];
       double yesterday = [_yesterday timeIntervalSince1970]; 
       double thisWeek = [_thisWeek timeIntervalSince1970];
       double lastWeek = [_lastWeek timeIntervalSince1970]; 
       double thisMonth = [_thisMonth timeIntervalSince1970];
    
       [self willAccessValueForKey:@"timestamp"];
           double ts = [self.timestamp timeIntervalSince1970];
       [self didAccessValueForKey:@"timestamp"];
    
       NSString *title = @"";
       if(ts >= today) title = NSLocalizedString(@"TODAY",nil);
       else if (ts >= yesterday) title = NSLocalizedString(@"YESTERDAY",nil);
       else if (ts >= thisWeek) title = NSLocalizedString(@"THIS WEEK",nil);
       else if (ts >= lastWeek) title = NSLocalizedString(@"LAST WEEK",nil);
       else if (ts >= thisMonth) title = NSLocalizedString(@"THIS MONTH",nil);
    
       return title;
    }
    

    【讨论】:

    • 永远不要使用dateByAddingTimeInterval 来计算日期。您是否考虑过闰年会发生什么?
    • 是的,这只是一个基本的例子来给你这个想法
    • 给出带有错误代码的示例代码总是一个坏主意。你的整个事情本来可以用一个简单的 NSDateFormatter 来完成,它的 dosRelativeDateFormatting 选项设置为 YES。
    • 是的,现在回想起来,我很惊讶我写了这样的东西:) 进展很好!
    【解决方案3】:

    我觉得这样会更好。

    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    
        // Replace DataClassObject with whatever object your using
        DataClassObject *tempObject = [[sectionInfo objects] objectAtIndex:0];
    
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"d MMMM yyyy"];
        NSString *formattedDateStr = [formatter stringFromDate:tempObject.date];
        [dateFormatter release]
    
        return formattedDateStr;
    }
    

    【讨论】:

    • 一开始我同意你的看法,但在尝试了这个之后,我意识到这会贯穿我获取的所有批次,并且在大型数据集上真的很慢。使用@Rog的解决方案会快一点。
    【解决方案4】:

    【讨论】:

    • 我可以让它工作在月和年。但我不能让它工作在日、月和年,请你帮帮我。
    【解决方案5】:

    这应该对你有用:

    - (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;  
    }
    

    [编辑]

    刚刚看到您的评论,对于您想要实现的目标,您可以创建一个瞬态 NSDate 属性(非持久性),其格式与上述代码类似(即没有 H:mm:ss ZZZZ)并将该属性用作您的 sectionNameKeyPath 值。

    简而言之,对于具有fooDatefooDateTransient 属性的foo 对象,您可以:

    1. 获取您的foo.fooDate 属性

    2. 使用上面的代码(或类似代码)对其进行转换并将NSDate结果分配给foo.fooDateTransient

    3. 在创建fetchedResultsController 对象时使用fooDateTransient 作为您的sectionNameKeyPath

    PS:我自己没有测试过,但应该值得一试!

    祝你好运, 罗格

    【讨论】:

    • 谢谢,但就是这样,如何显示部分的标题。但它不会对具有相同日期的两个对象进行分组。每个对象仍然有一个部分。
    • 公平地说,我已经编辑了我的答案,建议您可以尝试另一种方法。
    • 我们能得到更新吗?我在同一点 qPaul 在他的第一条评论中(即每个对象的部分标题。)我有点理解这里的技术与瞬态属性但我对在哪里执行项目 #1 感到困惑,并且#2?我有一个构造并返回正确的 NSFetchedResultsController 的方法。在此方法中,我需要为 sectionNameKeyPath 设置项目#3。我应该用哪些方法来做项目#1 和#2?在 viewDidLoad 中获得 FetchedResults 后,我是否要遍历所有结果并显式设置瞬态 Date 属性?
    • 是在 foo.fooDatefoo 托管对象中创建自定义设置器的最佳方法,该设置器将自身与瞬态值一起设置?谢谢!
    • 很遗憾,临时属性不能在 sectionNameKeyPath 或 SortDescroptors 中使用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    • 2012-05-13
    • 2014-07-30
    • 1970-01-01
    相关资源
    最近更新 更多