Isuru
Isuru

Reputation: 31283

'Array<(Object)>' is not convertible to 'Bool'

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]().

enter image description here

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 Posts 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 Posts 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

Answers (3)

Martin R
Martin R

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:

enter image description here

Upvotes: 2

Ali Riahipour
Ali Riahipour

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

rintaro
rintaro

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

Related Questions