Reusable UITableView for Varying Data Input - Swift/Xcode - swift

I have a TableView that I want to reuse for different categories of data (essentially as plugins.. the tableView being a skeleton and being filled with whatever I want it to be). The TableView is filled with different categories of data (and related actions) depending on essentially what ViewController the user came from. I understand how to make it display the various data (just send it whatever array I want it to display), but I can't figure out how I could control the actions for the data at the specific index selected in didSelectRowAtIndexPath.
How could I do this? How could I create an array that has both Strings and executable actions associated with each indices? For example:
arrayOneNames = ["Tigris", "Leo", "Barko"]
arrayOneActions = [displayTheTigers, displayTheCats, displayTheDogs]
If "Leo" is selected in the tableView, then "displayTheCats" is executed. Again, I want each array to be a separate Class that I can use as a plugin, so that I can fill the tableView with whichever Class of data I want it to display and execute, depending on which ViewController the user came from previously. Please answer in Swift.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=UITableViewCell() // cell you've created
cell.data = arrayOneNames[indexPath.row] // passing relevant data
cell.tag = indexPath.row // the tag you want to pass for particular data
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)! as UITableViewCell // take selected cell
if(cell.tag == 0) { // call relevant function accordingly.
self.tigrisTouch()
} else if (cell.tag == 1) {
self.leoTouch()
} else {
self.barkoTouch()
}
}
private func tigrisTouch() {
}
private func leoTouch() {
}
private func barkoTouch() {
}

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()

Realm results object with incorrect results (iOS, Swift)

I am having issues getting Realm's result object to be accessed correctly using UITableView's cellForRowAt.
Here's the setup:
UITableViewController is divided into sections based on the Objects's category (a string defined in the object).
UITableViewController has a segue to a UIViewController which takes form input. That view controller writes to Realm and then makes a call back via delegation to refresh the table view data.
When that screen dismisses and returns to the UITableViewController, as I try to add the row via category, I am getting empty objects. However, when I use a for loop within cellForRowAt, I can access the data.
Here is what I'm running in this section:
func loadItems() {
itemsList = try! Realm().objects(Items.self).filter("list_id = \(list_id)").sorted(byKeyPath: "item_category")
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemListCell", for: indexPath)
let categoryName = categories.categories[indexPath.section]
let currItem = itemsList[indexPath.row]
if currItem.item_category == categoryName {
cell.textLabel!.text = currItem.item_name
}
return cell
}
It seems to be evaluating category correctly and stepping into that block, but the object's item_name and item_category is null. Here is a screenshot of the debugger inside the if statement:
Debugger Image
Is there something I need to change with how I'm using the object, pulling data, etc, to get the data correct in the object?
Found my answer here: UITableView with Multiple Sections using Realm and Swift
This is the change I made to cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemListCell", for: indexPath)
let currItem = itemsList.filter("item_category = '\(categories[indexPath.section])'")[indexPath.row]
cell.textLabel!.text = currItem.item_name
return cell
}
The issue I was having was I was constantly pulling the first position results object and not the first position within a section. I needed to narrow down to my section and then pull the first row.
I think it may have to do with your filters predicate. Try changing
itemsList = try! Realm().objects(Items.self).filter("list_id = \(list_id)").sorted(byKeyPath: "item_category"
to this
itemsList = try! Realm().objects(Items.self).filter("list_id = '\(list_id)'").sorted(byKeyPath: "item_category"
Add single quotations around '(list_id)'.
Realm Filtering

How to pass all info from multiply table views cells with the info INSERTED to a new view controller?

#objc func addRow(sender: UIButton!) {
print("add")
names.insert("New Name", at: 0)
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .right)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
names.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// if section == 1 {
// return carArray.count
// }
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: bondeCellId, for: indexPath) as! BondCell
//
// if let lbl = cell.contentView.viewWithTag(101) as? UILabel {
// lbl.text = names[indexPath.row]
// }
//
// if let btnDelete = cell.contentView.viewWithTag(102) as? UILabel {
// btnDelete.addTarget(self, action: #selector(deleteRow()), for: .touchUpInside)
// }
return cell
}
enter image description hereI am fairly knew to programing and don’t know a lot.
I have built table view that lets the user add more table cells if they press the add button.
So the user will add more table views on depending if more information is needed.
The problem is to understanding is how to pass all info from multiply table views cells with the info to a new view controller.
If the User has insert 5 or 10 table cells I want those table cells info to be passed into a one new view controller.
The table view cell contains two text fields and two labels and the text field contains UIpickers and depending what they chose it updates in the labels in cell tables.
But I also want sum of all the table views chosen by the user to be added to a chart that I am making to a new view controller.
Would the labels and the text fields need to get new names each time a new table cell is made?
I am stock with ideas
If the User has insert 5 or 10 table cells I want those table cells info to be passed into a one new view controller.
The code you posted is a mess, and not very helpful with your question. It's mostly boilerplate.
You are thinking about this wrong. Cells don't store data. More generally, view objects don't store data. They display data.
You need a model object that stores the data for your table view. It looks like your table view only has one section, so a simple array would work just fine. Create a struct to hold the info you collect from the user. Let's call that struct CellData. Your model would be an array of CellData structs.
As the user creates new cells, you'd collect the new info and add it to your array.
Then when the user selects cells, and invokes the new view controller, you'd pass the whole array, along with the indexes of the selected cells, to the other view controller. (or perhaps just the structs for each selected item.)

