8

Od kilku dni zmagam się z tworzeniem filtru w projekcie Swift dla TableViewController, który wykorzystuje Core Data. I w końcu zorientowali się, że trzeba użyć UISearchController utwórz NSPredicate dla searchController.searchBar itpSwift UISearchController podłączony do Core Data Project, aplikacje działa, ale wyszukiwanie nie aktualizuje

znalazłem this post EXTREMELY helpful, ale po modelowania mój TVC po tym projekcie, znaleźć „wszystkie światła są włączone, ale home niczyja” . Mogę wyszukiwać, predykat jest tworzony w searchBar, dodawać, usuwać itd., Ale komórki nie aktualizują się w celu wyszukiwania. Brakuje mi czegoś, ale nie wiem co.

Oto odpowiednie fragmenty mojego kodu.

class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchControllerDelegate, UISearchResultsUpdating 

// Properties that get instantiated later 

var detailViewController: DetailViewController? = nil 
var addNoteViewController:AddNoteViewController? = nil // I added this 
var managedObjectContext: NSManagedObjectContext? = nil 

// Added variable for UISearchController 
var searchController: UISearchController! 
var searchPredicate: NSPredicate? // I added this. It's optional on and gets set later 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     self.navigationItem.leftBarButtonItem = self.editButtonItem() 

     if let split = self.splitViewController { 
      let controllers = split.viewControllers 
      let context = self.fetchedResultsController.managedObjectContext 
      let entity = self.fetchedResultsController.fetchRequest.entity! 
      self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController 
     } 

     // UISearchController setup 
     searchController = UISearchController(searchResultsController: nil) 
     searchController.dimsBackgroundDuringPresentation = false 
     searchController.searchResultsUpdater = self 
     searchController.searchBar.sizeToFit() 
     self.tableView.tableHeaderView = searchController?.searchBar 
     self.tableView.delegate = self 
     self.definesPresentationContext = true 
    } 

    // MARK: - UISearchResultsUpdating Delegate Method 
    // Called when the search bar's text or scope has changed or when the search bar becomes first responder. 
    func updateSearchResultsForSearchController(searchController: UISearchController) { 
     let searchText = self.searchController?.searchBar.text // steve put breakpoint 
     println(searchController.searchBar.text) 
     if let searchText = searchText { 
      searchPredicate = NSPredicate(format: "noteBody contains[c] %@", searchText) 
      self.tableView.reloadData() 
      println(searchPredicate) 
     } 
    } 

    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { 
     if editingStyle == .Delete { 
      var note: Note 
      if searchPredicate == nil { 
       note = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Note 
      } else { 
       let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() { 
        return self.searchPredicate!.evaluateWithObject($0) 
       } 
       note = filteredObjects![indexPath.row] as! Note 
      } 
      let context = self.fetchedResultsController.managedObjectContext 
      context.deleteObject(note) 

      var error: NSError? = nil 
      if !context.save(&error) { 
       abort() 
      } 
     } 
    } 

    // MARK: - Fetched results controller 

    var fetchedResultsController: NSFetchedResultsController { 
     if _fetchedResultsController != nil { 
      return _fetchedResultsController! 
     } 

     let fetchRequest = NSFetchRequest() 
     // Edit the entity name as appropriate. 
     let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: self.managedObjectContext!) 
     fetchRequest.entity = entity 

     // Set the batch size to a suitable number. 
     fetchRequest.fetchBatchSize = 20 

     // Edit the sort key as appropriate. 
     let sortDescriptor = NSSortDescriptor(key: "noteTitle", ascending: false) 
     let sortDescriptors = [sortDescriptor] 

     fetchRequest.sortDescriptors = [sortDescriptor] 

     // Edit the section name key path and cache name if appropriate. 
     // nil for section name key path means "no sections". 
     let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master") 
     aFetchedResultsController.delegate = self 
     _fetchedResultsController = aFetchedResultsController 

     var error: NSError? = nil 
     if !_fetchedResultsController!.performFetch(&error) { 
      // Replace this implementation with code to handle the error appropriately. 
      // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
      println("Unresolved error \(error), \(error?.userInfo)") 
      abort() 
     } 

     return _fetchedResultsController! 
    } 

    var _fetchedResultsController: NSFetchedResultsController? = nil 

    func controllerWillChangeContent(controller: NSFetchedResultsController) { 
     //  self.tableView.beginUpdates() // ** original code, change if doesn't work** steve put breakpoint here 
     // ANSWER said this section is redundant, but keeping it b/c it doesn't crash 
     if searchPredicate == nil { 
      tableView.beginUpdates() 
     } else { 
      (searchController.searchResultsUpdater as! MasterViewController).tableView.beginUpdates() 
     } 
    } 

    func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { 

     var tableView = UITableView() 
     if searchPredicate == nil { 
      tableView = self.tableView 
     } else { 
      tableView = (searchController.searchResultsUpdater as! MasterViewController).tableView 
     } 

     switch type { 
     case .Insert: 
      self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) 
     case .Delete: 
      self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) 
     default: 
      return 
     } 
    } 

