如何筛选CoreData中下月生日日期并在TableView展示
首先得明确,因为你CoreData里存的是字符串格式的生日,所以第一步必须把这些字符串转成Date对象才能做日期比较,而且得保证转换用的DateFormatter和你存的时候完全一致,不然会转失败哦。接下来一步步来:
1. 准备一致的DateFormatter
你存生日的时候用了.medium的dateStyle,没有timeStyle,所以解析的时候也要用一模一样的配置,还要注意locale和你存的时候一致(一般用当前系统locale就行):
private let birthdayFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .none formatter.locale = Locale.current // 一定要和存储时的locale一致! return formatter }()
2. 从CoreData获取数据并筛选
假设你的CoreData实体是Contact,存储生日的属性是birthdayString。先把所有数据取出来,然后筛选出符合条件的:
// 替换成你的CoreData上下文 let managedContext = persistentContainer.viewContext func loadNextMonthBirthdays() { let fetchRequest: NSFetchRequest<Contact> = Contact.fetchRequest() do { let allContacts = try managedContext.fetch(fetchRequest) // 筛选出下个月过生日的联系人 self.nextMonthBirthdayContacts = allContacts.filter { contact in guard let birthdayStr = contact.birthdayString, let birthdayDate = birthdayFormatter.date(from: birthdayStr) else { // 字符串转Date失败的直接跳过 return false } return self.isBirthdayInUpcomingNextMonth(birthdayDate: birthdayDate) } // 刷新TableView tableView.reloadData() } catch { print("加载联系人失败: \(error.localizedDescription)") } }
3. 核心:判断生日是否属于下个月
这里要注意跨年的情况(比如当前是12月,下个月是明年1月),还有生日已经过了今年的情况(比如当前是6月,用户生日是1月,那下一次生日是明年1月,如果下个月是7月,就不算;如果当前是12月,下个月1月,就算)。所以我们需要先计算出用户下一次生日的日期,再判断这个日期是否落在下个月的时间范围内:
private func isBirthdayInUpcomingNextMonth(birthdayDate: Date) -> Bool { let calendar = Calendar.current let today = Date() // 计算下个月的起始和结束时间 // 下个月第一天的0点 guard let nextMonthStart = calendar.date(byAdding: .month, value: 1, to: today, wrappingComponents: false)? .startOfDay else { return false } // 下下个月第一天的0点减去1秒,就是下个月的最后一刻 guard let nextMonthEnd = calendar.date(byAdding: .month, value: 2, to: today, wrappingComponents: false)? .startOfDay.addingTimeInterval(-1) else { return false } // 获取用户下一次生日的日期 let nextBirthday = getNextBirthday(from: birthdayDate) // 判断下一次生日是否在[nextMonthStart, nextMonthEnd]区间内 return (nextMonthStart...nextMonthEnd).contains(nextBirthday) } // 计算下一次生日的日期 private func getNextBirthday(from birthdayDate: Date) -> Date { let calendar = Calendar.current let today = Date() let birthdayComponents = calendar.dateComponents([.month, .day], from: birthdayDate) var thisYearBirthdayComponents = DateComponents() thisYearBirthdayComponents.year = calendar.component(.year, from: today) thisYearBirthdayComponents.month = birthdayComponents.month thisYearBirthdayComponents.day = birthdayComponents.day guard var nextBirthday = calendar.date(from: thisYearBirthdayComponents) else { return birthdayDate } // 如果今年的生日已经过了,就取明年的 if nextBirthday < today { thisYearBirthdayComponents.year! += 1 nextBirthday = calendar.date(from: thisYearBirthdayComponents)! } return nextBirthday }
4. TableView数据源实现
这部分就和普通的TableView配置一样了,假设你用了nextMonthBirthdayContacts作为数据源数组:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return nextMonthBirthdayContacts.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "BirthdayCell", for: indexPath) let contact = nextMonthBirthdayContacts[indexPath.row] cell.textLabel?.text = contact.name // 替换成你的联系人姓名属性 cell.detailTextLabel?.text = contact.birthdayString return cell }
额外建议:如果数据量大,建议把CoreData属性改成Date类型
因为现在存的是字符串,筛选需要先转成Date,数据量大的时候内存开销会比较大。如果改成直接存Date类型,就可以用NSPredicate直接在CoreData查询时筛选,效率会高很多。比如:
// 假设属性是birthday: Date let fetchRequest: NSFetchRequest<Contact> = Contact.fetchRequest() let calendar = Calendar.current let nextMonthStart = calendar.date(byAdding: .month, value: 1, to: Date())?.startOfDay let nextMonthEnd = calendar.date(byAdding: .month, value: 2, to: Date())?.startOfDay.addingTimeInterval(-1) fetchRequest.predicate = NSPredicate(format: "birthday >= %@ AND birthday <= %@", nextMonthStart! as CVarArg, nextMonthEnd! as CVarArg) // 然后fetch数据即可,不需要内存中筛选
当然,这个前提是你存储的生日是包含正确年份的,或者你处理生日的逻辑是基于每年重复的,这时候可能需要调整Predicate的写法,但总体来说存Date比存字符串更合理。
内容的提问来源于stack exchange,提问作者user8977455

