tableview doesn't refresh deleted data - ios

My table view does not refresh. it keep the old values of deleted data
and can show new added values.
it only refresh(remove deleted values) when i close the app and open it again.
here is my code
private func observeChannels() {
let userEmail = Auth.auth().currentUser?.email
channelRefHandle = channelRef.observe(.childChanged, with: { (snapshot) -> Void in
let channelData = snapshot.value as! Dictionary<String, AnyObject>
let id = snapshot.key
let groupimage = channelData["groupImage"] as? String!
let descc = channelData["desc"] as? String!
let groupCountvar = channelData["Members"]?.count
let groupTasksVar = channelData["Tasks"]?.count
if let name = channelData["name"] as! String!, name.characters.count > 0 {
//members snapshot
if let childSnapshot = snapshot.childSnapshot(forPath: "Members") as? DataSnapshot{
if let membersDictionary = childSnapshot.value as? [String:AnyObject] , membersDictionary.count > 0{
for membersDictionary in membersDictionary {
if userEmail == membersDictionary.value as? String {
self.groups.append(Group(id: id,name: name, createdBy: (userEmail)!, desc: (descc)!, groupImage: (groupimage)!, groupCount: ("\(groupCountvar!)") , groupTasksCount: ("\(groupCountvar!)")))
print(membersDictionary.value)
}
}
}
self.tableView.reloadData()
}}else {
print("Error!")
self.tableView.reloadData()
}
})}
datasource / delegate
override func numberOfSections(in tableView: UITableView) -> Int {
return 2 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let currentSection: Section = Section(rawValue: section) {
switch currentSection {
case .createNewChannelSection:
return 0
case .currentChannelsSection:
return groups.count
}
} else {
return 0 }
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75.0 }
//tableView
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExistingChannel", for: indexPath) as! GroupTableViewCell
if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
cell.goupName?.text = groups[(indexPath as NSIndexPath).row].name
cell.groupDesc?.text = groups[(indexPath as NSIndexPath).row].desc
cell.groupimg?.image = UIImage(named: groups[(indexPath as NSIndexPath).row].groupImage)
cell.numbMembLbl?.text = groups[(indexPath as NSIndexPath).row].groupCount
cell.taskNumbLbl?.text = groups[(indexPath as NSIndexPath).row].groupTasksCount
}
return cell }
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
let channel = groups[(indexPath as NSIndexPath).row]
self.performSegue(withIdentifier: "ShowChannel", sender: channel)
} }
this is my table view i dont know why the deleted/removed doesn't observe.

You are observing the node for .childChanged events only.
That means the code in the closure will only fire if a child is changed.
From the documentation
FIRDataEventTypeChildChanged - Listen for changes to the items in a
list. This event is triggered any time a child node is modified. This
includes any modifications to descendants of the child node. The
snapshot passed to the event listener contains the updated data for
the child.
So that means the code in your closure will only be called when a child is changed, and will not be called when a child is added or deleted.
You need to add additional observers for .childAdded events and .childRemoved events to handle those cases. When you receive a childAdded event, add that snapshot data to your array and likewise when you receive a childRemoved event, remove it from the array.
Remember to reload your table view in all cases so the UI updates with the fresh data.
I personally would change this line to make it more readable
for membersDictionary in membersDictionary {
to
for member in membersDictionary {
but that's just a style thing.

Try using this for reloading tableView
DispatchQueue.main.async {
self.tableView.reloadData()
}
This will help.

you need this :
func tableView(_ tableView: UITableView, commit editingStyle:
UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("Deleted")
self.yourArrayName.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
It'll do the job.

Related

How to optimize loading from Firestore to Tableview

My View Controller has a Tableview with 2 segments. Depending on which Segment is selected, the Tableview displays a different set of data.
#IBAction func didChangeSegment(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
model.getRecipes(starredTrue: false)
}
else if sender.selectedSegmentIndex == 1 {
model.getRecipes(userAdded: true)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recipe.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
let recipeInTable = recipe[indexPath.row]
cell.displayRecipe(recipe: recipeInTable, indexPathRow: indexPath.row)
return cell
}
And this is how model.getRecipes() gets data from Firestore before returning it to the Tableview:
let recipeQuery = db.collection("recipes")
let docRef = recipeQuery.document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.recipe = try document.data(as: Recipe.self)
let recipeFromFirestore = Recipe(
id: documentId,
title: self.recipe!.title ?? "")
self.recipes.append(recipeFromFirestore)
}
catch {
}
}
DispatchQueue.main.async {
self.delegateRecipes?.recipesRetrieved(recipes: self.recipes)
}
}
}
The issue I'm having is that the Tableview takes a very long time to display data. It appears this is because it has to wait for the model to finish loading all the data from Firestore every time I select one of the segments.
How can I optimize this process? Is it possible to have the TableView load/display cell by cell, instead of needing to wait for all data to be loaded?
Any guidance is much appreciated!

