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
Related
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.
I'm using MZDownloadManger library to download the files using url, every thing is working fine except the label update, when i start the downloading it changes to "Starting Downloading" then starts its progress like 10% 20% etc. its working fine but when i move to any other view controller its progress stops and not update the label value to "Downloaded". i have set a flag in my local data base '0' and '1', 0 means not downloaded and 1 means downloaded.
here is the code when a user select the cell and hit for download:
func keepOfflineFiles(sender: UIButton) {
if files[sender.tag].onLocal == "1"{
self.displayAlert(title: AlertTitle.alert, message: AlertMsg.alreadyDownloaded)
} else{
if self.files[sender.tag].status == "on amazon"{
let indexPath = IndexPath.init(row: sender.tag, section: 0)
let cell = self.tblFilesPro.cellForRow(at: indexPath)
if let cell = cell {
let downloadCell = cell as! filesTableViewCell
downloadCell.lblDetailsPro.text = "Starting Download. . ."
}
let pathString:String = ""
let fileName:String = self.files[sender.tag].file_id! + self.files[sender.tag].Extension!
if(fileName != ""){
let local_url = NSURL(fileURLWithPath: pathString.getDocumentsPath())
let filePath = local_url.appendingPathComponent(fileName)?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
// FILE AVAILABLE
let indexPath = IndexPath.init(row: sender.tag, section: 0)
let cell = self.tblFilesPro.cellForRow(at: indexPath)
if let cell = cell {
let downloadCell = cell as! filesTableViewCell
downloadCell.lblDetailsPro.text = "Downloaded"
}
self.fileid = self.files[sender.tag].file_id!
self.updateFileStatusToRealm()
//self.displayAlert(title: AlertTitle.alert, message: AlertMsg.alreadyDownloaded)
} else {
// FILE NOT AVAILABLE
let completeUrl:String = Tray.downloadURLBasePath + self.files[sender.tag].fileLink!
if(self.verifyUrl(urlString: completeUrl)) {
self.fileid = self.files[sender.tag].file_id!
let index:String = String(sender.tag)
self.AppDelegateObj.downloadManager.addDownloadTask(fileName as String, fileURL: completeUrl as String, destinationPath: index as String)
}
}
}
}else{
self.displayAlert(title: AlertTitle.alert, message: AlertMsg.inArchiveProcess)
}
}
}
here are the delegates of MZDownloadManager that i called in AppDelegate
To update the progress
func downloadRequestDidUpdateProgress(_ downloadModel: MZDownloadModel, index: Int) {
let root : UINavigationController = self.window?.rootViewController as! UINavigationController
if let master = root.topViewController as? TabBarController {
if let nav = master.viewControllers?[0] as? FilesVC {
nav.refreshCellForIndex(downloadModel, index: Int(downloadModel.destinationPath)!)
}
} else {
print("Somthing went wrong while downloading this file.")
}
}
When downloading finished
func downloadRequestFinished(_ downloadModel: MZDownloadModel, index: Int) {
let root : UINavigationController = self.window!.rootViewController! as! UINavigationController
if let master = root.topViewController as? TabBarController {
if let nav = master.viewControllers![0] as? FilesVC{
nav.getDownloadingStatusOfCellForIndex(downloadModel, index: Int(downloadModel.destinationPath)!)
}
} else {
print("Somthing went wrong while finishing downloading of this file.")
}
}
Method to refresh the cell label
func refreshCellForIndex(_ downloadModel: MZDownloadModel, index: Int) {
let indexPath = IndexPath.init(row: index, section: 0)
let cell = self.tblFilesPro.cellForRow(at: indexPath)
if let cell = cell {
let downloadCell = cell as? filesTableViewCell
downloadCell?.updateCellForRowAtIndexPath(indexPath, downloadModel: downloadModel)
}
}
Method to get the cell and change value
func getDownloadingStatusOfCellForIndex(_ downloadModel: MZDownloadModel, index: Int) {
let indexPath = IndexPath.init(row: index, section: 0)
let cell = self.tblFilesPro.cellForRow(at: indexPath)
if let cell = cell {
let downloadCell = cell as? filesTableViewCell
downloadCell?.lblDetailsPro.text = "Downloaded"
self.fileid = self.files[index].file_id!
self.updateFileStatusToRealm()
}
}
here is the method which change the flag value 0 to 1 in database:
func updateFileStatusToRealm(){
let fileToUpdate = uiRealm.objects(filesDataTable.self).filter("file_id = %#", self.fileid)
let realm = try! Realm()
if let file = fileToUpdate.first {
try! realm.write {
file.onLocal = "1"
tblFilesPro.reloadData()
}
}
}
im having a real nightmare with my project where i need to save cell contents to an object, for each object in an array. I cant get this to work by looping through table cells adn array objects and trying to match them all up.
So my next idea was to add didFinishEditing related functions into the cellForRowAt function?
Im not sure this would work either, but this is what i have:
Each row here has a label for the set, a picker for the reps that can be scrolled to a number, and a textfield to put a weight. Then i save each row as an object storing the set, rep and weight.
Issue is when editing this, how can i save these again overwriting the old values? Hence my plan above to use didFinishEditing methods.
My previous plan was the code below, but i cant figure out the annotated part. So i was hoping someone had guidance on how i can approach saying when editing rather than this save button function that doesnt work!
func saveUserExerciseSets() {
if userExercise == nil {
print("CREATING A FRESH SET OF SETS FOR THE NEW EXERCISE")
for cell in self.customSetsTable.visibleCells as! Array<NewExerciseTableViewCell> {
print("SAVING THESE CELLS \(customSetsTable.visibleCells)")
let newUserExerciseSet = UserExerciseSet(context: self.managedObjectContext)
newUserExerciseSet.setPosition = Int64(cell.setNumber.text!)!
newUserExerciseSet.setReps = Int64(cell.repsPicker.selectedRow(inComponent: 0))
newUserExerciseSet.parentExerciseName = self.userExerciseName.text
if self.localeIdentifier == "en_GB" {
let kgWeight = Measurement(value: Double(cell.userExerciseWeight.text!)!, unit: UnitMass.kilograms)
newUserExerciseSet.setWeight = kgWeight as NSObject?
newUserExerciseSet.initialMetricSystem = self.localeIdentifier
} else if self.localeIdentifier == "en_US" {
let lbsWeight = Measurement(value: Double(cell.userExerciseWeight.text!)!, unit: UnitMass.pounds)
newUserExerciseSet.setWeight = lbsWeight as NSObject?
newUserExerciseSet.initialMetricSystem = self.localeIdentifier
}
let fetchRequest: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %#", self.exerciseNameToAddTo!)
do {
let parentExercise = try self.managedObjectContext.fetch(fetchRequest).first
parentExercise?.addToExercisesets(newUserExerciseSet)
print("SET ADDED TO EXERCISE")
} catch {
print("Fetching Routine Failed")
}
}
} else if self.userExercise != nil {
print("UPDATING EXISTING SETS FOR THE EXISTING EXERCISE")
let cells = self.customSetsTable.visibleCells as! Array<NewExerciseTableViewCell>
for cell in cells {
let exerciseSets = self.userExercise?.exercisesets?.allObjects as! [UserExerciseSet]
let sortedexerciseSets = exerciseSets.sorted { ($0.setPosition < $1.setPosition) }
let cellsSet = sortedexerciseSets //match the sortedexerciseSets set object to the cell index positions
cellsSet.setPosition = Int64(setsCell.setNumber.text!)!
cellsSet.setReps = Int64(setsCell.repsPicker.selectedRow(inComponent: 0))
if self.localeIdentifier == "en_GB" {
let kgWeight = Measurement(value: Double(cell.userExerciseWeight.text!)!, unit: UnitMass.kilograms)
cellsSet.setWeight = kgWeight as NSObject?
} else if self.localeIdentifier == "en_US" {
let lbsWeight = Measurement(value: Double(cell.userExerciseWeight.text!)!, unit: UnitMass.pounds)
cellsSet.setWeight = lbsWeight as NSObject?
}
cellsSet.parentExerciseName = self.userExerciseName.text
}
}
do {
try self.managedObjectContext.save()
print("THE SET HAS BEEN SAVED")
} catch {
fatalError("Failure to save context: \(error)")
}
delegate?.didFinishEditing()
self.dismiss(animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? NewExerciseTableViewCell
else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
cell.repsPicker.dataSource = self
cell.repsPicker.delegate = self
configure(cell, at: indexPath)
return cell
}
func configure(_ cell: NewExerciseTableViewCell, at indexPath: IndexPath) {
// configuring cells when theres a loaded exercise causes the issues --------------------
if self.userExercise != nil {
print("RESTORING CELLS FOR THE EXISTING EXERCISE")
let unsortedExerciseSets = self.userExercise?.exercisesets?.allObjects as! [UserExerciseSet]
let exerciseSets = unsortedExerciseSets.sorted { ($0.setPosition < $1.setPosition) }
let cellsSet = exerciseSets[indexPath.row]
cell.setNumber.text = String((indexPath.row) + 1)
let indexRow = Int(cellsSet.setReps)
print("INDEX ROW INT IS \(indexRow)")
cell.repsPicker.selectRow(indexRow, inComponent: 0, animated: true) //fix this crashing issue!
let localeIdentifier = Locale(identifier: UserDefaults.standard.object(forKey: "locale") as! String)
let setWeight = cellsSet.setWeight as! Measurement<UnitMass>
let formatter = MassFormatter()
formatter.numberFormatter.locale = localeIdentifier
formatter.numberFormatter.maximumFractionDigits = 2
if localeIdentifier.usesMetricSystem {
let kgWeight = setWeight.converted(to: .kilograms)
let finalKgWeight = formatter.string(fromValue: kgWeight.value, unit: .kilogram)
let NumericKgResult = finalKgWeight.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted)
cell.userExerciseWeight.text = NumericKgResult
} else {
let lbsWeight = setWeight.converted(to: .pounds)
let finalLbWeight = formatter.string(fromValue: lbsWeight.value, unit: .pound)
let NumericLbResult = finalLbWeight.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted)
cell.userExerciseWeight.text = NumericLbResult
}
} else if self.userExercise == nil {
print("NEW SET CELL ADDED FOR FRESH EXERCISE")
cell.setNumber.text = String((indexPath.row) + 1)
}
}
Try something like this to match the setIds correctly. That's where I think the issue is.
for x in sortedexerciseSets {
if x.setPosition == Int64(setsCell.setNumber.text!)! {
//save
}
}
Proper way to do it would be to have an array of those sets (I guess, since you tagged core-data, they are instances of NSManagedObject?). When user does ANY change in the cell (write new value in the text field or scroll to another value for reps) you need to update the approproate object in your array immediately. Then you could call save on NSManagedObjectContext when you're sure you want to save changes, or just call rollback on the context to cancel all changes.
I have this problem, I have a tableview with 3 different kind of news manage by segmented control. When I scrolled the news stays for example in the fifth new, if I click in the second segment, appears in the position 5 . Is not showing the news from the segment 1(the new one),at row 0 (beginning) , stays in the position I leave when I was scrolling. Why is happening this? what I'm doing wrong?. I'm using one tableview for the 3 different kinds of news and reload the tableview data every time I change the segment.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
NSLog("selectedSegmentIndex: \(self.sectionSegmentedControl.selectedSegmentIndex) - Row: \(indexPath.row)")
let cell:UITableViewCell = self.tableview.dequeueReusableCellWithIdentifier("Cell")!
// Grab the elements using the tag
let labelTitle:UILabel? = cell.viewWithTag(1) as! UILabel?
let labelSection:UILabel? = cell.viewWithTag(2) as! UILabel?
let labelDate:UILabel? = cell.viewWithTag(3) as! UILabel?
let imageView:UIImageView? = cell.viewWithTag(4) as! UIImageView?
// Check which segment to get data from
switch self.sectionSegmentedControl.selectedSegmentIndex {
case 0:
// If segment is 0, take data from cover News
if (indexPath.row <= self.coverNews.count - 1){
//Current new to display
let currentNewToDisplay = self.coverNews[indexPath.row]
//let currentNewToDisplay = self.news[indexPath.row]
// Get the image and assign to the imageView
if let actualImageView = imageView {
// Imageview actually exists
if currentNewToDisplay.imageUrl != "" {
// Image url exists, so download it
let imageUrl:NSURL? = NSURL(string: currentNewToDisplay.imageUrl)
// Download image with SDWebImage library
if let url = imageUrl {
actualImageView.sd_setImageWithURL(url)
}
}
}
// Get the news title and assign to the label
if let actualLabelTitle = labelTitle {
let title = currentNewToDisplay.title
actualLabelTitle.text = title
actualLabelTitle.numberOfLines = 0
actualLabelTitle.minimumScaleFactor = 0.1
}
// Get the news date and assign to the label
if let actualLabelDate = labelDate {
let character = "| "
actualLabelDate.text = character + currentNewToDisplay.date_short
}
// Get the news section and assign to the label
if let actualabelSection = labelSection {
actualabelSection.text = currentNewToDisplay.section
}
}
case 1:
// If segment is 1, take data from toprated News
if (indexPath.row <= self.topratedNews.count - 1){
let currentNewToDisplay2 = self.topratedNews[indexPath.row]
// Get the image and assign to the imageView
if let actualImageView2 = imageView {
// Imageview actually exists
if currentNewToDisplay2.imageUrl != "" {
// Image url exists, so download it
let imageUrl2:NSURL? = NSURL(string: currentNewToDisplay2.imageUrl)
// Download image with SDWebImage library
if let url2 = imageUrl2 {
actualImageView2.sd_setImageWithURL(url2)
}
}
}
// Get the news title and assign to the label
if let actualLabelTitle2 = labelTitle {
actualLabelTitle2.text = currentNewToDisplay2.title
actualLabelTitle2.numberOfLines = 0
actualLabelTitle2.minimumScaleFactor = 0.1
}
// Get the news date and assign to the label
if let actualLabelDate2 = labelDate {
let character2 = "| "
actualLabelDate2.text = character2 + currentNewToDisplay2.date_short
}
// Get the news section and assign to the label
if let actualabelSection2 = labelSection {
actualabelSection2.text = currentNewToDisplay2.section
}
}
case 2:
if (indexPath.row <= self.latestNews.count - 1){
// If segment is 2, take data from latestNews News
let currentNewToDisplay3 = self.latestNews[indexPath.row]
// Get the image and assign to the imageView
if let actualImageView3 = imageView {
// Imageview actually exists
if currentNewToDisplay3.imageUrl != "" {
// Image url exists, so download it
let imageUrl3:NSURL? = NSURL(string: currentNewToDisplay3.imageUrl)
// Download image with SDWebImage library
if let url3 = imageUrl3 {
actualImageView3.sd_setImageWithURL(url3)
}
}
}
// Get the news title and assign to the label
if let actualLabelTitle3 = labelTitle {
actualLabelTitle3.text = currentNewToDisplay3.title
actualLabelTitle3.numberOfLines = 0
actualLabelTitle3.minimumScaleFactor = 0.1
}
// Get the news date and assign to the label
if let actualLabelDate3 = labelDate {
let character3 = "| "
actualLabelDate3.text = character3 + currentNewToDisplay3.date_short
}
// Get the news section and assign to the label
if let actualabelSection3 = labelSection {
actualabelSection3.text = currentNewToDisplay3.section
}
}
default:
break
}
// Set insets to zero
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
// MARK: Segmented Control
#IBAction func segmentedChanged(sender: UISegmentedControl) {
switch self.sectionSegmentedControl.selectedSegmentIndex {
case 0:
// If segment is 0, return rows for coverNews array
if (self.coverNews.count == 0) {
loadNews()
}else{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
})
}
case 1:
// If segment is 1, return rows for topratedNews array
if (self.topratedNews.count == 0) {
loadNews()
}else{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
})
}
case 2:
// If segment is 2, return rows for latestNews array
if (self.latestNews.count == 0) {
loadNews()
}else{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
})
}
default:
break
}
}
// MARK: Load News
func loadNews(){
switch(sectionSegmentedControl.selectedSegmentIndex){
case 0:
self.model.getFeedNews("cover")
case 1:
self.model.getFeedNews("toprated")
case 2:
self.model.getFeedNews("latest")
default:
break
}
}
This code does not add annotations to mapView. I saw in one answer that mapView function is called every time addAnotation is called so where's the problem? But when I move map they show up.
func addPlacesMarkers(location:CLLocation) {
self.communication.getJsonData(self.getPubsNearByCreator(location)) { (finalData, error) -> Void in
if error != nil {
print(error)
} else {
if let row: NSArray = finalData {
for var i = 0; i < row.count; i++ {
let lat = row[i]["lat"] as! String
let lng = row[i]["lng"] as! String
let title = row[i]["name"] as! String
let id = row[i]["id"] as! String
let point = CustomizedAnotation(id: Int(id)!, name: title)
point.title = title
point.coordinate.latitude = Double(lat)!
point.coordinate.longitude = Double(lng)!
let keyExists = self.places[Int(id)!] != nil
if keyExists == false {
self.places.updateValue(point, forKey: Int(id)!)
}
}
var finalPlaces :[MKPointAnnotation] = []
for place in self.places.values {
finalPlaces.append(place)
}
self.mView.addAnnotations(finalPlaces)
self.mView.showsPointsOfInterest = false
}
}
}
}
You can't modify the UI in a thread different from the main.
You should put your UI modification code inside a dispatch_async block like this:
dispatch_async(dispatch_get_main_queue()) {
//Your code that modify the UI
self.mView.addAnnotations(finalPlaces)
}