I have list of product items to display in my table view. At the same time i have i have some other api call, Where i will pass my prodcut item name to check. If that product item is available then that particular data or item cell alone will be highlighted and it will be disabled.
Now what i need is, when i do api call, and after that if that particular data or product name is available in that api, instead of highlight and disable... I should not show that particular data in my table view.
How to do that:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! AllConnectionsTableViewCell
if let contact = filtered?[indexPath.row]{
cell.emailOutlet.text = AccountDataCache.sharedInstance.displayMaskAccnt(items: product.name)
cell.nameOutlet.text = product.name
if let _ = self.checkapicall(items: product.name){
// here if my product name is availble in api, then only the backgroudnd and it will be disabled
if let product = filtered?[indexPath.row]{
cell.namelabel.text = product.name
if let _ = self.checkapicall(items: product.vpa){
cell.cellOuterView.backgroundColor = UIColor.red
cell.isUserInteractionEnabled = false
}else{
cell.cellOuterView.backgroundColor = UIColor.white
cell.isUserInteractionEnabled = true
}
}
}
Instead of chnaging BG, Disable..i should not show that data in that tableview cell.How to do that.?
Thanks
As you described, if your data looks like this:
name1, name2,name3, name4
Then you want to show four rows in your tableView.
If name2 is available in your API call then you want to show this:
name1, name3, name4
So what you need to do is to get all the names before you start updating the tableView. This is because you need to set how many rows you want to display in your tableView.
You could do something like this (I´m not sure how you fetch your data today, but this is an example to get you started):
// check add edit to your product
var products = [Product(name: "name1", vpa: "1"), Product(name: "name2", vpa: "2"), Product(name: "name3", vpa: "3"), Product(name: "name4", vpa: "4")]
// set the produts count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return products.count
}
// just set the name here, don´t make any checks
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StartPageCell", for: indexPath)
cell.namelabel.text = product.name
return cell
}
// check the names here and then reload the tableView
func checkNames() {
for product in products {
if self.checkapicall(items: product.vpa){ {
if let index = products.index(where: { $0.vpa == vpa }) {
products.remove(at: index)
}
}
}
tableView.reloadData()
}
First of all don't make such checks in cellForRow, make it in viewDidLoad or viewWillAppear
Add a property var isAvailable = false to your product model
In viewDidLoad or viewWillAppear check the availability of the products and set isAvailable accordingly.
Create the data source array var filtered = [Product]() (assuming Product is the data model) and filter the items filtered = allItems.filter { $0.isAvailable }
Reload the table
Related
I have a data source in this form:
struct Country {
let name: String
}
The other properties won't come into play in this stage so let's keep it simple.
I have separated ViewController and TableViewDataSource in two separate files. Here is the Data source code:
class CountryDataSource: NSObject, UITableViewDataSource {
var countries = [Country]()
var filteredCountries = [Country]()
var dataChanged: (() -> Void)?
var tableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
var filterText: String? {
didSet {
filteredCountries = countries.matching(filterText)
self.dataChanged?()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredCountries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let country: Country
country = filteredCountries[indexPath.row]
cell.textLabel?.text = country.name
return cell
}
}
As you can see there is already a filtering mechanism in place.
Here is the most relevant part of the view controller:
class ViewController: UITableViewController, URLSessionDataDelegate {
let dataSource = CountryDataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.tableView = self.tableView
dataSource.dataChanged = { [weak self] in
self?.tableView.reloadData()
}
tableView.dataSource = dataSource
// Setup the Search Controller
dataSource.searchController.searchResultsUpdater = self
dataSource.searchController.obscuresBackgroundDuringPresentation = false
dataSource.searchController.searchBar.placeholder = "Search countries..."
navigationItem.searchController = dataSource.searchController
definesPresentationContext = true
performSelector(inBackground: #selector(loadCountries), with: nil)
}
The loadCountries is what fetches the JSON and load the table view inside the dataSource.countries and dataSource.filteredCountries array.
Now, how can I get the indexed collation like the Contacts app has without breaking all this?
I tried several tutorials, no one worked because they were needing a class data model or everything inside the view controller.
All solutions tried either crash (worst case) or don't load the correct data or don't recognise it...
Please I need some help here.
Thank you
I recommend you to work with CellViewModels instead of model data.
Steps:
1) Create an array per word with your cell view models sorted alphabetically. If you have data for A, C, F, L, Y and Z you are going to have 6 arrays with cell view models. I'm going to call them as "sectionArray".
2) Create another array and add the sectionArrays sorted alphabetically, the "cellModelsData". So, The cellModelsData is an array of sectionArrays.
3) On numberOfSections return the count of cellModelsData.
4) On numberOfRowsInSection get the sectionArray inside the cellModelsData according to the section number (cellModelsData[section]) and return the count of that sectionArray.
5) On cellForRowAtindexPath get the sectionArray (cellModelsData[indexPath.section]) and then get the "cellModel" (sectionArray[indexPath.row]). Dequeue the cell and set the cell model to the cell.
I think that this approach should resolve your problem.
I made a sample project in BitBucket that could help you: https://bitbucket.org/gastonmontes/reutilizablecellssampleproject
Example:
You have the following words:
Does.
Any.
Visa.
Count.
Refused.
Add.
Country.
1)
SectionArrayA: [Add, Any]
SectionArrayC: [Count, Country]
SectionArrayR: [Refused]
SectionArrayV: [Visa]
2)
cellModelsData = [ [SectionArrayA], [SectionArrayC], [SectionArrayR], [SectionArrayV] ]
3)
func numberOfSections(in tableView: UITableView) -> Int {
return self.cellModelsData.count
}
4)
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionModels = self.cellModelsData[section]
return sectionModels.count
}
5)
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionModels = self.cellModelsData[indexPath.section]
let cellModel = sectionModels[indexPath.row]
let cell = self.sampleCellsTableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier",
for: indexPath) as! YourCell
cell.cellSetModel(cellModel)
return cell
}
My scenario, I have loaded my JSON data into tableView with help of codable. Here, I have added my tableView cell multiple check mark select and deselect. Now, If I am selecting tableView cell I can able to get cell data but I want to add within one array, same if I am unselecting cell It should remove from the array. Selected cell data I am moving to another ViewController. I would like to know how to do that.
My Code
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
if cell.accessoryType == .checkmark {
cell.accessoryType = .none
} else {
cell.accessoryType = .checkmark
let item = users[indexPath.row]
print(item) // here printing cell selection data
}
}
}
My Cell Selection current output
You tapped cell number 1.
User(userId: "121”, active: 1, name: example_table.Name(firstname: "jack", lastname: "m"))
You tapped cell number 2.
User(userId: "122”, active: 1, name: example_table.Name(firstname: “rose”, lastname: “h”))
You tapped cell number 3.
User(userId: "123”, active: 1, name: example_table.Name(firstname: “makj”, lastname: “i”))
So it sounds like you want to be able to have an array that contains all of the selected Users. Then what you would do is have an array like this instantiated in the class declaration:
var users:[Users] = []
Now what you should be doing is leveraging swift's protocols in order to handle the heavy lifting for you; meaning, when removing a previously selected user from the Users array, you shouldn't need a for loop, but something more familiar: contains.
extension User: Equatable {
static func == (lhs: User, rhs: User) -> Bool {
return lhs.userId == rhs.userId
}
}
So now you can call this when removing or adding, for example:
var thisUser = User(userId: "123”, active: 1, name: example_table.Name(firstname: “makj”, lastname: “i”))
if users.contains(thisUser) {
users.removeAtIndex(users.indexOf(thisUser))
} else {
//Add the user to the array here for example
users.append(thisUser)
}
Don't use an extra array, add the selected information to your model.
struct User : Codable {
var isSelected = false
// ... other members
}
Assuming the data source array is declared as users, set the checkmark depending on the isSelected value in cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let user = users[indexPath.row]
cell.accessoryType = user.isSelected ? .checkmark : .none
...
}
In didSelectRowAt just toggle isSelected and reload the row
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
users[indexPath.row].isSelected.toggle()
tableView.reloadRows(at: [indexPath], with: .none)
}
To get all selected users just filter the array
let selectedUsers = users.filter{ $0.isSelected }
You can try
var selectedArr = [Item]()
var users = [Item]()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
let item = users[indexPath.row]
if let cell = tableView.cellForRow(at: indexPath) { // you can also omit the if let and force-unwrap as in this case cell will never be nil
if cell.accessoryType == .checkmark {
cell.accessoryType = .none
selectedArr.remove(where:{ $0 == item })
} else {
cell.accessoryType = .checkmark
selectedArr.append(item)
}
}
}
Then inside cellForRowAt
let cell = ////
let item = users[indexPath.row]
cell.accessoryType = selectedArr.contains(item) ? .checkmark : .none
Also make sure model named Item conforms to Equatable
given
for (index, element) in item.enumerated() {
print("Item \(index): \(element)")
}
that gives
Item 0: 121, Item 1: 122, Item 2: 123
Then it's an array of Ints , so do
let res = item.map{ "\($0)" }.joined(separator: ",")
I am fetching previously selected categorylist from the server. say for an example.cateogrylist i fetched from the server was in following formate
categoryid : 2,6,12,17
now what i need to do is want to enable checkmark in my tableview based on this categorylist,for that purpose i converted this list into an [Int] array like this :
func get_numbers(stringtext:String) -> [Int] {
let StringRecordedArr = stringtext.components(separatedBy: ",")
return StringRecordedArr.map { Int($0)!}
}
in viewDidLoad() :
selectedCells = self.get_numbers(stringtext: UpdateMedicalReportDetailsViewController.catId)
print(myselection)
while printing it's giving me results like this : [12,17,6,8,10]
i want to enable checkimage based on this array.I tried some code while printing its giving me the right result like whatever the categories i selected at the time of posting ,i am able to fetch it but failed to place back this selection in tableview.Requirement : while i open this page it should show me the selection based on the categorylist i fetched from the server.
var selectedCells : [Int] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell1 = table.dequeueReusableCell(withIdentifier: "mycell") as! catcell
cell1.mytext.text = categoriesName[indexPath.row]
if UpdateMedicalReportDetailsViewController.flag == 1
{
selectedCells = self.get_numbers(stringtext: UpdateMedicalReportDetailsViewController.catId)
cell1.checkimage.image = another
print(selectedCells)
}
else
{
selectedCells = []
cell1.checkimage.image = myimage
}
return cell1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = table.cellForRow(at: indexPath) as! catcell
cell.checkimage.image = myimage
if cell.isSelected == true
{
self.selectedCells.append(indexPath.row)
cell.checkimage.image = another
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = table.cellForRow(at: indexPath) as! catcell
if cell.isSelected == false
{
self.selectedCells.remove(at: self.selectedCells.index(of: indexPath.row)!)
cell.checkimage.image = myimage
}
}
output :
This is a very common use case in most apps. I'm assuming you have an array of all categories, and then an array of selected categories. What you need to do is in cellForRowAtIndexPath, check to see if the current index path row's corresponding category in the "all categories" array is also present in the "selected categories" array. You can do this by comparing id's etc.
If you have a match, then you know that the cell needs to be selected/checked. A clean way to do this is give your cell subclass a custom load method and you can pass a flag for selected/checked.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCell(withIdentifier: "mycell") as! catcell
let category = self.categories[indexPath.row] // Let's say category is a string "hello"
Bool selected = self.selectedCategories.contains(category)
cell.load(category, selected)
return cell
}
So with the code above, let's say that categories is just an array of category strings like hello, world, and stackoverflow. We check to see if the selectedCategories array contains the current cell/row's category word.
Let's say that the cell we're setting up has a category of hello, and selectedCategories does contain it. That means the selected bool gets set to true.
We then pass the category and selected values into the cell subclass' load method, and inside that load method you can set the cell's title text to the category and you can check if selected is true or false and if it's true you can display the checked box UI.
I have a search function in my app which gets data from a fetchedResultsController. The problem is when I display the filtered data in the tableView everytime it gets to a new section, the array starts over and over (due to indexPath.row being 0 everytime indexPath.section increments). I've never been good at nested arrays and I thought this is the perfect time to learn them, since I can't get over my problem without this array.
So I have this array which is the filtered data out of the fetchedResultsController:
filteredItems = (fetchedResultsController.fetchedObjects?.filter({(budget : Budget) -> Bool in
return (budget.dataDescription?.lowercased().contains(searchText.lowercased()))!
}))!
How can I make an array called filteredObjects which sorts my items for sections? For example
- Section 1 ( filteredObjects[0] ):
item 1 ( filteredObjects[0][1] )
item 2 ( filteredObjects[0][2] )
-
Section 2 ( filteredObjects[1] ):
item 1 ( filteredObjects[1][0] )
etc
Use below method to define number of sections
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return filteredObjects.count
}
And, for number of rows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredObjects[section].count
}
And Finally, for CellForRowAtIndexpath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TaskTableViewCell
let currentItem = filteredObjects[indexPath.section][indexPath.row]
... Here, use currentItem as whatever it is (Object or dictionary)
return cell
}
let budgets = [Budget]()
let searchResult = [Budget]()
You should always use searchResult array in tableview datasource methods. initially you should add all objects of budgets array to searchResult & load table view.
when search started remove all items from searchResult and add filtered result to search result array & reload tableview.
func search(searchText:String){
searchResult.removeAll()
let result = budgets.filter({
var budget = $0
let filtered = budget.expenses.filter({
if let deptName = $0.deptName{
return deptName.lowercased().contains(searchText.lowercased())
}
return false
})
budget.expenses = filtered
return budget.expenses.count != 0
})
searchResult.append(contentsOf: result)
}
struct Budget {
var expenses = [Expense]()
}
struct Expense{
var deptName:String?
}
I have a one view app with embedded UITableView that displays a list of "stores"(Realm object). By default I populate the table view of all the Store objects. IF the user wants to then narrow the results they can do so by using any combination of text fields in MasterVC. When they hit search - simply update TableView with 'filtered' Realm objects.
What works:
Populate UITableView with objects from the Realm.
Create new Realm entries via text field entries in MasterVC and repopulate table in ResultsVC.
Swipe to delete object on table / and Realm object.
What sort of works:
If user enters a search term then 'filter' the Realm object (Stores) and repopulate the table. This correctly reloads and returns the number of results. However the First Cell (0) of the TableView is always the exact same and never updates.. If there are 20 returned results in the search then Rows 1-18 are correctly displayed. Row 0 is static and never changes its text. Any obvious reasons why?
Results Table View Controller
class ResultsVC: UITableViewController {
// data source
var stores: Results<Store> = {
let realm = try! Realm()
return realm.objects(Store.self)
}()
var token: NotificationToken?
...
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stores.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self)
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}
}
Here is how I'm accessing the ResultsVC from MasterVC
Master View Controller
class MasterViewController: UIViewController {
...
#IBAction func searchDatabase(_ sender: Any) {
let CVC = childViewControllers.first as! UINavigationController
let resultVC = CVC.viewControllers[0] as? ResultsVC
result.stores = stores.filter("address = '1234 Blue Street'")
result.tableView.reloadData()
}
...
}
Turns out I had a duplicate variable which was overwriting the orig from above.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self) // <- OVERWRITING ORIGINAL //
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}