Deleted Table View Cell Copying Remaining Tableview Cell Instead of Disappearing

I've got a UITableView that pulls some information from a Firebase Realtime Database. The information all gets pulled and populated properly, and deletes when it is supposed to, but I get a weird bug. If there are multiple cells in the table view, when I delete one of the cells, instead of that cell disappearing, it just gets replaced with a copy of one of the remaining cells. When I then close the table view and reopen it, everything is correct (i.e. the copied cell is gone). I have included some photos of this below as an illustration. Here is the swift file for the table view:
import UIKit
import Firebase
import FirebaseAuth
class SpotRemove: ViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tblSpots: UITableView!
var spotsList = [ArtistModel]()
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return spotsList.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let spot: ArtistModel
spot = spotsList[indexPath.row]
cell.lblName.text = spot.type
cell.lblGenre.text = spot.avail
cell.lblPrice.text = spot.price
return cell
}
var refSpots: DatabaseReference?
override func viewDidLoad() {
super.viewDidLoad()
tblSpots.allowsMultipleSelectionDuringEditing = true
refSpots = Database.database().reference().child("Spots")
refSpots?.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0{
for spots in snapshot.children.allObjects as![DataSnapshot]{
let spotKey = spots.key
let spotObject = spots.value as? [String: AnyObject]
let spotType = spotObject?["Type"]
let spotAvail = spotObject?["Availability"]
let spotPrice = spotObject?["Price"]
let spotID = spotObject?["UserID"]
let spot = ArtistModel(id: spotID as! String?, avail: spotAvail as! String?, type: spotType as! String?, price: spotPrice as! String?, key: spotKey as! String?)
let userID = Auth.auth().currentUser?.uid
if userID == spotID as? String {
self.spotsList.append(spot)
}
}
self.tblSpots.reloadData()
}
})
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let spot = self.spotsList[indexPath.row]
if let spotKey = spot.key {
Database.database().reference().child("Spots").child(spotKey).removeValue { (error, ref) in
if error != nil {
print("Failed to delete message:", error!)
return
}
self.spotsList.remove(at: indexPath.row)
self.tblSpots.deleteRows(at: [indexPath], with: .automatic)
self.tblSpots.reloadData()
}
tblSpots.reloadData()
}
}
}
I believe the issue is that I'm calling reloadData() without changing numberOfRowsInSection. Would the fix be as simple as putting something like numberOfRowsInSection = numberOfRowsInSection - 1 right before I call reload data? Thank you all in advance.
the two original cells
the two cells after deletion
The reloaded table view showing only the remaining cell (as it is supposed to)
It's a round-about, brute force way, but I decided to have the array and the table view clear all contents then reload them from scratch. This works (but is not perfect for optimizing efficiency). The whole delete function now looks like this:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let spot = self.spotsList[indexPath.row]
if let spotKey = spot.key {
Database.database().reference().child("Spots").child(spotKey).removeValue { (error, ref) in
if error != nil {
print("Failed to delete message:", error!)
return
}
self.spotsList.removeAll()
self.tblSpots.reloadData()
self.refSpots = Database.database().reference().child("Spots")
self.refSpots?.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0{
for spots in snapshot.children.allObjects as![DataSnapshot]{
let spotKey = spots.key
let spotObject = spots.value as? [String: AnyObject]
let spotType = spotObject?["Type"]
let spotAvail = spotObject?["Availability"]
let spotPrice = spotObject?["Price"]
let spotID = spotObject?["UserID"]
let spot = ArtistModel(id: spotID as! String?, avail: spotAvail as! String?, type: spotType as! String?, price: spotPrice as! String?, key: spotKey as! String?)
let userID = Auth.auth().currentUser?.uid
if userID == spotID as? String {
self.spotsList.append(spot)
}
}
self.tblSpots.reloadData()
}
})
}
}
}

