Dynamically adding item to TableView resulted in repeated cells - swift

I have a tableview that's loading data dynamically from Firebase Database. Also I have an add button that allows the user to add inside the Firebase Database after that the table view must be updated and reload the data again from firebase plus the new added item. However, I'm having an issue that the data in the table view are duplicated after adding an item. I have tried to clear the datasource array and then reloading the table view data but it is not working. This is my code:
override func viewDidLoad()
{
recipeTableView.delegate = self
recipeTableView.dataSource = self
// clear the array
myRecipes.removeAll()
// get recipes
getRecipes()
}
Table view extension functions
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return myRecipes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
recipe = self.myRecipes[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "MyRecipeCell")
as! UIMyRecipesCell
cell.setRecipe(recipe: recipe!)
}
method to get data from fire base
func getRecipes()
{
self.myRecipes.removeAll()
childRef.observe(.value, with: { snapshot in
for child in snapshot.children
{
//create the object and get the info from the fire base and finally store it MyRecipes array
self.myRecipes.append(recipe)
}
self.recipeTableView.reloadData()
})

You need to use observeSingleEvent instead of observe
childRef.observeSingleEvent(of: .value) { snapshot in
////
}
OR
childRef.observe(.childAdded) { snapshot in
////
}
OR
childRef.observe(.value, with: { snapshot in
self.myRecipes.removeAll ()
///
}
observe fetches all the data again every change unlike observeSingleEvent that gets all the data once

Related

Saving Data in UITableview

I have an app whereby a user can add books to a favourites list, by populating a tableview. Details on the book are displayed in a view and by tapping a 'favourites', a segue is performed inputting info on that book into a tableview cell.
At present only one book can appear in the table at a time adding a new book will remove the initial entry (so in effect only the first cell of the tableview is ever used)
Is there a way to save each entry in the tableview so in effect a list of favourites is created
saveButton
#IBAction func saveButton(_ sender: Any) {
let bookFormat = formatLabel.text
if (bookFormat!.isEmpty)
{
displayMyAlertMessage(userMessage: "Please Select a Book Format")
return
}
else{
self.performSegue(withIdentifier: "LibViewSegue", sender: self)
}
}
TableView
extension LibrarybookViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 115
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(#function, dataSource.count)
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(#function, "indexPath", indexPath)
guard let bookCell = tableView.dequeueReusableCell(withIdentifier: "libCell", for: indexPath) as? LibrarybookTableViewCell else {
return UITableViewCell()
}
let libbook = dataSource[indexPath.row]
bookCell.cellTitleLabel.text = libbook.title
bookCell.cellReleaseLabel.text = libbook.release
bookCell.cellFormatLabel.text = bookFormat
return bookCell
}
I have been reading about defaults and CoreData but I'm not sure whether this should be implemented within the segue button action or within the tableview functions?
I see you have a dataSource array which contains list of books. At the simplest you could just append data to your dataSource, then reload your UITableView. But if you want persistent storage, you could look into local db solutions like SQLite, CoreData, or Realm. Then it would just be the matter of storing -> get data -> display on UITableView.
The general idea would be on add button tap (or whatever event listener you want to attach the action to), save it to persistent storage, reload your tableView, since the dataSource is from persistent storage, it'll update automatically. Suppose you load the data from persistent storage.
Also, don't store your array in UserDefaults, it's for bite sized data for things like user session etc.
Edit: as #Leo Dabus point out, indeed you can insert row without reloading the tableView by using
let index = IndexPath(row: items.count - 1, section: 0) // replace row with position you want to insert to.
tableView.beginUpdates()
tableView.insertRows(at: [index], with: .automatic)
tableView.endUpdates()

Trying to edit a List in realm

I'm trying to make a List with realm:
class TripsList : Object {
let trips = List<Trip>()
}
Then, inside my ViewController class:
var trips : Results<TripsList>?
override func viewDidLoad() {
super.viewDidLoad()
trips = realm.objects(TripsList.self)
}
When someone moves a UITableViewRow, I want to update my realm database.
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = self.realm.objects(Trip.self)[sourceIndexPath.row]
trips.remove(at: sourceIndexPath.row)
trips.insert(movedObject, at: destinationIndexPath.row)
}
Here are my TableView Datasource methods:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return realm.objects(Trip.self).count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.font = UIFont.systemFont(ofSize: 17)
cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
cell.textLabel?.text = nameData.names[realm.objects(Trip.self)[indexPath.row].tripID]
return cell
}
The problem is there is no option to do trips.remove(at:) or trips.insert(_:at:).
My overall goal is the ability to insert and remove when someone moves a UITableViewRow and update my realm database.
You cannot directly modify a Results instance. Results are auto-updating collections, meaning that they always reflect the current state of the query you used to initialise them. You need to modify the Realm in order to modify the Results instance.
Moreover, a Results instance is only guaranteed to keep its ordering in case you sort it. So you'll need to introduce a property on Trip that you use to sort the objects and modify that property when the user moves a row.
Your TripsList class seems to be unnecessary, since it seems that you simply want to store a number of Trip objects in Realm, then retrieve them without actually doing any grouping. Even if you needed to group them, you could do so using Realm queries. Keeping this in mind, this is how I'd modify your current code to allow the user to sort their Trips and save the sorting to Realm.
class Trip: Object {
// Your existing code for Trip
...
// Sorting property
#objc dynamic var sortingIndex = 0
}
In your table view controller:
var trips : Results<Trip>?
override func viewDidLoad() {
super.viewDidLoad()
trips = realm.objects(Trip.self).sorted(byKeyPath: "sortingIndex")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return trips?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.font = UIFont.systemFont(ofSize: 17)
cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
if let tripID = trips?[indexPath.row].tripID {
cell.textLabel?.text = nameData.names[tripID]
}
return cell
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
guard let movedObject = trips?.[sourceIndexPath.row] else { return }
// Depending on your exact needs, you might want to update the `sortingIndex` property of your other rows as well, whose position in the table view was affected by the reordering
try! realm.write {
movedObject.sortingIndex = destinationIndexPath.row
}
}