Myślę, że mój problem "żyje" tu, w tej sekcji. Autouzupełnianie jest moim przyjacielem, ale nie widzę "searchIndex", o którym mowa w autouzupełnianiu. Myślę, że czegoś brakuje, ale nie jestem pewien, co i jak.

Jeśli dotarłeś tak daleko, dziękuję za przeczytanie. Here's the GitHub repo dla oddziału, nad którym pracuję.

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { 

     var tableView = UITableView() 

     if self.searchPredicate == nil { 
      tableView = self.tableView 
     } else { 
      tableView = (self.searchController.searchResultsUpdater as! MasterViewController).tableView 
     } 

     switch type { 
     case .Insert: 
      println("*** NSFetchedResultsChangeInsert (object)") 
      tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) 
     case .Delete: 
      println("*** NSFetchedResultsChangeDelete (object)") 
      tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) 
     case .Update: 
      println("*** NSFetchedResultsChangeUpdate (object)") 
      // TODO: need fix this 

      // ORIGINAL CODE 
      // self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code 

      // PROSPECTIVE SOLUTION CODE 
      println("*** NSFetchedResultsChangeUpdate (object)") 
      if searchPredicate == nil { 
       self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code 
      } else { 
       // Should search the do something w/ the UISearchControllerDelegate or UISearchResultsUpdating 
       // Instead of "indexPath", it should be "searchIndexPath"--How? 
       let cell = tableView.cellForRowAtIndexPath(searchIndexPath) as LocationCell // My cell is a vanilla cell, not a xib 
       let location = controller.objectAtIndexPath(searchIndexPath) as Location // My object is a "Note" 
       cell.configureForLocation(location) // This is from the other guy's code, don't think it's applicable to me 
      } 

     case .Move: 
      println("*** NSFetchedResultsChangeMove (object)") 
      tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) 
      tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) 
     default: 
      return 
     } 
    } 



    func controllerDidChangeContent(controller: NSFetchedResultsController) { 
     if self.searchPredicate == nil { 
      self.tableView.endUpdates() 
     } else { 
      println("controllerDidChangeContent") 
      (self.searchController.searchResultsUpdater as! MasterViewController).tableView.endUpdates() 
     } 

    } 

Edit: Per @pbasdf, Dodaję metody Tableview.

// MARK: - Table View 

override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 
    return self.fetchedResultsController.sections?.count ?? 0 
} 

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    if self.searchPredicate == nil { 
     let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo 
     return sectionInfo.numberOfObjects 
    } 

    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 
} 

override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { 
    // Return false if you do not want the specified item to be editable. 
    return true 
} 
+1

Czy delegat wyszukiwarki SearchController jest ustawiony na własny? W 'viewDidLoad':' searchController.delegate = self' i 'searchController.searchResultsUpdater = self' – Ian

+0

