Tableview laggy when inserting dynamic cells and scrolling to bottom - swift

I'm making a chatbot app, in which I'm adding different kind of cells in a tableview (with dynamic height), and after adding, I'm scrolling to the bottom.
When the content size of the tableview is smaller compared to the height of the screen, there's no problem with adding the cells. But when the tableview content size has to grow (outside the screen), adding of the cells is laggy, and on my old iPad (iOS 10), cells aren't even displayed right (parts are cut off). Could anyone help me?
I do a network call to get the new message, after I have clicked on a cell option. With the result of that call, I fill the tableview.
func ask(sessionId: String, question: String, retry : Bool, completion : #escaping CompletionBlock) {
Alamofire.request(url!, method: .get, parameters: params, encoding: URLEncoding.default, headers: self.defaultHeaders).responseJSON { response in
switch response.result {
case .success(let results):
completion(.success, results as? NSObject)
break
case .failure:
completion(.error, nil)
break
}
}
}
When the call was successful, and the dataobject was added to a array with the new message in it, I'm adding the new cell in this part:
func insertRowChatView(chatContent: [ChatMessage]) {
if chatContent.count > self.chatMessageArray.count {
self.chatMessageArray = chatContent
let indexPath: IndexPath = IndexPath(row: self.chatMessageArray.count - 1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .none)
self.tableView.reloadRows(at: [indexPath], with: .none)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
And my cellforrowatindexpath code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (chatMessageArray.count) > indexPath.row {
if chatMessageArray[indexPath.row].dialogOptions != nil && chatMessageArray[indexPath.row].dialogOptions!.count > 0 {
// Dialogoptions cell
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatDialogOptionCell", for: indexPath as IndexPath) as! ChatDialogOptionCell
cell.delegate = self
cell.setupCell(dialogOptions: chatMessageArray[indexPath.row].dialogOptions!, text: chatMessageArray[indexPath.row].text!, lastItem: chatMessageArray[indexPath.row].lastItem)
return cell
} else if chatMessageArray[indexPath.row].links != nil && chatMessageArray[indexPath.row].links!.count > 0 {
// Links cell
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatLinkCell", for: indexPath as IndexPath) as! ChatLinkCell
cell.delegate = self
cell.setupCell(links: chatMessageArray[indexPath.row].links!, text: chatMessageArray[indexPath.row].text!, lastItem: chatMessageArray[indexPath.row].lastItem)
return cell
} else if chatMessageArray[indexPath.row].text != nil && chatMessageArray[indexPath.row].text != "" {
// Text only
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatTextOnlyCell", for: indexPath as IndexPath) as! ChatTextOnlyCell
cell.setupCell(text: chatMessageArray[indexPath.row].text!, askedByUser: chatMessageArray[indexPath.row].askedByUser, lastItem: chatMessageArray[indexPath.row].lastItem)
return cell
} else {
// Typing indicator
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatTypingIndicatorCell", for: indexPath as IndexPath) as! ChatTypingIndicatorCell
cell.setupCell()
return cell
}
}
let cell = UITableViewCell()
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
Is there a problem with my code, causing this performance problem?
Thanks for helping out!

Related

UITableView reloadRows crashes sometime

I have 4 rows which shows and hide some data below the selected row. Its works fine Most of the time. But when I click second/third/fourth rows multiple times(like 13-14) times and then click on first row then sometime it crashes not always but it crashes most of the time after multiple clicks. I am changing row height as well. Crash only happen when I select first row(case a), after selecting other rows multiple times. Below is my code -
I tried multiple things like calling it between beginUpdate and endUpdate, check is indexPath exist before reloading
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ”cell”, for: indexPath) as! CustomTableViewCell
let model = dataSource[indexPath.row]
cell.model = model
switch model.type {
case .a:
cell.aList = savedCardList
cell.addACallback = {[weak self] (sender) in
self?.moveToAVC()
}
cell.addACallback= {[weak self] (a) in
self?.selectedA = a
self?.updateButton()
}
case .b:
cell.bList = bList
cell.bSelectedCallback = {[weak self] (bInfo) in
self?.selectedB= bInfo
self?.updateButton()
}
case .c:
cell.cList = cList
if let cInfo = selectedC {
cell.selectOtherCButton.setTitle(cInfo.name, for: .normal)
}
cell.selectOtherCCallback = {[weak self] (sender) in
self?.showSelectCVC()
}
cell.CSelectedCallback = {[weak self] (cInfo) in
self?.selectedC = cInfo
self?.updateButton()
}
case .d:
cell.dTextField.text = dString
cell.dChangedCallBack = {[weak self] (textField) in
if let dStr = textField.text {
self?.dString = dStr
}
self?.updateButton()
}
default:
break
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let model = dataSource[indexPath.row]
guard model.isSelected == true else {
return 90
}
switch model.type {
case .a:
if aList.count > 0 {
return 350
}
return 140
case .b:
return 200
case .c:
return 270
case .d:
return 130
default:
return 90
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let model = dataSource[indexPath.row]
selectedType = model.type
updateButtonState(false)
updateButton()
if let selectedIndexPath = selectedIndexPath, selectedIndexPath != indexPath {
self.updatemodelSelectedState(false, at: selectedIndexPath)
self.updatemodelSelectedState(true, at: indexPath)
if let _ = tableView.cellForRow(at: indexPath), let _ = tableView.cellForRow(at: selectedIndexPath) {
self.reloadTableViewRows([selectedIndexPath, indexPath])
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
else if !model.isSelected {
self.updatemodelSelectedState(true, at: indexPath)
if let _ = tableView.cellForRow(at: indexPath) {
self.reloadTableViewRows([indexPath])
}
}
selectedIndexPath = indexPath
}
private func reloadTableViewRows(_ indexPaths: [IndexPath]) {
DispatchQueue.main.async {
self.myTableView.beginUpdates()
self.myTableView.reloadRows(at: indexPaths, with: .automatic)
self.myTableView.endUpdates()
}
}
Xcode console log(I am only getting this one liner) -
libc++abi.dylib: terminating with uncaught exception of type
NSException
It crashes at self.myTableView.endUpdates() this line. If I remove begin and end updates then it crashes at self.myTableView.reloadRows(at: indexPaths, with: .automatic). Also for one of the row I show a popup on a button click and after then if I select my first row, it crashes!
Also I have collection view inside table view rows which I am hiding and showing. Even though its flow is set to horizontal, after multiple clicks once in a while I notice that collection view rows comes in vertical flow and then in next click it gets back to horizontal flow again. Not sure why this is happening but could this be issue?
Please help. I am trying to resolve this issue from last 2-3 hours :(
Try This one i hope it will be work for you:
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath!], with: .none)
tableView.endUpdates()
UIView.setAnimationsEnabled(true)

How to Use Table View Cell Outside Table View Function

i need to change my image in cell using view will appear. But i can't use my cell in view will appear here what i've done
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
let cell: HomeCellTableViewCell = self.tableCity.dequeueReusableCell(withIdentifier: "Cell") as! HomeCellTableViewCell
if session == nil {
print("first")
cell.iconForDownload.setImage(UIImage(named: "ic_download"), for: .normal)
} else {
print("second")
cell.iconForDownload.setImage(UIImage(named: "ic_next_green"), for: .normal)
}
}
it print "first" but the image still didn't change
in my cellForRowAt :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: HomeCellTableViewCell = self.tableCity.dequeueReusableCell(withIdentifier: "Cell") as! HomeCellTableViewCell
let city = listData[indexPath.row]
cell.labelNameCity.text = city.region
cell.labelNameCityJpn.text = city.regionJpn
let stringImage = config.BASE_URL+city.imgArea
let url = URL(string: stringImage.replacingOccurrences(of: " ", with: "%20"))
urlDownload = config.BASE_URL+kota.urlDownload
urlDownloadFinal = URL(string: urlDownload.replacingOccurrences(of: " ", with: "%20"))
if session == nil {
cell.imageCity.kf.setImage(with: url)
cell.iconForDownload.setImage(UIImage(named: "ic_download"), for: .normal)
} else {
cell.imageCity.kf.setImage(with: url)
cell.iconForDownload.setImage(UIImage(named: "ic_next_green"), for: .normal)
}
return cell
}
You need to use cellForRow(at:) to get the cell it will return optional UITableViewCell so use if let or guard let to wrapped the optional.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
let indexPath = IndexPath(row: 0, section: 0) //Set your row and section
if let cell = tableView.cellForRow(at: indexPath) as? HomeCellTableViewCell {
//access cell
}
}
Note: The batter approach is to set your datasource array and simply reload the affected table rows using reloadRows(at:with:).
let indexPath = IndexPath(row: 0, section: 0) //Set your row and section
self.tableView.reloadRows(at: [indexPath], with: .automatic)