Reload TableView After Deleting, Adding, or Modifying Firestore Document and Paginating Results

I am retrieving documents from Firebase Firestore and displaying them in a table view. From the table view I want to be able to delete and add items. I also modify documents from the item detail view. I'll focus on my issues deleting items for this question though. I'm getting paginated results with my query by using the last snapshot to only get the next set of items. I'm also using a listener to get realtime updates for when items are modified. The issue with deleting is how to I handle it correctly? What I currently have deletes items just fine but then doubles the remaining rows in the table view.
var items = [Item]()
var itemQuery: Query?
var lastSnapshot: QueryDocumentSnapshot?
func getItems() {
if lastSnapshot == nil {
itemQuery = Firestore.firestore().collection("items").whereField("collection", isEqualTo: self.collection!.id).order(by: "name").limit(to: 25)
} else {
itemQuery = itemQuery?.start(afterDocument: lastSnapshot!)
}
itemQuery!.addSnapshotListener( { (snapshot, error) in
guard let snapshot = snapshot else {
return
}
if snapshot.documents.last != nil {
self.lastSnapshot = snapshot.documents.last
} else {
return
}
if let error = error {
print(error.localizedDescription)
} else {
for document in snapshot.documents {
let docName = document["name"] as? String
let docId = document.documentID
let docImages = document["images"] as? [String]
let docCollection = document["collection"] as? String
let docInfo = document["info"] as? String
let docQuantity = document["quantity"] as? Int
let item = Item(id: docId, name: docName!, collection: docCollection!, info: docInfo!, images: docImages!, quantity: docQuantity!)
self.items.append(item)
}
if self.items.count >= 25 {
self.addFooter()
}
self.tableView.reloadData()
}
})
}
func deleteItem(at indexPath: IndexPath) {
let itemToDelete = items[indexPath.row]
// Delete images from storage
for url in itemToDelete.images {
let store = Storage.storage()
let storageRef = store.reference(forURL: url)
storageRef.delete { error in
if let error = error {
print(error.localizedDescription)
} else {
print("Image file deleted successfully")
}
}
}
Firestore.firestore().collection("items").document(itemToDelete.id).delete() { error in
if let error = error {
print(error.localizedDescription)
} else {
print("Item deleted")
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRows(): \(items.count)")
return items.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
let item = items[indexPath.row]
cell.itemNameLabel.text = item.name
if item.images.count > 0 {
let thumbnailUrl = item.images[0]
cell.itemImageView.sd_setImage(with: URL(string: thumbnailUrl), placeholderImage: UIImage(named: "photo"), completed: { (image, error, cacheType, imageUrl) in
cell.itemImageView.roundCornersForAspectFit(radius: 10)
})
} else {
cell.itemImageView.image = UIImage(named: "photo")
}
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("Items before delete: \(items.count)")
deleteItem(at: indexPath)
// items.removeAll()
// tableView.reloadData()
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
print("Items after delete: \(items.count)")
}
}
You can use Property Observer to handle your tableView.reloadData()
var items = [Item]() {
didSet {
tableView.reloadData()
}
}
what it does above is whenever variable items is modified, it will trigger didSet {} block of code.
Hope is will answer your question.

Selected row from each section of UITableView ( Multiple Selection )

I have used tableview(grouped).
So i need to select one row from the each section of UITableviewSection.
So for that i have tableview and one submit button .So i need to check when i click on the submit button i need to check whether i have selected one row from the each section ,if not then show alert as not selected the section number.How to check?
This is my data.
{
"data":[
{
"question": "Gender",
"options": ["Male","Female"]
},
{
"question": "How old are you",
"options": ["Under 18","Age 18 to 24","Age 25 to 40","Age 41 to 60","Above 60"]
},
{
"question": "I am filling the Questionnaire for?",
"options": ["Myself","Mychild","Partner","Others"]
}
]
}
QuestionModel:-
class QuestionListModel: NSObject {
var selected = false
var dataListArray33:[NH_OptionsModel] = []
var id:Int!
var question:String!
var buttontype:String!
var options:[String]?
var v:String?
var optionsModelArray:[OptionsModel] = []
init(dictionary :JSONDictionary) {
guard let question = dictionary["question"] as? String,
let typebutton = dictionary["button_type"] as? String,
let id = dictionary["id"] as? Int
else {
return
}
if let options = dictionary["options"] as? [String]{
print(options)
print(options)
for values in options{
print(values)
let optionmodel = OptionsModel(values: values)
self.optionsModelArray.append(optionmodel)
}
}
self.buttontype = typebutton
self.question = question
self.id = id
// print(self.dataListArray33)
}
}
optionModel:-
class OptionsModel: NSObject {
var isSelected:Bool? = false
var v:String?
var values:String?
init(values:String) {
self.values = values
print( self.values)
}
ViewModel:-
func numberOfSections(tableView: UITableView) -> Int{
print((datasourceModel.dataListArray?.count)!)
return (datasourceModel.dataListArray?.count)!
}
func titleForHeaderInSection(atsection section: Int) -> NH_QuestionListModel {
return datasourceModel.dataListArray![section]
}
func numberOfRowsIn(section:Int) -> Int {
print( datasourceModel.dataListArray?[section].optionsModelArray.count ?? 0)
return datasourceModel.dataListArray?[section].optionsModelArray.count ?? 0
// return self.questionsModelArray?[section].optionsModelArray.count ?? 0
}
func datafordisplay(atindex indexPath: IndexPath) -> NH_OptionsModel{
print(datasourceModel.dataListArray![indexPath.section].optionsModelArray[indexPath.row])
return datasourceModel.dataListArray![indexPath.section].optionsModelArray[indexPath.row]
}
func question(answer:String) {
print(questions)
questions.append(answer)
print(questions )
}
func questionlist(answer:String) {
print( questionlist )
questionlist.append(answer)
print( questionlist )
}
func answer(answer:String) {
answers.append(answer)
print(answers)
}
and finally viewController:-
func numberOfSections(in tableView: UITableView) -> Int {
return questionViewModel.numberOfSections(tableView: tableView)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifier = "HeaderCell"
var headercell: questionheader! = tableView.dequeueReusableCell(withIdentifier: identifier) as? questionheader
if headercell == nil {
tableView.register(UINib(nibName: "questionheader", bundle: nil), forCellReuseIdentifier: identifier)
headercell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader
}
headercell.setReviewData(reviews:questionViewModel.titleForHeaderInSection(atsection:section))
return headercell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionViewModel.numberOfRowsIn(section: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
var cell: QuestionListCell! = tableView.dequeueReusableCell(withIdentifier: identifier) as? QuestionListCell
if cell == nil {
tableView.register(UINib(nibName: "QuestionListCell", bundle: nil), forCellReuseIdentifier: identifier)
cell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_QuestionListCell
}
cell.contentView.backgroundColor = UIColor.clear
let questionsModel = questionViewModel.titleForHeaderInSection(atsection:indexPath.section)
print(questionsModel.buttontype)
questionViewModel.button = questionsModel.buttontype
cell.setOptions(Options1: questionViewModel.datafordisplay(atindex: indexPath))
print("Section \(indexPath.section), Row : \(indexPath.row)")
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
print("Section \(indexPath.section), Row : \(indexPath.row)")
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.isSelected)
cell?.setOptions(OptionsSelected:questionViewModel.datafordisplay(atindex: indexPath))
print(model.isSelected)
questionViewModel.isselected = model.isSelected!
let section = indexPath.section
let index = indexPath.row
print(section)
print(index)
if !questionViewModel.selectedIndexPaths.contains(indexPath) {
questionViewModel.selectedIndexPaths.append(indexPath)
print(questionViewModel.selectedIndexPaths.append(indexPath))
let questionModel = questionViewModel.titleForHeaderInSection(atsection: section)
print(questionModel.question)
questionViewModel.question = questionModel.question
questionViewModel.questionlist(answer: questionViewModel.question!)
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.values)
questionViewModel.answer(answer: model.values!)
let value: Int = questionModel.id
let string = String(describing: value)
//let x: Int? = Int(model.id)
questionViewModel.question_id = string
questionViewModel.question(answer: questionViewModel.question_id!)
print(questionModel.id)
// append the selected index paths
} // if indexPath.section == section {
// questionViewModel.indexPath(indexPaths: index)
// }
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let index = questionViewModel.selectedIndexPaths.index(of: indexPath) {
print(index)
questionViewModel.selectedIndexPaths.remove(at: index)
}
}
According to this i got the output .
But i have button action in viewcontroller.
#IBAction func forward(_ sender: AnyObject) {
}
In this button action i need to check whether from each section did i selected one row or not .if not show alert .How to do
my current didselect method :-
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.isSelected)
cell?.setOptions(OptionsSelected:questionViewModel.datafordisplay(atindex: indexPath))
print(model.isSelected)
questionViewModel.isselected = model.isSelected!
let section = indexPath.section
let index = indexPath.row
print(section)
print(index)
if !questionViewModel.selectedIndexPaths.contains(indexPath) {
questionViewModel.selectedIndexPaths.append(indexPath)
print(questionViewModel.selectedIndexPaths.append(indexPath))
let questionModel = questionViewModel.titleForHeaderInSection(atsection: section)
print(questionModel.question)
questionViewModel.question = questionModel.question
questionViewModel.questionlist(answer: questionViewModel.question!)
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.values)
questionViewModel.answer(answer: model.values!)
let value: Int = questionModel.id
let string = String(describing: value)
//let x: Int? = Int(model.id)
questionViewModel.question_id = string
questionViewModel.question(answer: questionViewModel.question_id!)
print(questionModel.id)
}
I have 3 array
According to this didselect method:-
ex:-for section 1 :-i selected 1st row so the data append as below.
questionlist:["How r u?"]
answelist:["fine"]
But suppose i think that i need 2nd indexpath ,so i need to remove the previous appended data from arrays and append the current data .As below:
questionlist:["How r u?"]
answelist:["not well"]
And next for section 2 : i selected 1st indexpath.row data .then that data is append.So i need to get as below:-
questionlist:["How r u?","Gender"]
answelist:["not well","Male"]
Here selecting i think that i need the 2nd option then remove the added indexpath.row data from array and show as:-
questionlist:["How r u?","Gender"]
answelist:["not well","Female"]
Such way how to set?
you can update your model based on the selection like
"data":[
{
"question": "Gender",
"options": ["Male","Female"],
"optionSelected": "Male"
}
]
and on Submit , check data for selections
The table view has a property to get selected index paths. You can use all native components for that. What you need is to deselect an item at index path where one is already selected in a certain section. You also just need to then check that the number of selected index paths is the same as number of arrays in your data source.
Check something like this:
var dataSource: [[Any]]!
var tableView: UITableView!
func didSelectRowAt(_ indexPath: IndexPath) {
guard let selectedPaths = tableView.indexPathsForSelectedRows else { return } // We need to have selected paths
guard selectedPaths.contains(indexPath) == false else { return } // The same cell being selected
let previouslySelectedCellIndexPaths: [IndexPath] = selectedPaths.filter { $0.section == indexPath.section && $0 != indexPath } // Getting all selected index paths within this section
previouslySelectedCellIndexPaths.forEach { tableView.deselectRow(at: $0, animated: true) } // Deselect waht was previously selected
}
/// Will return array of selected objects only if all sections have a selected index
///
/// - Returns: A result array
func getSelectionData() -> [Any]? {
guard let selectedPaths = tableView.indexPathsForSelectedRows else { return nil } // We need to have selected paths
guard selectedPaths.count == dataSource.count else { return nil } // This should prevent missing selections assuming all index paths are unique in sections
return selectedPaths.map { dataSource[$0.section][$0.row] } // Map selected index paths back to objects
}
I tried to use kind of minimum code to show all of this. It is all commented so you can see row by row what goes on.
You might want to check is all sections are unique the second method but it is not needed if the first one is always used.
You can store selected indexPath in an array. OnClick of submit just loop through array and check either at least one element is from each section.
FYI : indexPath contains section info also.
Declare an mutable array and allocate in viewDidLoad.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[anArray addObject:indexPath];
}
on Submit action follow this, you can improvise based on your requirement
-(void)onSubmitAction{
[anArray addObject:indexPath];
NSMutableArray *countOfSection=[[NSMutableArray alloc]init];
for (NSIndexPath*indexPath in anArray ) {
if(![anArray containsObject:indexPath.section])
[countOfSection addObject:indexPath.section];
}
if(countOfSection.count == self.tableview.numberOfSections){
//write your code
}else{
// show alert
}
}
Step 1 : Create Global Variable
var selectedIndexPaths = [IndexPath]()
Step 2: Add UITableView Property
tableView.allowsMultipleSelection = true
Step 3 : Implement the delegate methods
//On Selection
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndexPathAtCurrentSection = selectedIndexPaths.filter({ $0.section == indexPath.section})
for indexPath in selectedIndexPathAtCurrentSection {
tableView.deselectRow(at: indexPath, animated: true)
if let indexOf = selectedIndexPaths.index(of: indexPath) {
selectedIndexPaths.remove(at: indexOf)
}
}
selectedIndexPaths.append(indexPath)
}
// On DeSelection
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let index = selectedIndexPaths.index(of: indexPath) {
selectedIndexPaths.remove(at: index)
}
}
Step 4: Getting Selected IndexPaths with sections
#IBAction func forward(sender:Any){
let totalSections = questionViewModel.numberOfSections(tableView: tableView)
for section in 0..<totalSections {
if (selectedIndexPaths.filter({ $0.section == section}).count >= 1) {
continue
} else {
// Show alert
print("Please select item at",(section))
return
}
}
}

