8

Tôi đã vật lộn vài ngày qua với việc tạo bộ lọc trong dự án Swift cho TableViewController sử dụng Dữ liệu chính. Cuối cùng tôi đã tìm ra tôi cần phải sử dụng một UISearchController, tạo ra một NSPredicate cho searchController.searchBar vvSwift UISearchController có dây trong Core Data Project, ứng dụng chạy, nhưng tìm kiếm không cập nhật

tôi thấy this post EXTREMELY helpful, nhưng sau khi mô hình của tôi TVC sau khi dự án này, tôi tìm thấy "tất cả các đèn đang ở trên, nhưng chẳng ai nhấc máy của" . Tôi có thể tìm kiếm, vị từ được tạo trong searchBar, thêm, xóa, v.v. nhưng các ô không cập nhật cho tìm kiếm. Tôi đang thiếu một cái gì đó, nhưng tôi không biết những gì.

Dưới đây là các phần có liên quan của mã của tôi.

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

Tôi nghĩ rằng sự cố của tôi "sống" ở đây trong phần này. Tự động điền đã là bạn của tôi đến đây, nhưng tôi không thấy "searchIndex" được tham chiếu trong Tự động hoàn thành. Tôi nghĩ rằng tôi đang thiếu một cái gì đó, nhưng tôi không chắc chắn những gì hoặc làm thế nào.

Nếu bạn đã thực hiện điều này từ xa, cảm ơn bạn đã đọc. Here's the GitHub repo cho chi nhánh tôi đang làm việc.

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: mỗi @pbasdf, tôi thêm các phương pháp 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

Tìm kiếm của bạnĐối tượng ủy quyền được đặt thành tự? Trong 'viewDidLoad':' searchController.delegate = self' và 'searchController.searchResultsUpdater = self' – Ian

+0

Điều đó không làm gì cả, nhưng ứng dụng không đổ vỡ khi nó chạy.Não của tôi đã chiên trong ngày, nhưng tôi sẽ đào bới và xem liệu tôi có thiếu phương pháp đại biểu ở đâu đó để giữ nó không được cập nhật. Cảm ơn bạn đã tìm kiếm. – Adrian

+1

Liên kết tới repo GitHub của bạn bị hỏng. Nhưng bạn có thể thêm các phương thức tableView dataSource (numberOfSectionsInTableview, numberOfRowsInSection, cellForRowAtIndexPath, v.v.) vào bài viết của bạn ở trên, thay vào đó. Cảm ơn. – pbasdf

Trả lời

8

Những vấn đề nằm ở phương pháp nguồn dữ liệu tableView của bạn: numberOfSectionsInTableview:, tableView:numberOfRowsInSection:tableView:cellForRowAtIndexPath:. Bạn cần mỗi phương thức đó để trả về các kết quả khác nhau nếu searchPredicate không phải là nil - giống như phương thức tableView:commitEditingStyle: của bạn. Tôi sẽ làm cho một tài sản filteredObjects dụ (được xác định vào lúc bắt đầu của lớp) để tất cả các phương pháp đó có thể truy cập vào nó:

var filteredObjects : [Note]? = nil 

Bây giờ, khi những thay đổi văn bản tìm kiếm, bạn muốn xây dựng lại các mảng filteredObjects. Vì vậy, trong updateSearchResultsForSearchController, thêm một dòng vào recompute nó dựa trên vị mới:

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) 
    } 

Tôi cũng muốn giới thiệu (vì đơn giản) mà khi bạn kích hoạt tìm kiếm, kết quả sẽ được hiển thị tất cả trong một phần (nếu không bạn có để tìm ra bao nhiêu phần kết quả lọc của bạn rơi vào - có thể nhưng mệt mỏi):

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

Tiếp theo, nếu searchPredicate không phải là con số không, số lượng hàng trong phần sẽ là đếm 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 
    } 
} 

Cuối cùng, nếu searchPredicate không phải là con số không, bạn cần phải xây dựng các tế bào bằng cách sử dụng dữ liệu filteredObjects, chứ không phải là 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 
    } 
} 

Không chắc những gì nhãn vv bạn có trong các tế bào của bạn, vì vậy tôi rời khỏi nó để bạn sắp xếp bit đó.

+0

Whoa! Tôi đã cố gắng để làm điều này kể từ thứ bảy (tôi bắt đầu iOS 1 tháng trước). Cảm ơn bạn rất nhiều! Đối tượng My Note có một 'noteTitle' &' noteBody', không có ô đặc biệt nào. Tôi nghĩ rằng tôi cần phải thêm một phương thức đại biểu hoặc hai để đặt lại searchPredicate thành 'nil' và tôi nghĩ mọi thứ sẽ trở lại bình thường nếu biến vị ngữ là' nil'. – Adrian

+1

Có, sử dụng phương thức ủy nhiệm 'didDismissSearchController' của searchController để đặt biến vị ngữ thành nil và tải lại TV. – pbasdf

+0

Đúng! Tôi đặt lại 'searchPredicate' và' filtersObjects' thành 'nil' và thêm' self.tableView.reloadData() 'vào cuối' didDismissSearchController'. Bây giờ tôi đang vật lộn với việc hiển thị kết quả chính xác. Trong giao diện điều khiển, 'po lọcItems' in ra những thứ đúng, nhưng điều đó không được phản ánh trên màn hình. Cảm ơn bạn rất nhiều! Tôi đang học rất nhiều ở đây. – Adrian

Các vấn đề liên quan