SWIFT 3 and Firebase using tableEdit mode to insert data to firebase which updates my table array for tableview

SWIFT 3 and Firebase - I'm trying to use the UITable inEditing mode to insert cells into firebase and have a firebase listener that updates my arraty. My insertions to fire base are fine, but I keep getting an index out of range when I try to do the insertion with the new add button i created. Has anyone used Firebase with UITable inEditing mode to insert into a table and update firebase.
This is what I looking for:
enter image description here
The problem maybe be how I wrote the cellForAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("zzz indexPath index path row: \(indexPath.row)")
print("zzz indexPath name \(myPins[indexPath.row].pinName)")
print("ZZZ myPins Count \(myPins.count)")
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
} else {
//let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
}
return cell
}
i get this error message: fatal error: Index out of range
Here is the rest of the table set up code:
//When the use press the editing button it will make the table enditable
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
//if the viw is in editing mode then we will set the table to be in enditing mode.
if isEditing {
//This is adding the addtional cell in for add place
tableView.beginUpdates()
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
// self.tableView.insertRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
tableView.setEditing(true, animated: true)
} else {
//when the 'Done' is pressed this will take away the add button
tableView.beginUpdates()
let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
self.tableView.deleteRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
//otherwise the table will not be in editing mode.
tableView.setEditing(false, animated: false)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//STEP 1 for adding a row that will fuction as add place
//If we are in editing mode a 1 will be return and the extra cell will be added
let adjustment = isEditing ? 1 : 0
//add the extra cell if we are in editing mode
return myPins.count + adjustment
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
//addCell.addButton.tag = indexPath.row
//addCell.addButton.addTarget(self, action: "addPlace", for: .touchUpInside)
} else {
// let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
//this is the info I want to dispaly in the cell
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
return cell
}
//add the green plus button icon for the extra row added
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
// let indexRow = IndexPath(item: 0, section: 0)
if indexPath.row == 0 {
return .insert
}
return .delete
}
//delete with a with a swipe
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
print("pins count before deleate\(myPins.count)")
if editingStyle == .delete {
let pin = myPins[indexPath.row]
guard let currentUser = FIRAuth.auth()?.currentUser?.uid else {return}
rootRef.child(child.users.rawValue).child(currentUser).child(users.myPins.rawValue).child(plotToEdit!.plotID).child(pin.placeID).removeValue()
}
//When you press on the green plus button as appose to the row.
else if editingStyle == .insert {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
}
//When in ending more I can slect the first row that is add places
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
// let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
if isEditing && (indexPath.row > 0) {
return nil
}
return indexPath
}