Limit the amount of cells shown in tableView, load more cells when scroll to last cell

I'm trying to set up a table view that only shows a specific amount of cells. Once that cell has been shown, the user can keep scrolling to show more cells. As of right now I'm retrieving all the JSON data to be shown in viewDidLoad and storing them in an array. Just for example purposes I'm trying to only show 2 cells at first, one the user scrolls to bottom of screen the next cell will appear. This is my code so far:
class DrinkViewController: UIViewController {
#IBOutlet weak var drinkTableView: UITableView!
private let networkManager = NetworkManager.sharedManager
fileprivate var totalDrinksArray: [CocktailModel] = []
fileprivate var drinkImage: UIImage?
fileprivate let DRINK_CELL_REUSE_IDENTIFIER = "drinkCell"
fileprivate let DRINK_SEGUE = "detailDrinkSegue"
var drinksPerPage = 2
var loadingData = false
override func viewDidLoad() {
super.viewDidLoad()
drinkTableView.delegate = self
drinkTableView.dataSource = self
networkManager.getJSONData(function: urlFunction.search, catagory: urlCatagory.cocktail, listCatagory: nil, drinkType: "margarita", isList: false, completion: { data in
self.parseJSONData(data)
})
}
}
extension DrinkViewController {
//MARK: JSON parser
fileprivate func parseJSONData(_ jsonData: Data?){
if let data = jsonData {
do {
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String : AnyObject]//Parses data into a dictionary
// print(jsonDictionary!)
if let drinkDictionary = jsonDictionary!["drinks"] as? [[String: Any]] {
for drink in drinkDictionary {
let drinkName = drink["strDrink"] as? String ?? ""
let catagory = drink["strCategory"] as? String
let drinkTypeIBA = drink["strIBA"] as? String
let alcoholicType = drink["strAlcoholic"] as? String
let glassType = drink["strGlass"] as? String
let drinkInstructions = drink["strInstructions"] as? String
let drinkThumbnailUrl = drink["strDrinkThumb"] as? String
let cocktailDrink = CocktailModel(drinkName: drinkName, catagory: catagory, drinkTypeIBA: drinkTypeIBA, alcoholicType: alcoholicType, glassType: glassType, drinkInstructions: drinkInstructions, drinkThumbnailUrl: drinkThumbnailUrl)
self.totalDrinksArray.append(cocktailDrink)
}
}
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
DispatchQueue.main.async {
self.drinkTableView.reloadData()
}
}
//MARK: Image Downloader
func updateImage (imageUrl: String, onSucceed: #escaping () -> Void, onFailure: #escaping (_ error:NSError)-> Void){
//named imageData because this is the data to be used to get image, can be named anything
networkManager.downloadImage(imageUrl: imageUrl, onSucceed: { (imageData) in
if let image = UIImage(data: imageData) {
self.drinkImage = image
}
onSucceed()//must call completion handler
}) { (error) in
onFailure(error)
}
}
}
//MARK: Tableview Delegates
extension DrinkViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return numberOfRows
return drinksPerPage
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = drinkTableView.dequeueReusableCell(withIdentifier: DRINK_CELL_REUSE_IDENTIFIER) as! DrinkCell
//get image from separate url
if let image = totalDrinksArray[indexPath.row].drinkThumbnailUrl{//index out of range error here
updateImage(imageUrl: image, onSucceed: {
if let currentImage = self.drinkImage{
DispatchQueue.main.async {
cell.drinkImage.image = currentImage
}
}
}, onFailure: { (error) in
print(error)
})
}
cell.drinkLabel.text = totalDrinksArray[indexPath.row].drinkName
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let image = totalDrinksArray[indexPath.row].drinkThumbnailUrl{
updateImage(imageUrl: image, onSucceed: {
}, onFailure: { (error) in
print(error)
})
}
performSegue(withIdentifier: DRINK_SEGUE, sender: indexPath.row)
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = drinksPerPage
if indexPath.row == lastElement {
self.drinkTableView.reloadData()
}
}
}
I saw this post: tableview-loading-more-cell-when-scroll-to-bottom and implemented the willDisplay function but am getting an "index out of range" error.
Can you tell me why you are doing this if you are getting all results at once then you don't have to limit your display since it is automatically managed by tableview. In tableview all the cells are reused so there will be no memory problem. UITableViewCell will be created when it will be shown.
So no need to limit the cell count.
I dont now what you are doing in your code but:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = drinksPerPage // no need to write this line
if indexPath.row == lastElement { // if block will never be executed since indexPath.row is never equal to drinksPerPage.
// As indexPath starts from zero, So its value will never be 2.
self.drinkTableView.reloadData()
}
}
Your app may be crashing because may be you are getting only one item from server.
If you seriously want to load more then you can try this code:
Declare numberOfItem which should be equal to drinksPerPage
var numberOfItem = drinksPerPage
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return numberOfRows
return numberOfItem
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == numberOfItem - 1 {
if self.totalDrinksArray.count > numberOfItem {
let result = self.totalDrinksArray.count - numberOfItem
if result > drinksPerPage {
numberOfItem = numberOfItem + drinksPerPage
}
else {
numberOfItem = result
}
self.drinkTableView.reloadData()
}
}
}

Resources