How to control the contents of a table view based on cell selection in a collection view?

I have a collection view with 4 cells and a table view inside the same view controller. Each cell in the CV must make the table view display a different list; one table cell type per CV cell. When the user taps on one CV cell the table must reload its data and display the correct type of cell, (basically a different list with a custom cell). Each of these lists contains only 1 section.
I created and registered all my different cells with the table view and have all my data ready, with the views set and so on. How do I make the table view discard its current cells (list) and show a new list with different cells when the user has tapped on another CV cell?
I suppose that the solution is in the didSelectItem method from the CV delegate but I cannot find any information that shows how to make the table dequeue a different type of cell when the user has changed the cell selection inside the CV; or discard the previous one if needed.
At the moment I only register and dequeue one type of cell, and inside the delegate method for the CV I am calling empty functions that are supposed to put the new list inside the table.
The number of rows for each list is dynamic and this implies that I would have to call the delegate method on the table view again.
I have found an example of the MVVM pattern but I cannot apply it to my logic as that is more static.
Any help would be much appreciated. Thank you.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedMenuTab = indexPath.item
switch selectedMenuTab { // show different type of cell.
case 0: showAList()
case 1: showBList()
case 2: showCList()
case 3: showDList()
default:
print("no such main tab")
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableOfConversations.dequeueReusableCell(withIdentifier: rowID, for: indexPath) as! ConversationsListCell
let messageInChatList = listOfOneToOneChats[indexPath.item]
cell.messageInChatList = messageInChatList
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listOfOneToOneChats.count
}
I think this should be as simple as calling tableView.reloadData() at the end of the collection view delegate's didSelectItemAt method.
The tableView should have it's data source based on a common array, for example:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return displayingList.count
}
And then in the didSelect of the collection view set that array and then tell the tableView to reload:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedMenuTab = indexPath.item
switch selectedMenuTab { // show different type of cell.
case 0: displayingList = listA
case 1: displayingList = listB
case 2: displayingList = listC
case 3: displayingList = listD
default:
print("no such main tab")
}
tableView.reloadData()
}
And for dequeueing cells, check based on the type if they are different, or based on selectedMenuTab:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UICollectionViewCell
let selectedMenuTab = indexPath.item
switch selectedMenuTab { // show different type of cell.
case 0: cell = tableOfConversations.dequeueReusableCell(withIdentifier: rowID, for: indexPath) as! ConversationsListCell
//And so on
default:
fatalError("No cell for this tab")
}
let item = displayingList[indexPath.item]
// Cell setup
return cell
}
The type of the values will be something to consider in order to avoid an array of Any, which would not be super type-safe, but that will depend on the types of your objects.