Swift: How to reload row height in UITableViewCell without reloading data

I have a case in which I have to reload only height of a UITableViewCell.
but if I call the function
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: webView.tag, inSection: 0)], withRowAnimation: .Automatic)
it reloads the height as well as the data of the cell.
How can i just control the height of cell in Swift?
This is my cellForRow block :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! CustomTableViewCell1
cell.heading.text = headerText
return cell
}
else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath) as! CustomTableViewCell2
ImageLoader.sharedLoader.imageForUrl(self.headerImage , completionHandler:{(image: UIImage?, url: String) in
cell.mainImage.image = image
})
return cell
}
else if indexPath.row == 2 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell3", forIndexPath: indexPath) as! CustomTableViewCell3
//cell.aurthorImage.image = UIImage(named : "obama")
ImageLoader.sharedLoader.imageForUrl(self.headerImage , completionHandler:{(image: UIImage?, url: String) in
cell.aurthorImage.image = image
})
cell.aurthorImage.tag = aurthorID
cell.aurthorImage.layer.cornerRadius = cell.aurthorImage.frame.height/2
cell.aurthorImage.clipsToBounds = true
cell.aurthorImage.userInteractionEnabled = true
cell.aurthorImage.addGestureRecognizer(aurthorImageTapRecignizer)
cell.aurthorName.text = self.authorName
cell.time.text = self.time
self.followButton = cell.followButton
return cell
}
else if indexPath.row == 3 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell4", forIndexPath: indexPath) as! CustomTableViewCell4
let htmlHeight = contentHeights[indexPath.row]
cell.webElement.tag = indexPath.row
cell.webElement.delegate = self
cell.webElement.loadHTMLString(HTMLContent, baseURL: nil)
cell.webElement.frame = CGRectMake(0, 0, cell.frame.size.width, htmlHeight)
return cell
}
else if indexPath.row == 4 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! CustomTableViewCell1
cell.heading.text = "Related Posts"
return cell
}
else if indexPath.row == 5{
let cell = tableView.dequeueReusableCellWithIdentifier("cell6", forIndexPath: indexPath) as! CustomTableViewCell6
return cell
}
else if indexPath.row == 6 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! CustomTableViewCell1
cell.heading.text = "Comments"
return cell
}
else if indexPath.row == 7 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell5", forIndexPath: indexPath) as! CustomTableViewCell5
let htmlHeight = contentHeights[indexPath.row]
self.commentSection = cell.commentsView
self.commentSection.tag = indexPath.row
self.commentSection.delegate = self
let url = NSURL(string: commentsURL)
let requestObj = NSURLRequest(URL: url! )
self.commentSection.loadRequest(requestObj)
self.commentSection.frame = CGRectMake(0, 0, cell.frame.size.width, htmlHeight)
commentSectionDidNotLoad = false
return cell
}
else {
let cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! CustomTableViewCell1
cell.heading.text = headerText
return cell
}
You can use this code to update the cell's height without reloading their data:
tableView.beginUpdates()
tableView.endUpdates()
You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell.
UITableView Class Reference
start an update of the tableview and then end it without changing anything.
tableView.beginUpdates()
tableView.endUpdates()
This should make the tableview set the new height
You can regulate the height by either implementing the (a)heightForRowAtIndexPath with logic setting the heights or (b)with auto layout and automatic tableview row height
A.
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
if [your condition, row == 5 in your comment] {
return 100
} else {
return 40
}
}
Whenever you want to change the height you would just call these two rows
tableView.beginUpdates()
tableView.endUpdates()
B.
in viewDidLoad
tableView.estimatedRowHeight = 40.0
tableView.rowHeight = UITableViewAutomaticDimension
and then you can just expose the layout constraint that set's the cells height and access it to set the height when you want
cell.heightConstraint.constant = 100
tableView.beginUpdates()
tableView.endUpdates()
About your question for example, to give a specific height dimension :
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 3 {
return 50.0
}
return 72.0
}
But I think you have a webView inside a cell so, generally, to calculate the dynamic height of a UITableViewCell with a UIWebView:
(this example have two webViews)
class MyTableViewController: UITableViewController, UIWebViewDelegate
{
var content : [String] = ["<!DOCTYPE html><html><head><title>Page Title</title></head><body><h1>My First Heading</h1><p>My first paragraph</p></body></html>", "<HTML><HEAD><TITLE>Coca-Cola</TITLE></HEAD><BODY>In Chinese, Coca-Cola means Bite the Wax Tadpole</BODY></HTML>"]
var contentHeights : [CGFloat] = [0.0, 0.0]
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCell", forIndexPath: indexPath) as! MyCustomCell
let htmlString = content[indexPath.row]
let htmlHeight = contentHeights[indexPath.row]
cell.webView.tag = indexPath.row
cell.webView.delegate = self
cell.webView.loadHTMLString(htmlString, baseURL: nil)
cell.webView.frame = CGRectMake(0, 0, cell.frame.size.width, htmlHeight)
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return contentHeights[indexPath.row]
}
func webViewDidFinishLoad(webView: UIWebView)
{
if (contentHeights[webView.tag] != 0.0)
{
// height knowed, no need to reload cell
return
}
contentHeights[webView.tag] = webView.scrollView.contentSize.height
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: webView.tag, inSection: 0)], withRowAnimation: .Automatic)
}
}
You can use the view height constraint in the cell, by updating the view in cell height you can update the specific cell height.
let height = cell.Height_constraint.constant
cell.Height_constraint.constant = height + 200 //200 you can use any number
Height_constraint: is the height constraint of subView in my cell.

