UITableView reload data flashes for a second - ios

I am using pagination on UITableView, after the first load when the next items is about to come or as I reload the tableView it flashes for a second and then comes to it's last position. But it does not happen every time having a hard time solving this out. Anyone can help me please?
Here is the code I am using to reload the data in the tableView
func handleWSSuccessResponse(_ arrRestaurant:[Restaurant],filterBy:filteringByValues){
let offSet = self.IBtblUserFeed.contentOffset
print(arrRestaurant.count)
if (self.arrRestaurants.count > 0) {
if arrRestaurant.count > 0 && arrRestaurant.count <= 10 {
if arrRestaurant.count % 10 != 0 {
isDataLoading = false
actInd.stopAnimating()
}
if !(self.isFromRefresh) {
self.arrRestaurants = self.arrRestaurants + arrRestaurant
}
DispatchQueue.main.async {
if (self.isFromRefresh) {
self.IBtblUserFeed.reloadData()
} else {
UIView.setAnimationsEnabled(false)
self.IBtblUserFeed.reloadData()
UIView.setAnimationsEnabled(true)
self.IBtblUserFeed.layoutIfNeeded()
self.IBtblUserFeed.setContentOffset(offSet, animated: false)
}
}
} else {
isDataLoading = false
DispatchQueue.main.async {
if (self.isFromRefresh) {
self.IBtblUserFeed.reloadData()
} else {
UIView.setAnimationsEnabled(false)
self.IBtblUserFeed.reloadData()
self.IBtblUserFeed.layoutIfNeeded()
self.IBtblUserFeed.setContentOffset(offSet, animated: false)
UIView.setAnimationsEnabled(true)
}
}
}
} else {
self.arrRestaurants = arrRestaurant
if arrRestaurant.count % 10 != 0 {
isDataLoading = false
actInd.stopAnimating()
}
if self.arrRestaurants.count == 0 {
self.IBtblUserFeed.tableHeaderView = nil
self.IBtblUserFeed.tableHeaderView?.isHidden = true
} else {
if filterBy.isFiltering{
} else {
self.IBtblUserFeed.tableHeaderView = self.header()
}
self.IBtblUserFeed.tableHeaderView?.isHidden = false
}
if filterBy.isFiltering{
self.reloadTableView()
// tejas changes
// filterRestaurant(filterBy: filterBy)
}else{
self.reloadTableView()
}
}
}

Your example code is too cluttered with unrelated things to give a solid answer; however, I think that the problem is related to the use of reloadData; you should instead use insertRows(at:with:) to get smoothly appended rows.
So whenever you append restaurant model objects to your array (from the code it looks like you never apply other changes like modifications or removals), generate the index paths of the new rows ...
let indexPaths = (countBeforeAdding..<countAfterAdding).map { IndexPath.init(row: $0, section: 0) }
... and insert them into the table view:
tableView.beginUpdates()
tableView.insertRows(at: indexPaths, with: .automatic)
tableView.endUpdates()

Related

UITableView scrolling performance problem

I am currently working as a 5 month junior ios developer.
The project I'm working on is an application that shows the prices of 70 cryptocurrencies realtime with websocket connection.
we used websocket connection, UItableview, UITableViewDiffableDataSource, NSDiffableDataSourceSnapshot while developing the application.
But right now there are problems such as slowdown scrolling or not stop scroling and UI locking while scrolling in the tableview because too much data is processed at the same time.
after i check cpu performance with timer profiler I came to the conclusion that updateDataSource and updateUI functions exhausting the main thread.
func updateDataSource(model: [PairModel]) {
var snapshot = DiffableDataSourceSnapshot()
let diff = model.difference(from: snapshot.itemIdentifiers)
let currentIdentifiers = snapshot.itemIdentifiers
guard let newIdentifiers = currentIdentifiers.applying(diff) else {
return
}
snapshot.appendSections([.first])
snapshot.deleteItems(currentIdentifiers)
snapshot.appendItems(newIdentifiers)
dataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
}
func updateUI(data: SocketData) {
guard let newData = data.data else { return }
guard let current = data.data?.price else { return }
guard let closed = data.data?.lastDayClosePrice else { return }
let dailyChange = ((current - closed)/closed)*100
DispatchQueue.main.async { [self] in
if model.filter({ $0.symbol == newData.pairSymbol }).first != nil {
let index = model.enumerated().first(where: { $0.element.symbol == newData.pairSymbol})
guard let location = index?.offset else { return }
model[location].price = current
model[location].dailyPercent = dailyChange
if calculateLastSignalTime(alertDate: model[location].alertDate) > 0 {
//Do Nothing
} else {
model[location].alertDate = ""
model[location].alertType = ""
}
if let text = allSymbolsView.searchTextField.text {
if text != "" {
filteredModel = model.filter({ $0.name.contains(text) || $0.symbol.contains(text) })
updateDataSource(model: filteredModel)
} else {
filteredModel = model
updateDataSource(model: filteredModel)
}
}
}
delegate?.pricesChange(data: self.model)
}
}
Regards.
ALL of your code is running on the main thread. You have to wrap your entire updateUI function inside a DispatchQueue.global(qos:), and then wrap your dataSource.apply(snapshot) line inside a DispatchQueue.main.async. the dataSource.apply(snapshot) line is the only UI work you're doing in all that code you posted.

