Reputation: 31283
Say there's a class called Post
(as in a forum post).
class Post: NSManagedObject {
@NSManaged var id: NSNumber
@NSManaged var title: String?
@NSManaged var body: String?
@NSManaged var voteCount: NSNumber?
@NSManaged var thread: Thread?
}
And a Thread
class.
class Thread: NSManagedObject {
@NSManaged var topic: String
@NSManaged var id: NSNumber
@NSManaged var posts: NSSet
}
A thread contains a set of Post
objects.
From the local core data database I retrieve an array of Thread
objects to var threads = [Thread]()
.
Now I need to filter out posts in threads that have a vote count of more than 0. In other words I need an array of Thread
objects excluding the Post
s with 0 votes.
Here's what I've tried so far.
var filteredThreads = threads.filter() { $0.posts.filter() { $0.voteCount > 0 } }
But I get the error 'Array<(Post)>' is not convertible to 'Bool'.
It seems I cannot use nested filters like this. Or am I doing it wrong?
Edit: I also tried the below code. No compile time errors but it doesn't filter the returned results array as expected.
threads = items.filter() {
for post in $0.posts.allObjects as [Post] {
if post.voteCount!.integerValue > 0 {
return true
}
}
return false
}
I uploaded a Xcode project here demonstrating the issue. Any help is appreciated.
Thanks.
Attempt 2: I tried iterating through the Thread
objects I receive and filter out the Post
s inside it. But I don't know how to add the filtered array back to the Thread
object.
if let items = viewModel.getThreads() {
for thread in items {
let posts = thread.posts.allObjects as [Post]
var s = posts.filter() {
if $0.voteCount!.integerValue > 0 {
return true
} else {
return false
}
}
}
}
Upvotes: 1
Views: 741
Reputation: 539685
Your method cannot work. You cannot filter the Thread objects in such a way that you get an array of "modified" Thread objects which are only related to the Posts with positive vote count. Filtering gives always a subset of the original objects, but those objects are not modified.
The proper way to achieve what you want is to fetch all Post objects with positive vote count, and group them into sections according to their Thread.
The easiest way to do so is a NSFetchedResultsController
.
Here is a quick-and-dirty version of your ForumViewController that uses a fetched results controller. Most of it is the boilerplate code that you get with a standard "Master-Detail Application + Core Data" in Xcode. It certainly can be improved but hopefully should get you on the right track.
To make this compile in your sample project, managedObjectContext
in the ForumViewModel
needs to be a public property.
Alternatively, you can move the fetched results controller creation
into the ForumViewModel
class.
// ForumViewController.swift:
import UIKit
import CoreData
class ForumViewController: UITableViewController, NSFetchedResultsControllerDelegate {
private let viewModel = ForumViewModel()
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
// Fetch "Post" objects:
let fetchRequest = NSFetchRequest(entityName: "Post")
// But only those with positive voteCount:
let predicate = NSPredicate(format: "voteCount > 0")
fetchRequest.predicate = predicate
// First sort descriptor must be the section key:
let topicSort = NSSortDescriptor(key: "thread.topic", ascending: true)
// Second sort descriptor to sort the posts in each section:
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [topicSort, titleSort]
// Fetched results controller with sectioning according the thread.topic:
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: viewModel.managedObjectContext,
sectionNameKeyPath: "thread.topic", cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
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
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let post = self.fetchedResultsController.objectAtIndexPath(indexPath) as Post
cell.textLabel!.text = post.title
cell.detailTextLabel!.text = "\(post.voteCount!)"
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionInfo = self.fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
return sectionInfo.name
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
default:
return
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
}
Result:
Upvotes: 2
Reputation: 524
That function inside filter must return something with type 'Bool'
I think this following code might come handy for you
var filteredThreads = threads.filter({
var result = false;
for (var i = 0; i<$0.posts.count;i++) {
if ($0.posts[i].voteCount > 0){
result = true;
}
}
return result
})
This blog post might become usefull
Upvotes: 0
Reputation: 51911
$0.posts.filter() { $0.voteCount > 0 }
returns an array of Post
that voteCount
is positive. You have to check the count
of it:
var filteredThreads = threads.filter() {
$0.posts.filter({ $0.voteCount > 0 }).count > 0
// ^^^^^^^^^^
}
But, this unconditionally iterates all posts
. Instead, you should return true
as soon as possible:
var filteredThreads = threads.filter() {
for p in $0.posts {
if p.voteCount > 0 {
return true
}
}
return false
}
Upvotes: 1