Reload UITableView has UITableViewCell visibility issue

I have a UITableView with two row. First row have the UITextField and second row have the UICollectionView. On reloading the UITableView, cell of UICollectionViewCell does not appear.But on scrolling the UITableView all cell of UICollectionView becomes visible. What wrong I might be doing.
if indexPath.row == 0 {
let cellIdentifier = "NewPostCell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? NewPostCell
if (cell == nil) {
cell = Utils.getCellForGivenIdentifier(cellIdentifier, cellIdentifier: cellIdentifier, ownerT: self) as? NewPostCell
}
cell?.txtPost.delegate = self
cell?.txtPost.text = userThoughts
newPostCell = cell
return cell!
} else {
let cellIdentifier = "ASAttachmentCell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? ASAttachmentCell
if (cell == nil) {
cell = Utils.getCellForGivenIdentifier(cellIdentifier, cellIdentifier: cellIdentifier, ownerT: self) as? ASAttachmentCell
}
if let height = cell?.collectionAttachment?.contentSize.height
{
cell?.cntCollectionHeight.constant = height
}
attachmentCell = cell
return cell!
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 1 {
attachmentCell = cell as? ASAttachmentCell
attachmentCell?.attachmentCollection(self)
}
}
Try to put in your code, exactly to the part where you set the collection view datasource also a :
collectionView.reloadData()
This is most likely because you are calling tableView.reloadData() on a background thread. Therefore, when you actually begin to scroll, the UI will get updated on the main thread and everything will look normal. Try calling the reloading on the main thread.
dispatch_async(dispatch_get_main_queue()){
self.tableView.reloadData()
}