How to add a custom cell to a dynamicly generated tableview?

Nearly during a week i'm trying to figure out how i can append a static-/custom tableviewcell to a dynamically generated tableview. I'm populating the cells based on the data i'm getting from the database. Basically what i'm trying to accomplish is like the following picture from the app ClassDojo:
As you may know and see, you can add add as many groups as you want with the ClassDojo app, but the latest cell, in this case Voeg een nieuwe klas toe, will always stay at the bottom of the tableview. That's exactly what i'm trying to do.
What i tried to do till this moment is trying to calculate the latest cell in the tableview and trying to append my custom cell, but unfortunately i couldn't get my head around it.
I would really appreciate if someone can help me out with this.
Thanks in advance.
Please let me know if you guys need any code.
---------EDITED POST---------
I did accomplish to assign my custom cell thanks to #Slayter, but now i'm facing with the problem that my custom cell is immediately overwritten by my dynamically created cells (with Alamofire).
Any help would be appreciated.
ClassDojo iOS Engineer here! More than happy to share how we do this.
Like Slayter mentioned, we do
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count + 1 // Add one for your custom cell
}
But in addition we also do the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = nil;
if self.indexIsForCustomCell(indexPath) {
cell = tableView.dequeueReusableCellWithIdentifier(CustomCell.reuseIdentifier)
// Additional configuration
} else {
cell = tableView.dequeueReusableCellWithIdentifier(RegularCell.reuseIdentifier)
// Additional configuration
}
return cell
}
Where CustomCell looks something like this (not exact):
class CustomCell: UITableViewCell {
static let reuseIdentifier = "CustomCell"
// More code
}
And regular cell looks like:
class RegularCell: UITableViewCell {
static let reuseIdentifier = "RegularCell"
// More code
}
The reason your dynamic cells are getting overwritten is because of the line
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
Notice that you are using the same cellIdentifier regardless of whether or not it is the custom cell or the regular cells. This means that most likely you are doing the following:
self.tableView.registerClass(RegularCell.class, forIdentifier: "new group")
self.tableView.registerClass(CustomCell.class, forIdentifier: "new group")
When you should be doing:
self.tableView.registerClass(RegularCell.class, forIdentifier: RegularCell.reuseIdentifier)
self.tableView.registerClass(CustomCell.class, forIdentifier: CustomCell.reuseIdentifier)
OR
self.tableView.registerClass(RegularCell.class, forIdentifier: "regularCellReuseIdentifier")
self.tableView.registerClass(CustomCell.class, forIdentifier: "customCellReuseIdentifier")
By using the same identifier key, you are telling the UITableView to treat both cells as the same type. So when it needs to reclaim memory for a new cell being drawn on screen, it's going to use RegularCell and CustomCell interchangeably.
Hope this helped and thanks for checking out our App!
================= EDIT =================
Realized that I forgot to add the indexIsForCustomCell method. Here it is:
func indexIsForCustomCell(indexPath : NSIndexPath) -> Bool {
if self.myData.count > 0 {
return indexPath.section == 0 && indexPath.row == (self.myData.count+1)
}
return indexPath.section == 0 && indexPath.row == 0
}
It's pretty simple. You just need to tell the tableView to expect one more cell than what is in your data source.
Example
In your numberOfRowsInSection method, you will have something like this:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count + 1 // Add one for your custom cell
}
Then in your cellForRowAtIndexPath method you just need to add some custom logic for that indexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
if indexPath.row < myData.count {
// configure cell as normal
} else {
// Add your custom cell logic here
}
return cell
}