UITableView filtering
You should create a "Model" for the Person (using MVC pattern):
First, create "Person" Model:
struct Person {
var name: String?
var imageName: String?
}
instead of using two separated arrays for storing the persons's data, you can create an array of Person Model:
// add those vars to your ViewController:
var persons = [Person]()
var filteredPersons = [Person]()
var isFiltering = false
override func viewDidLoad() {
super.viewDidLoad()
persons = [Person(name: "Ahmad", imageName: "img.png"), Person(name: "Harry", imageName: "img.png")]
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if (searchText.characters.count>0) {
isFiltering = true
filteredPersons = persons.filter {
$0.name?.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
}
print(filteredPersons)
}
else
{
isFiltering = false
filteredPersons = persons
}
self.tableviewww.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return isFiltering == true ? filteredPersons.count : persons.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
// getting the current person
let currentPerson = isFiltering == true ? filteredPersons[indexPath.row] : persons[indexPath.row]
// do the rest of the implementation...
//...
}
Note that this is Swift 3 Code.
Performing segue after filtering in uitableview
This is happening because you are using shoutout
Array in prepare(for segue
even when filtering.
destination.shoutoutSelected = shoutout[(tableView.indexPathForSelectedRow?.row)!]
Change it to
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "shoutoutDetailSegue" {
if let destination = segue.destination as? shoutoutDetailViewController {
// Here `Model` should be your class or struct
destination.shoutoutSelected = sender as! Model
}
}
OR move your logic here from didSelectRowAt
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "shoutoutDetailSegue" {
if let destination = segue.destination as? shoutoutDetailViewController {
if isFiltering == true {
object = filteredShoutout[indexPath.row]
} else {
object = shoutout[indexPath.row]
}
destination.shoutoutSelected = object
}
}
Filtering Array and displaying in Table View?
Your filtered
also type of [Exercise]
and you need to filter it like.
var filtered = [Exercise]()
self.filtered = exercises.filter { $0.name == searchText }
Here $0
is type of Exercise
object, so you need to access its property name using $0.name
.
Edit: If you want filtered
as type of [String]
with only name then you can need to use both map
and filter
like this.
self.filtered = exercises.filter { $0.name == searchText }.map { $0.name }
OR
self.filtered = exercises.map { $0.name }.filter { $0 == searchText }
OR directly using filterMap
as @dfri suggested.
self.filtered = exercises.flatMap{ $0.name == searchText ? $0.name : nil }
how to filter section wise row in tableview?
To search data inside the model you can use the Filter function.
Same way as you are showing data inside tableview cell. You can search using the filter function and show data inside based on the search flag.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard let searchText = searchText, !searchText.isEmpty else {
return
}
searchData = cat?.category.map({
$0.skill.filter({$0.vName == searchText})
}).filter({ $0.count > 0 })
searching = true
self.searchedSkillsResults = searchData
tableView.reloadData()
}
//Check here if your search is true and show skills array else original array results with search false.
reference link: how to search data from model in swift?
//below link help you to search within nested array.
Swift Filter Nested Array
My sample code which is working:
import Foundation
struct Welcome: Codable {
let category: [Category]
}
struct Category: Codable {
let vName: String
let skill: [Skill]
enum CodingKeys: String, CodingKey {
case vName,skill
}
}
struct Skill: Codable {
let vName: String
enum CodingKeys: String, CodingKey {
case vName
}
}
var cat : Welcome?
private func parseJSON(){
guard let path = Bundle.main.path(forResource: "category", ofType:
"json")else{
return
}
let url = URL(fileURLWithPath: path)
print(url)
do {
let jsonData = try Data(contentsOf: url)
cat = try JSONDecoder().decode(Welcome.self, from: jsonData)
if let result = cat {
print(result)
}
} catch {
print("Error is \(error)")
}
}
parseJSON()
//print(cat)
let searchText = "Section 0 Row 1"
let result = cat?.category.map({
$0.skill.filter({$0.vName.lowercased().contains(searchText.lowercased())})
}).filter({ $0.count > 0 })
//output = Optional([[Skill(vName: "Section 0 Row 1")]])
How to preserve the original indexPath.row after applying filters to Table View?
Thank you all for the comments/advice! Instead of connecting the data in the view controllers through the indexPath, I used a document ID that is consistent with the data flowing between my view controllers. This works with all of my filtering.
This is in my first ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow {
let ingredientsVC = segue.destination as! IngredientsViewController
let documentID = mealPlan[indexPath.row].docID
ingredientsVC.currentMealPlanIndex = indexPath.row
ingredientsVC.passedDocID = documentID!
}
}
And this is in my second ViewController:
// This variable references the unique Document ID
var passedDocID = ""
// This is how I use that document ID to get a reference to the appropriate data
let selectedMealPlanIndex = mealPlan.firstIndex(where: {$0.docID == passedDocID})
let currentMealPlan = mealPlan[selectedMealPlanIndex!]
TableView how to filter certain cells from the table
Problem - With UITableView
, the method which is fired first is numberOfRowsInSection
this will tell the TableView how many cell are there in the TableView
, after getting the numbers the method cellForRowAtIndexPath
will get fired which will used to design your TableView
.
Now you are returning a count lets say 10 in numberOfRowsInSection
out of which you just want to show 5 cell lets say those 5 are meal.veg == true
, based on your filter meal.veg which is not possible as you need to return a cell from cellForRowAtIndexPath
.
Solution - To resolve it, before reloading your table view you need to filter your array and filter out those results which are having a value meal.veg == true
, then you need to pass the count of your filtered array in numberOfRowsInSection
and as per that filtered array you can design your cell from cellForRowAtIndexPath
Is it better to hide a tableviewcell or filter in data source? (performance issue)
I would recommend to let the table view data source methods to deal with a filtered version of timeline
. However, do not do this in cellForRowAt
method because we need to do it one time but not for each cell drawing.
So, what you could do is to declare filteredTimeline
and do the filter one time in the viewDidLoad
method (for instance):
class TableViewController: UIViewController {
// ...
var filteredTimeline // as the same type of `timeline`
override func viewDidLoad() {
// ...
filteredTimeline = timeline?.postObjects?.filter{!($0.hidden ?? false)}
// ...
}
// Returning only the number of visible cells
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredTimeline.count ?? 0
}
// And creating cells for only visible rows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = filteredTimeline?.postObjects?.filter{!($0.hidden ?? false)}[indexPath.row] {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
// ...
}
In case of there is a better place to filteredTimeline = timeline?.postObjects?.filter{!($0.hidden ?? false)}
rather than viewDidLoad
, you might need to call tableView.reloadData()
.
An alternative you could do:
if you think that you don't need the original timeline
you could filter it itself:
timeline = timeline?.postObjects?.filter{!($0.hidden ?? false)}
tableView.reloadData()
and you will not need an extra filtered array.
Extra tip:
In case of returning 0.0
value in heightForRowAt
method for a certain row, cellForRowAt
will not even get called; For example:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.row == 0 ?? 0.0 : 100.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ...
}
At this point, cellForRowAt
should get called only one time because the height for the first row is 0.0
.
Related Topics
Swift - Add Same Navigation Bar Items to Every Page
CSS Flexible Box Layout on Ipad
How to Connect Xcode 9 and Github
How to Check a App Was Installed or Updated
Nscoding Required Initializer in Inherited Classes in Swift
Populating Input Text Field in Wkwebview
My App Is Getting Crashed on UIdocumentpickerviewcontroller
Customize Google Maps Blue Dot for Current Location
How Is a Swift Cgvector Created with Dx and Dy (Derivative)
Uicollectionviewcompositionallayout - Center Items in Sections or Groups
Uidocumentinteractioncontroller Does Not Open Other App in iOS 11
Terminating App Due to Uncaught Exception 'Nsinvalidargumentexception' - iOS Google Sign In
How to Get a Low Res Image, or Thumbnail from the Alassetrepresentation in Swift
Sorting Nsarray Containing Nsdate Objects
Firebase Database Not Equal Request - Alternative Solution (For iOS)