Better performance events inside TableView cell - swift

I am developing a Facebook like application, where if the post is too long, it gets cut down and at the end of it "See more" appears that has some click events. (I use FRHyperLabel for it)
Everything works fine, but I've got a question: If the formatting+event is decleared inside tableView: cellForRowAt indexPath: is it bad for the performance?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var message : message
var cell : MessageCell
let message = self.messages[indexPath.row]
// World Message
cell = tableView.dequeueReusableCell(withIdentifier: "essageCell", for: indexPath) as! MessageCell
cell.messageLabel.attributedText = message.limitMessageMax250char()
//Add link if needed
if message.count > appSettings.maxCharacters.message {
let handler = {
(hyperLabel: FRHyperLabel!, range: NSRange!) -> Void in
self.alert(title: "Test", message: "Alert: because clicked on link")
}
let text = cell.messageLabel.text!
let nsRange = text.calculateUnicodeScalar(start: text.unicodeScalars.count-8, len: 8) // See more is 8 characters
cell.messageLabel.setLinkFor(nsRange, withLinkHandler: handler)
}
return cell
}
Or should I do it in the Cell's file, when it's awakeFromNib + with delegate

IMO you don't want to do this in the for cellForRowAt call mostly because you want to make that call return quickly and also because you are only returning the formatted cell.
I would argue/recommend that you set your handler for the row when it is being initialized and that way the handler value is only being set once.
Remember that cellForRowAt is called multiple times as the user scrolls through and as cells come into view.
review the https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614861-tableview for more information as to how cells are reused.

Related

Reusable UITableView for Varying Data Input - Swift/Xcode

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

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

Change checked status of selected cell from uiTableViewCell

I'm new to swift and trying to change selected tableviewcell checked.
I'm trying to; user select city and districts and click to save button and app send the informations to server. There is no problem so there. When user wants to update the city or districts, I should check before districts and user can see what is he/she select or not. I'm getting first informations from server with json and append them to array. When user click the update button I can see before selected informations in debug mode but tableview cells are not going to be as checked. Sorry for my bad English btw.
What am I doing for first time selection :
in viewDidLoad()
self.tableViewDistrict.isEditing = true
self.tableViewDistrict.allowsMultipleSelection = true
self.tableViewDistrict.allowsMultipleSelectionDuringEditing = true
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: tableView, indexPath: indexPath)
}
extension ViewController{
func selectDeselectCell(tableView: UITableView, indexPath: IndexPath){
self.selectedDistId.removeAll()
if let array = tableView.indexPathsForSelectedRows{
for index in array{
selectedDistId.append(districtArray[index.row].id!)
}
}
}
}
Lets suppose you have stored all your previous selected data inside an array like
var alreadySelectedDistricts : [String] = []
so in your cellForRowAt method you can do a check like
for district in alreadySelectedDistricts {
if cell.districtLabel.text.contains(district) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}

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

Swift - Making a Button for UITableViewCell with .addTarget

I'm trying to make two different buttons for each cell that I create in my table view. One of the buttons is a + button that will increment a label. In my testing however I cannot get the function to work. My current error says
Argument of #selector does not refer to an '#objc' method, property, or initializer
I feel like I'm implementing the .addTarget method completely wrong but I am new. Here is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! AddItemCell
cell.setCell(item: item)
let itemAmount = cell.itemAmount as UILabel?
cell.addButton.addTarget(self, action: #selector(addItem(sender:cell.addButton,forLabel:itemAmount!)), for: .touchUpInside)
}
#objc func addItem(sender: UIButton, forLabel label:UILabel) {
print("Add Button Clicked")
}
You are using selector syntax incorrectly:
action: #selector(addItem(sender:cell.addButton,forLabel:itemAmount!))
Just say:
action: #selector(addItem)
Then, however, you will face a new problem. You think that somehow you can cause this button to call something called addItem(sender:forLabel:). You can't. Change the declaration of addItem to addItem(_ sender:UIButton). That is the only kind of function a button tap can call.
You will thus have the sender (the button), but you must figure out from there what the label is. (And this should be easy, because, knowing the button, you know the cell, and knowing the cell, you know the label.) You cannot pass the label as a parameter in response to the button tap — but you don't need to.
You need to create callback function in you cell
class AddItemCell: UITableViewCell {
var buttonClickCallback:(() -> Void)?
#IBAction func onButtonClick(_ sender:Any) {
buttonClickCallback?()
}
}
and assign buttonClickCallback in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! AddItemCell
cell.setCell(item: item)
let itemAmount = cell.itemAmount as UILabel?
cell.buttonClickCallback = {
self.addItem(sender:cell.addButton,forLabel:itemAmount!)
}
}