Getting list of events from an array

I want to print out a list of events in one day selected on a calendar made using FSCalendar. So far I have written this in my app:
while counter2 < noOfEvents{
print("counter[counter2]: ", counter[counter2])
cell.eventName.text = eventsArray[counter[counter2]].nameOfEvent
cell.eventDescription.text = eventsArray[counter[counter2]].descriptionEvent
cell.countryName.text = eventsArray[counter[counter2]].country
cell.eventDate.text = eventsArray[counter[counter2]].dateEvent
cell.eventTime.text = eventsArray[counter[counter2]].timeEvent
print("eventsArray[counter[counter2]].nameOfEvent: ", eventsArray[counter[counter2]].nameOfEvent)
counter2 += 1
}
My question is how to make the list display both events and not just the latest one. In the logs both are clearly there but in the actual list on the app only the latest one is displayed. Any helpp is appreciated
// MARK: - Table view data source
// Set the number of sections in the table view (assumes 1 if simple list of events)
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// Set the number of events
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsArray.count
}
// Provide each cell when requested
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CalendarCell2", for: indexPath) as! CalendarCell2
// Configure the cell...
if let event = eventsArray[indexPath.row] {
// Set the appropriate elements in the cell
}
return cell
}
Note that if the number of events changes you'll want to update the table view by the reloadData() function.

Mutli-threaded giving results out of order

I'm really new to multithreading. It has produced a result I wasn't expecting, but it makes sense when I think about it. Now I need to find a way to make it work right.
Here is the scenario....
I have routine that calls a web page with a requested date. Let's call it getPage(inDate: String). The results are of unknown, but different sizes, depending on the date submitted. (Today could have 100 items, tomorrow 2 items, the next day 10 items).
I have a loop that calls getPage, in date sequence.
Each return is a section in a tableview.
PSEUDO CODE
func viewDidLoad() {
...
for cntr in 1...4 {
getPage(calculatedDate)
}
...
}
getPage(inDate: String) {
let task = NSURLSession ( ....) {
...
// create tableview section data using self. arrays of string
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.dateSection.count // array of sections
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataItem1[section].count // an array of data for the section
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let mycell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! calCell
self.navigationItem.title = "Calendar Data"
// Configure the cell...
mycell.dataItem1.text = self.data1[indexPath.section][indexPath.row]
mycell.dataItem2.text = self.data2[indexPath.section][indexPath.row]
mycell.dataItem3.text = self.data3[indexPath.section][indexPath.row]
mycell.dataItem4.text = self.data4[indexPath.section][indexPath.row]
return mycell
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// print("In titleForHederInSection: Section \(section)")
// print("Title: \(self.dateSection[section])")
return self.dateSection[section]
}
It doesn't seem to matter where I put the reload(), or if I use dispatch_sync or dispatch_async, the table is out of order...giving me the quickest returns first.
My initial attempts with semaphores has been a dismal failure...locking up the application forever.
So the question is, how do I get the viewtable to be built in the order that I called the web page (in this case, date order)?

swift 2 how to remove empty rows in a tableview in a viewcontroller

Is there a way to reset tableview height so that no empty rows are showed. For an example, a tableview displays 3 rows but there is only one row having real data. I'd like the tableview shrinks it size so there is only one row display
I guess you have a View Controller like this
class Controller: UITableViewController {
private var data: [String?] = ["One", nil, "Three", nil, nil]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
return cell
}
}
Since your model (data in my example) contains nil values you are getting a table view like this
One
_
Three
_
_
Removing the empty rows
Now you want instead a table view like this right?
One
Three
Filtering your model
Since the UI (the table view) is just a representation of your model you need to change your model.
It's pretty easy
class Controller: UITableViewController {
private var data: [String?] = ["One", nil, "Three", nil, nil]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
return cell
}
override func viewDidLoad() {
data = data.filter { $0 != nil }
super.viewDidLoad()
}
}
As you can see, inside viewDidLoad() I removed the bill values from the data array. Now you'll get only cells for real values.
I think you need remove the empty data before execute func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int.
//EDITED
I give you this possible solution, maybe there are more ways to do it, you can try with this.
class MyViewController: UITableViewDataSource, UITableViewDelegate {
private var realHeight = 0
private var data : [String] = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
self.realHeight += self.tableView.rowHeight //Increase real height value.
return cell
}
func loadData() { //This function reload the data
self.realHeight = 0
self.tableView.reloadData()
//Update tableViewHeight here, use self.realHeight. You can use constraints or update directly the frame.
}
}
The solution is have a counter that represents the sum of all visibles rows height. e.g If you have n cells then your height will be self.tableView.rowHeight * n, then you ever will have an effective height.
I recommend you to create a constraint for the table height and when the cells change, you only need change the constant of the constraint, is more easy than change the frame.