To nic nie dało, ale aplikacja nie zawiesza się po uruchomieniu.Mój mózg jest smażony na cały dzień, ale mam zamiar wykopać się i zobaczyć, czy brakuje mi metody delegatów gdzieś, co powstrzymuje ją od aktualizacji. Dziękuję za patrzenie. – Adrian

+1

Link do repozytorium GitHub jest zepsuty. Ale możesz zamiast tego dodać metody tableView dataSource (numberOfSectionsInTableview, numberOfRowsInSection, cellForRowAtIndexPath, itp.) Do swojego posta. Dzięki. – pbasdf

Odpowiedz

8

Problemy leżą w twoich metod DataSource Tableview: numberOfSectionsInTableview:, tableView:numberOfRowsInSection:, a tableView:cellForRowAtIndexPath:. Potrzebujesz każdej z tych metod, aby zwrócić różne wyniki, jeśli parametr searchPredicate nie jest zerowy - tak jak ma to miejsce w przypadku metody tableView:commitEditingStyle:. Chciałbym zrobić filteredObjects właściwość instancji (zdefiniowany na początku klasy) tak, że wszystkie te metody można do niego dostęp:

var filteredObjects : [Note]? = nil 

Teraz, kiedy przeszukać zmiany tekstowe, chcesz odbudować tablicę filteredObjects. Więc w updateSearchResultsForSearchController, dodaj linię do przeliczyć ją na podstawie nowego orzecznika:

if let searchText = searchText { 
     searchPredicate = NSPredicate(format: "noteBody contains[c] %@", searchText) 
     filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() { 
      return self.searchPredicate!.evaluateWithObject($0) 
     } as [Note]? 
     self.tableView.reloadData() 
     println(searchPredicate) 
    } 

polecam również (dla uproszczenia), które po uaktywnieniu wyszukiwania Wyniki wyświetlane są w jednym punkcie (w przeciwnym razie trzeba wypracowanie ile sekcje twoi filtrowane wyniki wpaść - możliwe, ale męczące):

override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 
    if searchPredicate == nil { 
     return self.fetchedResultsController.sections?.count ?? 0 
    } else { 
     return 1 
    } 
} 

Następnie, jeśli searchPredicate nie jest zerowa, liczba wierszy w sekcji będzie liczba filteredObjects:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    if self.searchPredicate == nil { 
     let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo 
     return sectionInfo.numberOfObjects 
    } else { 
     return filteredObjects?.count ?? 0 
    } 
} 

Wreszcie, jeśli searchPredicate nie jest zerowa, trzeba zbudować komórkę wykorzystując dane filteredObjects, zamiast fetchedResultsController:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell 
    if searchPredicate == nil { 
     self.configureCell(cell, atIndexPath: indexPath) 
     return cell 
    } else { 
     // configure the cell based on filteredObjects data 
     ... 
     return cell 
    } 
} 

Nie wiesz co etykiet itp masz w swoich komórkach, więc zostawiam go do ciebie, żeby posortować ten kawałek.

+0

Whoa! Próbuję to zrobić od soboty (zacząłem iOS 1 miesiąc temu). Dziękuję bardzo! Mój obiekt Note ma 'noteTitle' &' noteBody', bez specjalnej komórki. Myślę, że muszę dodać metodę delegata lub dwie, aby zresetować searchPredicate do 'nil' i myślę, że wszystko powinno wrócić do normy, jeśli predykat jest' nil'. – Adrian

+1

Tak, użyj metody delegata searchDontroller 'didDismissSearchController', aby ustawić predykat na zero i ponownie załadować telewizor. – pbasdf

+0

Tak! Zresetowałem 'searchPredicate' i' filtersObjects' do 'nil' i dodałem' self.tableView.reloadData() 'na końcu' didDismissSearchController'. Teraz zmagam się z prawidłowym wyświetlaniem wyników. W konsoli "po przefiltrowanych elementach" wypisuje właściwe elementy, ale nie jest to odzwierciedlane na ekranie. Dziękuję bardzo! Dużo się tutaj uczę. – Adrian