How to update my xib labels from another view controller

I am trying to figure out how to update my xib labels when a button is pressed on another view controller
Currently I have a xib swift file that contains all my labels
var habit: Habit? {
didSet {
self.updateUI()
}
}
func updateUI() {
habitXibName?.text = habit?.title
totalProgress?.text = habit?.totalCount
progressBarXib?.progress = habit?.progress ?? 0.0
userProgress?.text = String(habit?.userCount ?? 0)
}
In another view controller I have the following code
do {
try realm.write {
if selectedHabit!.userCount == (Int(selectedHabit!.totalCount )! - 1) {
selectedHabit!.userCount += 1
habitcell.habitXibName?.text = String(selectedHabit?.userCount ?? 0)
view.addSubview(confettiView)
confettiView.startConfetti()
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.confettiView.stopConfetti()
}
} else {
selectedHabit!.userCount += 1
}
}
} catch {
print(error)
}
what I want to happen is to update my xib label to the selectedHabit?.userCount
habitcell.habitXibName?.text = String(selectedHabit?.userCount ?? 0)
However, my habitcell.habitXibName returns nil and I'm not sure why. Thanks for everything!

Tableview sliding up after scroll to row

Hey guys I need your help please, when I reload a tableview and call the scroll to row function for the bottom row (array.length - 1) as index path, it scrolls to it, then it scrolls one row up. I have made a cool chat part to my app, and it is simple, I have a function that grabs messages, and for some reason on my iPhone simulator, it does this weird scrolling back up motion. Basically I was hoping one of you guys could help me find out why it is doing that. It is some simple code.
main function:
func grabMessages () {
if let uid = Auth.auth().currentUser?.uid {
if let theirId = theirUid {
let ref = Database.database().reference()
ref.child("users").child(uid).child("chats").child(theirId).child("messages").observe(.value, with: {(snapshot) in
var reloadi = false
if let values = snapshot.value as? [String : AnyObject] {
for (_, one) in values {
if let whoSent = one["sender"] as? String, let messagl = one["message"] as? String, let timerl = one["timeStamp"] as? Int, let keyer = one["key"] as? String {
let newMess = Message()
print("googd")
newMess.key = keyer
newMess.timeStamp = timerl
newMess.messager = messagl
newMess.sender = whoSent
if self.messages.contains( where: { $0.key == newMess.key } ) {
} else {
self.messages.append(newMess)
if self.messages.count != 0 {
self.messages.sort { $1.timeStamp > $0.timeStamp }
reloadi = true
}
if newMess.sender == theirId {
let update = ["unseen" : "iViewed"]
ref.child("users").child(theirId).child("chats").child(uid).updateChildValues(update)
}
}
}
if reloadi == true {
reloadi = false
DispatchQueue.main.async {
self.tablerView.reloadData()
let indexPat = IndexPath(row: 0, section: self.messages.count - 1)
self.tablerView.isHidden = false
self.tablerView.scrollToRow(at: indexPat, at: .bottom, animated: false)
}
}
}
}, withCancel: nil)
}
}
}
I will say in my viewdidload I set the frame of the tableview, which is just a cgrect and same size across all platform, fixed width and height. And on my own iPhone it runs and works fine, only on simulator iPhones did it do this.
Here is a screen shot
https://imgur.com/a/ZAhbJ
So if you can see, it shows other section below it, but it is scrolled to the one above it. So it originally scrolls to it, then scrolls back up one section.
some other stuff
numb of sections = messages.count
number of rows in section = 1

UILabel text in table view cell disappearing on scroll even when set in didSet block

My problem: the text on some of my UILabels (the answers for the questions) in a particular cell are blank when I scroll down (only the 3rd through 7th labels, as you'll see in my code the first and second are always set and seem to magically work no matter what). It only happens for some cells, for most cells when they render the first time it's correct (not always, though).
I've been struggling with this for a few hours now and can't seem to fix it. I'm pretty sure it has to do with table cell reuse, but I set the data in a didSet block in my custom table cell and even tried the prepareForReuse() override as suggested here: Stop the reuse of custom cells Swift. Nothing seems to work, I still have some cells (only some, not all!) that lose some of their text when I scroll through my tableView. What am I missing?
Here is the didSet block for my custom cell class QuestionCustomCell (and if it helps, the NSLog printout does show the correct text that should be in the cell when I scroll down, but it doesn't render):
var cellQuestion: Question! {
didSet {
questionLabel.text = cellQuestion.topQuestion
questionLabel.sizeToFit()
//definitely at least 2 answers
switchLabel1.text = cellQuestion.theResponses[0]
switchLabel2.text = cellQuestion.theResponses[1]
if(cellQuestion.theResponses[2] == nil) {
switch3.isHidden = true
switchLabel3.isHidden = true
} else {
switch3.isHidden = false
switchLabel3.text = cellQuestion.theResponses[2]
let questionName : String = cellQuestion.topQuestion
NSLog("question: "+questionName+" response: "+cellQuestion.theResponses[2]!)
}
if(cellQuestion.theResponses[3] == nil) {
switch4.isHidden = true
switchLabel4.isHidden = true
} else {
switch4.isHidden = false
switchLabel4.text = cellQuestion.theResponses[3]
let questionName : String = cellQuestion.topQuestion
NSLog("question: "+questionName+" response: "+cellQuestion.theResponses[3]!)
}
if(cellQuestion.theResponses[4] == nil) {
switch5.isHidden = true
switchLabel5.isHidden = true
} else {
switch5.isHidden = false
switchLabel5.text = cellQuestion.theResponses[4]
let questionName : String = cellQuestion.topQuestion
NSLog("question: "+questionName+" response: "+cellQuestion.theResponses[4]!)
}
if(cellQuestion.theResponses[5] == nil) {
switch6.isHidden = true
switchLabel6.isHidden = true
} else {
switch6.isHidden = false
switchLabel6.text = cellQuestion.theResponses[5]
let questionName : String = cellQuestion.topQuestion
NSLog("question: "+questionName+" response: "+cellQuestion.theResponses[5]!)
}
if(cellQuestion.theResponses[6] == nil) {
switch7.isHidden = true
switchLabel7.isHidden = true
} else {
switch7.isHidden = false
switchLabel7.text = cellQuestion.theResponses[6]
let questionName : String = cellQuestion.topQuestion
NSLog("question: "+questionName+" response: "+cellQuestion.theResponses[6]!)
}
restorePriorAnswer()
}
}
And here is restorePriorAnswer() (I use it so the cells remember whether the switch was off or on when reused - that does work):
func restorePriorAnswer ( )
{
if(cellQuestion.theAnswer != nil)
{
self.cellDelegate.restorePrior(forCell: self, forShortName: cellQuestion.shortName, subAction: "\(cellQuestion.theAnswer!.answerSelect)")
print("Prior answer was \(cellQuestion.theAnswer!.answerSelect)")
switch(cellQuestion.theAnswer!.answerSelect)
{
case 0:
switch1.isOn = true
break
case 1:
switch2.isOn = true
break
case 2:
switch3.isOn = true
break
case 3:
switch4.isOn = true
break
case 4:
switch5.isOn = true
break
case 5:
switch6.isOn = true
break
case 6:
switch7.isOn = true
break
default:
self.cellDelegate.restorePrior(forCell: self, forShortName: "Control", subAction: "restorePriorAnswer (Bad Select Value)")
break
}
}
else
{
switch1.setOn(false, animated: false)
switch2.setOn(false, animated: false)
switch3.setOn(false, animated: false)
switch4.setOn(false, animated: false)
switch5.setOn(false, animated: false)
switch6.setOn(false, animated: false)
switch7.setOn(false, animated: false)
}
}
And here is my cellForRowAtIndexPath:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:QuestionCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuse, for: indexPath) as! QuestionCustomCell
if((currentQuestion?.canSubsume())!) {
if((currentQuestion?.shouldDoSubsume())!) {
//since this apparently refreshes random rows, we need to hard-code which row goes where
if(indexPath.row == 0) {
cell.cellQuestion = currentQuestion
} else {
let rowQuestion: Question = (currentQuestion?.getSubsumeList().getQuestion(shortID: currSubsumeArray![indexPath.row - 1]))!
cell.cellQuestion = rowQuestion
}
} else {
//there should only be one cell at this point - the first one
cell.cellQuestion = currentQuestion
//print out the first question, just to make sure...
let logStringOne : String = "the current question: " + (currentQuestion?.topQuestion)!
NSLog(logStringOne)
let logString : String = "the first subsumed question: " + (currentQuestion?.subsumeList!.firstQuestion)!
NSLog(logString)
}
} else {
cell.cellQuestion = currentQuestion
}
cell.selectionStyle = UITableViewCellSelectionStyle.none
cell.cellDelegate = self
return cell
}
Please let me know if I'm missing any code you think is helpful to solve the problem. Everything else works besides the labels - the switches remember if they were on or off when scrolling back and forth, the appropriate switches are hidden if their appropriate slot in the answers array is nil, and all other text is as it should be - except some of the labels for the answers, which is the problem. As I mentioned, I tried the prepareForReuse() method but it didn't do anything, so I took it out.
I apologize, it was staring me in the face for several hours and I didn't realize it. In the didSet block I had hidden the labels if they weren't supposed to be used, like this:
if(cellQuestion.theResponses[2] == nil) {
switch3.isHidden = true
switchLabel3.isHidden = true
} else {
switch3.isHidden = false
switchLabel3.text = cellQuestion.theResponses[2]
}
but in the else block, I didn't unhide them...when a cell was loaded fresh and not reused, it had no issue because the label had never been hidden previously. But in a reused cell, it was, and so the label didn't exist for those cells that were reused.
So, each of these if/else blocks should have a switchLabelx.isHidden = falsein them, with x being the appropriate number. Not that I wish staring at code for hours on anyone, but hopefully someone will find this useful.

UISegmentedControl and .selectedSegmentIndex wrong values?

I have a simple Segment in my code with 3 elements. For testing purposes I also do have a variable that increments based on which of the segments I press (3). The value of that variable is printed in a UITextView. This is the code:
import UIKit
class ViewController: UIViewController
{
#IBOutlet weak var segment: UISegmentedControl!
#IBOutlet weak var prwtoView: UIView!
#IBOutlet weak var prwtoText: UITextField!
var i : Int = 0
override func viewWillAppear(animated: Bool)
{
prwtoText.backgroundColor = UIColor.purpleColor()
prwtoText.textColor = UIColor.whiteColor()
segment.setTitle("Zero", forSegmentAtIndex: 0)
segment.addTarget(self, action: "action", forControlEvents: .ValueChanged)
segment.insertSegmentWithTitle("random", atIndex: 2, animated: false)
}
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
}
While I know that it starts with value -1 i don't want my app to do anything if not pressed. The thing is that even when I press the first segment (0) and it is supposed to make i = 0 it doesn't do that, although if I print argumentForSegment in my terminal it does show the 0 as value. Concluding, every time I press the zero segment (0), my i value won't become 0. Perhaps I am using the wrong method from UISegmentedControl?
edit: Got it fixed by changing the following code:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
to:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else if argumentForSegment == 2 // <==== here
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
Could someone explain why it used the priority of else although the value was zero when printing argumentForSegment? In other words why when I had an else alone for the value of argumentForSegment == 0 it chose the else instead of the first statement?
Could someone explain why it used the priority of else although the
value was zero when printing argumentForSegment? In other words why
when I had an else alone for the value of argumentForSegment == 0 it
chose the else instead of the first statement?
When you have a situation where the code is not behaving as you expect, it is helpful to step through it in the debugger, or add some diagnostic print statements.
For example:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
print("In first block")
i = 0
}
if argumentForSegment == 1
{
print("In second block")
i += 2
}
else
{
print("In third block")
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
If you do this, you will notice that when argumentForSegment is 0, the output will be:
In first block
In third block
So, the problem is not that it is choosing the third block over the first. The problem is that it is doing both. You want it to stop after it has detected that argumentForSegment is 0, so add an else to the second conditional statement so that it only does that when the first conditional statement failed:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
else if argumentForSegment == 1 // added "else" here
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
To improve on Vacawama's answer, you can format this much easier by using a switch statement:
func action() {
let argumentForSegment = segment.selectedSegmentIndex
switch argumentForSegment {
case 0:
i = 0
case 1:
i += 1
case 2:
i += Int(arc4random_uniform(100))
default:
break
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
it's much more clean for this type of thing.
(thanks, vacawama)

Resources