Swift UISearchController wired up in Core Data Project, app runs, but search not updating
The problems lie in your tableView datasource methods: numberOfSectionsInTableview:
, tableView:numberOfRowsInSection:
, and tableView:cellForRowAtIndexPath:
. You need each of those methods to return different results if the searchPredicate is not nil - much like your tableView:commitEditingStyle:
method does. I would make filteredObjects
an instance property (defined at the start of the class) so that all those methods can access it:
var filteredObjects : [Note]? = nil
Now, when the search text changes, you want to rebuild the filteredObjects array. So in updateSearchResultsForSearchController
, add a line to recompute it based on the new predicate:
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)
}
I would also recommend (for simplicity) that when you activate the search, the results are displayed all in one section (otherwise you have to work out how many sections your filtered results fall into - possible but tiresome):
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if searchPredicate == nil {
return self.fetchedResultsController.sections?.count ?? 0
} else {
return 1
}
}
Next, if the searchPredicate is not nil, the number of rows in the section will be the count of 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
}
}
Finally, if the searchPredicate is not nil, you need to build the cell using the filteredObjects data, rather than the 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
}
}
Not sure what labels etc you have in your cells, so I leave it to you to sort that bit.
Deleting from UISearchController's filtered search results
The problem arises because of a mismatch between the indexPath used by the fetched results controller and the indexPath for the corresponding row in the tableView.
Whilst the search controller is active, the existing tableView is reused to display the search results. Hence your logic to differentiate the two tableViews:
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsUpdater as LocationViewController).tableView
}
is unnecessary. It works, because you set searchController.searchResultsUpdater = self
when you initialise the searchController, so there is no need to change it, but the same tableView is used in either case.
The difference lies in the way the tableView is populated whilst the searchController is active. In that case, it looks (from the numberOfRowsInSection
code) as though the filtered results are all displayed in one section. (I assume cellForRowAtIndexPath
works similarly.) Suppose you delete the item at section 0, row 7, in the filtered results. Then commitEditingStyle
will be called with indexPath 0-7
, and the following line:
let location: Location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location
will try to get the object at index 0-7 from the FRC. But the item at index 0-7 of the FRC might be a completely different object. Hence you delete the wrong object. Then the FRC delegate methods fire, and tell the tableView to delete the row at index 0-7. Now, if the object really deleted was NOT in the filtered results, then the count of rows will be unchanged, even though a row has been deleted: hence the error.
So, to fix it, amend your commitEditingStyle
so that it finds the correct object to delete, if the searchController is active:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
var location : Location
if searchPredicate == nil {
location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location
} else {
let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
}
location = filteredObjects![indexPath.row] as Location
}
location.removePhotoFile()
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(location)
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
}
I haven't been able to test the above; apologies if some errors slipped in. But it should at least point in the right direction; hope it helps. Note that similar changes may be required in some of the other tableView delegate/datasource methods.
Turning objects filtered from managedObjectContext into an NSArray
The managed object context has nothing to do with an array. It cannot be "filled" with objects. It is simply an abstract class that gives you access to your data.
The way you get objects from your Core Data store is as follows: make a fetch request, optionally add filter and/or sorting to the request, fetch the objects.
let request = NSFetchRequest(entityName:"Note")
let result = managedObjectContext.executeFetchRequest(request error:nil) as [Note]
You can now either filter this array (with filteredArrayUsingPredicate
after casting to NSArray) or you can add your predicate to the original fetch request (which should even be more efficient).
How to change viewController based on device orientation
The reason for those warnings or errors is that there is a delay when dismissing and presenting view controllers and your calls to present and dismiss are overlapping with each other.
The first error reporting that the view is not in the hierarchy means that you are attempting to present a view on your original view controller or navigation controller that is no longer available because another view controller has been modally presented.
The second error regarding loading a view while it is deallocating indicates that the view controller dismissal has not fully completed when another modal presentation is being attempted.
To prevent these problems requires ensuring the dismissals are complete before presenting a new view controller. This can be accomplished by handling the states using the completion handler in the dismiss methods and adding a check on the isBeingDismissed
state.
Here is some code that accomplishes that:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation) {
let landscapeVC = storyboard.instantiateViewControllerWithIdentifier("landscapeViewController") as! beerCollectionViewController
if presentedViewController != nil {
if !presentedViewController!.isBeingDismissed() {
dismissViewControllerAnimated(false, completion: {
self.presentViewController(landscapeVC!, animated: true, completion: nil)
})
}
} else {
self.presentViewController(landscapeVC!, animated: true, completion: nil)
}
} else { // Portrait
let portraitVC = storyboard.instantiateViewControllerWithIdentifier("portraitViewController") as! ViewController
if presentedViewController != nil {
if !presentedViewController!.isBeingDismissed() {
dismissViewControllerAnimated(false, completion: {
self.presentViewController(portraitVC!, animated: true, completion: nil)
})
}
} else {
self.presentViewController(portraitVC!, animated: true, completion: nil)
}
}
}
Related Topics
How to Add "%" to Data in iOS-Chart
How to Record Video in Realitykit
Get Rawvalue from Enum in a Generic Function
Why Is There a Memory Leak at String Creation in Swift
How to Get the Download Progress with the New Try Await Urlsession.Shared.Download(...)
iOS Screen Sharing (Using Replaykit) Using Webrtc in Swift
How to Play Sound with Avaudiopcmbuffer
Sending Whatsapp Message to a Specific Contact Number (Swift Project)
How to Remove Multiple Items from a Swift Array
Nsuserdefaults in Swift - Implementing Type Safety
Swiftui MACos Nswindow Instance
Collection View Cell Button Not Triggering Action