TableView Load More is fetching non-stop - ios

I setup a TableView and fetching paginated data. For page 1, as it's hitting route without any parameter (e.g /?page=2). Now I am trying to implement infinite scroll kind load more. I am trying to make a call everytime it hits the 5th row from bottom.
What this code does is fetches non-stop as soon as I hit the 5th cell from bottom. I think it's because the table stays on the 5th cell from bottom and keep adding cells from bottom toward up (but it may be just visual illusion)
class TableView1: UITableViewController {
var currentPage: Int = 1
var results: [JSON]? = []
var urlEndpoint: String?
var isLoading = false
override func viewDidLoad() {
loadItems()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// connections to cell..
let rowsToLoadFromBottom = 5;
let rowsLoaded = self.results?.count // count of fetched items
if (!isLoading && (indexPath.row >= (rowsLoaded! - rowsToLoadFromBottom))) {
self.loadItems()
}
return cell
}
And loadItems() function:
func loadItems() {
isLoading = true
urlEndpoint = "http://appurl.app/feed"
if currentPage != 1 {
currentPage = currentPage! + 1
urlEndpoint = "http://appurl.app/feed?page=\(currentPage)"
// print(urlEndpoint)
}
// Alamofire call.. {
// Error handling
// Got results {
self.currentPage = json["current_page"].integer
self.currentPage = self.currentPage + 1
self.results = data
self.tableView.reloadData()
}
self.isLoading = false
}
}
Update: I made it fetch properly. Now, I am seeing the links with page parameters on console. However, now it's loading all pages in a sudden (I have 6 pages, so 6 of them) so it doesn't really stop appending and I can actually see like a loop.
Update 2:
self.currentPage = json["current_page"].int!
if self.currentPage == 1 {
self.results = data // data is json data fetched
self.tableView.reloadData()
} else {
var currentCount = self.results!.count;
let indxesPath : [NSIndexPath] = [NSIndexPath]()
for result in data {
self.results?.append(result)
currentCount++
}
self.tableView.insertRowsAtIndexPaths(indxesPath, withRowAnimation: UITableViewRowAnimation.Bottom)
}
self.currentPage = self.currentPage + 1
}
self.isLoading = false
But it's crashing on line self.tableView.insertRowsAtIndexPaths(indxesPath, withRowAnimation: UITableViewRowAnimation.Bottom)
The error: Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (40) must be equal to the number of rows contained in that section before the update (20), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

I have found my answer in another SO question. Here it is applied to my scenario
if self.currentPage == 1 {
self.results = data
self.tableView.reloadData()
} else {
var currentCount = self.results!.count;
var indxesPath : [NSIndexPath] = [NSIndexPath]()
for result in data {
indxesPath.append(NSIndexPath(forRow:currentCount,inSection:0));
self.results?.append(result)
currentCount++
}
self.tableView.insertRowsAtIndexPaths(indxesPath, withRowAnimation: UITableViewRowAnimation.Bottom)
}

Related

How to use pagination in table view

Basically i have a table view and get 5 item in every time when i call the api. So how to use/ reload more data white scrolling table view.
array.count-1 not for me at cell for row indexpath because when device size bigger all data shown at a time and api not call
You should be sending the page number in the API request.
First: declare the variable currentPage with initial value 0 and a boolean to check if any list is being loaded with initial value false, it's to prevent the scroll view from getting more lists/items in one scroll to the bottom of the tableView.
var currentPage = 0
var isLoadingList = false
Second: This is the function that fetches the data:
func getFromServer(_ pageNumber: Int){
self.isloadingList = false
self.table.reloadData()
}
Third: Implement the function that increments the page number and calls the API function on the current page.
func loadMoreItems(){
currentPage += 1
getFromServer(currentPage)
}
Fourth: When the scrollView scrolls you should get the other items/lists from the API.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) && !isLoadingList){
self.isLoadingList = true
self.loadMoreItems()
}
}
You should implement the UITableViewDataSourcePrefetching protocol. This protocol will help you to fill seamlessly a table by new data
Declare
current page = 1
Then add this method in viewController
private func isLastCell(indexPath: IndexPath) -> Bool {
print(indexPath)
if array.count > 0 {
return ((indexPath.section == sectionCount-1) && (indexPath.row == (array.count-1)))
} else {
return false
}
}
After that in tableViewWillDisplayCell method add this function
if array.count > 0, self.isLastCell(indexPath: indexPath) {
self.currentPage += 1
// call api here
}
It works for me

UITableViewController's Cell's Content disappearing

I am making a coin collection app which is supposed to help users maintain a portable record of their collection. There are two view controllers so far: the CoinTableViewController which presents a tableview of all the categories of coins, and the CoinCategoryViewController which is supposed to have a table view of the specific coins in this category.
I obviously want my coins to be reportable based on multiple criteria (such as the same year, same country, etc. and etc.). To this effect, I have created a reusable tableview cell subclass called CoinTableViewCell that has 5 UILabels which represent all the information that the coins into the collection can be grouped into categories by.
Here is what my storyboard looks like
The logic is that based on my current sorting criteria, I can hide certain labels from the cell to reflect the criteria that the coins were sorted by. The settings wheel opens up a menu that appears on the left side of the screen with options for how to sort the coins, and the closing of the menu makes the Coin Categories controller resort the coins in the collection if the sorting option was changed.
My problem is that while my program works overall, sometimes, some of the cells do not appear completely after the program resorts them (after the user opens up the menu and selects an option).
Here's what this looks like:
As you may see, the bottom two cells in the view controller are missing the top label even though the other cells have them. And since I have implemented the tableview controller's cells as being resizable, the table view cells should automatically resize themselves to fit the content inside.
Here is my code:
// Controls the table view controller showing the general coins (one per each category)
import UIKit
import CoreData
class CoinTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchResultsUpdating, UITabBarControllerDelegate
{
//this is an array of all the coins in the collection
//each row of this two-dimensional array represents a new category
var coinsByCategory: [CoinCategoryMO] = []
var fetchResultController: NSFetchedResultsController<CoinCategoryMO>!
//we sort the coins by the category and then display them in the view controller
//example includes [ [Iraq Dinar 1943, Iraq Dinar 1200], etc. etc.]
//<OTHER VARIABLES HERE>
//the data here is used for resorting the coins into their respective categories
//the default sorting criteria is sorting the coins into categories with the same country, value, and currency
//and the user can change the app's sorting criteria by opening the ConfiguringPopoverViewController and changing the sorting criteria there
private var isCurrentlyResortingCoinsIntoNewCategories : Bool = false
override func viewDidLoad()
{
super.viewDidLoad()
self.tabBarController?.delegate = self
//we now fetch the data
let fetchRequest : NSFetchRequest<CoinCategoryMO> = CoinCategoryMO.fetchRequest()
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
let context = appDelegate.persistentContainer.viewContext
let sortDescriptor = NSSortDescriptor(key: "index", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do
{
try fetchResultController.performFetch()
if let fetchedObjects = fetchResultController.fetchedObjects
{
self.coinsByCategory = fetchedObjects
}
}
catch
{
print(error)
}
}
//if there is an empty area in the table view, instead of showing
//empty cells, we show a blank area
self.tableView.tableFooterView = UIView()
//we configure the row heights for the table view so that the cells are resizable.
//ALSO: should the user want to adjust the text size in "General"->"Accessibility"
//the text size in the app will be automatically adjusted for him...
tableView.estimatedRowHeight = 120
tableView.rowHeight = UITableViewAutomaticDimension
//WE CONFIGURE THE SEARCH BAR AND NAVIGATION BAR....
//if the user scrolls up, he sees a white background, not a grey one
tableView.backgroundView = UIView()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchResultController.sections!.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if searchController != nil && searchController.isActive
{
return searchResults.count
}
else
{
if let sections = fetchResultController?.sections
{
return sections[section].numberOfObjects
}
else
{
return 0
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//configure the cell
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CoinTableViewCell
//Initialize the Cell
let category = (searchController != nil && searchController.isActive) ? searchResults[indexPath.row] : coinsByCategory[indexPath.row]
//we now remove the extra labels that we do not need
cell.configureLabelsForCategoryType(theType: (category.coinCategory?.currentCategoryType)!)
let sampleCoin : Coin = category.coinCategory!.getCoin(at: 0)!
cell.countryLabel.text = "Country: \(sampleCoin.getCountry())"
cell.valueAndDenominationLabel.text = "Value & Denom.: \(sampleCoin.valueAndDenomination)"
//now we add in the quantity
cell.quantityLabel.text = "Quantity: \(String(describing: coinsByCategory[indexPath.row].coinCategory!.count))"
//we now add in the denomination
cell.denominationOnlyLabel.text = "Denom.: \(sampleCoin.getDenomination())"
//we now add in the year
if sampleCoin.getYear() == nil
{
cell.yearLabel.text = "Year: " + (Coin.DEFAULT_YEAR as String)
}
else
{
let yearABS = abs(Int32(sampleCoin.getYear()!))
cell.yearLabel.text = "Year: \(yearABS) \(sampleCoin.getYear()!.intValue > 0 ? TimePeriods.CE.rawValue : TimePeriods.BCE.rawValue)"
}
//we add in an accessory to indicate that clicking this cell will result in more information
cell.accessoryType = .disclosureIndicator
return cell
}
func deleteCoinCategory(rowPath: IndexPath)
{
if 0 <= rowPath.row && rowPath.row < self.coinsByCategory.count
{
//we have just tested that the rowPath index is valid
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
let context = appDelegate.persistentContainer.viewContext
let coinCategoryToDelete = self.fetchResultController.object(at: rowPath)
context.delete(coinCategoryToDelete)
appDelegate.saveContext()
//ok we now deleted the category, now we update the indices
updateIndices()
appDelegate.saveContext()
}
}
}
func deleteCoin(c: Coin, indexOfSelectedCategory: IndexPath) -> Bool
{
//we have a coin that we want to delete from this viewcontroller
//and the data contained in it.
//
//the parameter indexOfSelectedCategory refers to the IndexPath of the
//row in the TableView contained in THIS viewcontroller whose category
//of coins we are modifying in this method
//
//Return value: a boolean that indicates whether a single coin has
//been deleted - meaning that the user should return to the parentviewcontroller
if 0 < indexOfSelectedCategory.row && indexOfSelectedCategory.row < self.coinsByCategory.count && self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.hasCoin(c: c) == true
{
//the index is valid as it refers to a category in the coinsByCategory array
//and the examined category has the coin in question
if self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.count == 1
{
//the coin "c" that we are going to delete is the only coin in the entire category
//we reduce the problem to a simpler one that has been already solved (thanks mathematicians!)
self.deleteCoinCategory(rowPath: indexOfSelectedCategory)
return true
}
else
{
//there is more than one coin in the category
self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.removeCoin(c: c)
//we save the changes in the database...
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
{
appDelegate.saveContext()
}
return false
}
}
return false
}
func addCoin(coinToAdd: Coin)
{
//we check over each category to see if the coin can be added
var addedToExistingCategory: Bool = false
if let appDelegate = UIApplication.shared.delegate as? AppDelegate
{
for i in 0..<self.coinsByCategory.count
{
if self.coinsByCategory[i].coinCategory?.coinFitsCategory(aCoin: coinToAdd) == true
{
//we can add the coin to the category
self.coinsByCategory[i].coinCategory = CoinCategory(coins: self.coinsByCategory[i].coinCategory!.coinsInCategory+[coinToAdd], categoryType: coinsByCategory[i].coinCategory!.currentCategoryType)
addedToExistingCategory = true
break
}
}
if addedToExistingCategory == false
{
//since the coinToAdd does not fall in the existing categories, we create a new one
let newCategory = CoinCategoryMO(context: appDelegate.persistentContainer.viewContext)
newCategory.coinCategory = CoinCategory(coins: [coinToAdd], categoryType: CoinCategory.CategoryTypes.getTheCategoryFromString(str: UserDefaults.standard.object(forKey: "currentSortingCriteria") as! NSString).rawValue)
//this index indicates that we are going to insert this newCategory into index "0" of all the categories in the table
newCategory.index = 0
}
appDelegate.saveContext()
//now since we have added the coin, we now updated the indices of each CoinCategoryMO object
updateIndices()
}
}
func coinFitsExistingCategory(coin: Coin) -> Bool
{
//this function checks if the coin can be added to the existing categories
for i in 0..<self.coinsByCategory.count
{
if self.coinsByCategory[i].coinCategory?.coinFitsCategory(aCoin: coin) == true
{
//we can add the coin to the category
return true
}
}
return false
}
func resortCoinsInNewCategories(newCategorySetting : CoinCategory.CategoryTypes?)
{
//we want to resort all the coins in the category by new sorting criteria
if newCategorySetting != nil && newCategorySetting! != CoinCategory.CategoryTypes.getTheCategoryFromString(str: UserDefaults.standard.object(forKey: "currentSortingCriteria") as! NSString)
{
//We have a valid CoinCategory.CategoryTypes sorting criteria that is different from the one currently used.
//We resort the coins in the collection by the new category
UserDefaults.standard.setValue(newCategorySetting!.rawValue, forKey: "currentSortingCriteria")
if self.coinsByCategory.count != 0
{
//we actually have some coins to resort... let's get to work!
self.isCurrentlyResortingCoinsIntoNewCategories = true
//we first get an array of all the coins in existing categories
var allCoinsArray : [Coin] = []
for i in 0..<self.coinsByCategory.count
{
allCoinsArray += self.coinsByCategory[i].coinCategory!.coinsInCategory
}
//now we need to delete all the categories in existence...
let firstCategoryIndexPath = IndexPath(row: 0, section: 0)
let numberOfCategoriesToDelete = self.coinsByCategory.count
for _ in 0..<numberOfCategoriesToDelete
{
self.deleteCoinCategory(rowPath: firstCategoryIndexPath)
}
//OK... now that we have deleted all old categories... it is time to start to create new ones...
for i in 0..<allCoinsArray.count
{
//AND we add the coin to the array!
//this function also automatically updates the indices, so it is not an issue there
self.addCoin(coinToAdd: allCoinsArray[i])
}
//we are done resorting
self.isCurrentlyResortingCoinsIntoNewCategories = false
}
}
}
private func updateIndices()
{
//this function updates the "index" property so that
//each CoinCategoryMO object in the coinsByCategory array
//has an index corresponding to its position.
//After this function is called, we must save the core data in the AppDelegate.
//
//This function is called ONLY after the changes to the CoinCategoryMO objects
//are saved in core data and the self.coinsByCategory array is updated to have
//the latest version of the data
for i in 0..<self.coinsByCategory.count
{
//the only reason why we create an entirely new CoinCategory object
//is that the creation of an entirely new CoinCategory object
//is the only way that the appDelegate will save the information
self.coinsByCategory[i].index = Int16(i)
}
if let appDelegate = UIApplication.shared.delegate as? AppDelegate
{
appDelegate.saveContext()
}
}
//these delegate methods control the core data database
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
{
switch type
{
case .insert :
if let newIndexPath = newIndexPath
{
tableView.insertRows(at: [newIndexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath
{
tableView.deleteRows(at: [indexPath], with: .fade)
}
case .update:
if let indexPath = indexPath
{
tableView.reloadRows(at: [indexPath], with: .fade)
}
default:
tableView.reloadData()
}
if let fetchedObjects = controller.fetchedObjects
{
self.coinsByCategory = fetchedObjects as! [CoinCategoryMO]
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
tableView.endUpdates()
if self.isCurrentlyResortingCoinsIntoNewCategories != true
{
//we let the user know if the collection is empty
if self.coinsByCategory.count == 0
{
self.messageUserIfCollectionEmpty()
}
else
{
self.activateCollectionEmptyLabel(newState: false)
}
}
}
And then my CoinTableViewCell class is:
// Represents a cell of the coin buttons
import UIKit
class CoinTableViewCell: UITableViewCell {
#IBOutlet var countryLabel: UILabel!
#IBOutlet var valueAndDenominationLabel: UILabel!
#IBOutlet var quantityLabel: UILabel!
#IBOutlet var denominationOnlyLabel : UILabel!
#IBOutlet var yearLabel : UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func restoreAllLabelsToCell()
{
//this is a function that is called when this cell is being initialized in the cellForRowAt method in a tableview..
//we want to make all the labels visible so that the previous usage of a reusable tableview cell does not affect this usage of the cell
countryLabel.isHidden = false
valueAndDenominationLabel.isHidden = false
quantityLabel.isHidden = false
denominationOnlyLabel.isHidden = false
yearLabel.isHidden = false
}
func configureLabelsForCategoryType(theType : NSString)
{
//in this function, we remove all the extra labels
//that contain information that does not relate to the general type of the category from the stack view
//For example, the year label is removed when the category is a country, as a year does not determine what category a coin falls into.
//we restore all the labels in this cell as we do not want the reusable cell's past usage
//which may have lead to a label dissappearing to carry over into this new usage of the cell
self.restoreAllLabelsToCell()
switch theType
{
case CoinCategory.CategoryTypes.COUNTRY_VALUE_AND_CURRENCY.rawValue:
//we do not need information about the coin's denomination (without its value) or the year
denominationOnlyLabel.isHidden = true
yearLabel.isHidden = true
//after we remove the labels, we now make the first label bold and black
valueAndDenominationLabel.font = UIFont.boldSystemFont(ofSize: self.valueAndDenominationLabel.font.pointSize)
valueAndDenominationLabel.textColor = UIColor.black
case CoinCategory.CategoryTypes.COUNTRY.rawValue:
//we do not need the information about the coin's value and denominations nor year
valueAndDenominationLabel.isHidden = true
denominationOnlyLabel.isHidden = true
yearLabel.isHidden = true
//after we remove the labels, we make the first label bold and black
countryLabel.font = UIFont.boldSystemFont(ofSize: self.countryLabel.font.pointSize)
countryLabel.textColor = UIColor.black
case CoinCategory.CategoryTypes.CURRENCY.rawValue:
//we do not information about the coin's value & denomination (together, that is), or year
valueAndDenominationLabel.isHidden = true
yearLabel.isHidden = true
//after we remove the labels, we make the first label bold and black
denominationOnlyLabel.font = UIFont.boldSystemFont(ofSize: self.denominationOnlyLabel.font.pointSize)
denominationOnlyLabel.textColor = UIColor.black
case CoinCategory.CategoryTypes.YEAR.rawValue:
//we do not information about the coin's value, denomination, or country
valueAndDenominationLabel.removeFromSuperview()
denominationOnlyLabel.isHidden = true
countryLabel.isHidden = true
//after we remove the labels, we make the first label bold and black
yearLabel.font = UIFont.boldSystemFont(ofSize: self.yearLabel.font.pointSize)
yearLabel.textColor = UIColor.black
default:
//the string does not match any of the categories available
//we do not remove any labels
break
}
}
}
My CoreData implementation is a collection of CoinCategoryMO objects which have the property "Index" (for their position in the uitableviewcontroller) and a CoinCategory object which holds objects of the Coin Class.
I have been trying to debug this for several days now, and I have no idea what is going wrong. Could anyone please help?
Many many thanks in advance, and have a great day!
I guess, CategoryType in category.coinCategory?.currentCategoryType is not same across all objects in searchResults / coinsByCategory.
i.e. 2nd and 3rd objects has COUNTRY_VALUE_AND_CURRENCY as its currentCategoryType and 4th and 5th has COUNTRY.
Can you confirm that currentCategoryType is same for all objects in searchResults / coinsByCategory?
I think I know what the error is now:
In my configureLabelsForCategoryType method for my CoinTableViewCell class, I had invoked the removeFromSuperView() method on the valueAndDenomination label, which had the effect of permanently removing that label from the cell.
Thus the cell did not have that label, even if it was later reused for another CoinCategory.
I replaced the valueAndDenominationLabel.removeFromSuperView() line with valueAndDenominationLabel.isHidden = true which has the effect of hiding the valueAndDenominationLabel from the user, but not permanently removing it from the cell.
Many many thanks to those who took the time to read my question and to respond.
Your efforts helped me think about my problem and track it down!
Thank you!

How to resize table view, when if any data is there and no data in table view

I have one screen with the title "current team" , below that one table view will be there, and below that one text field with + button. So when user enter any data in UITextField and if user press + button that data will add in above UITableView
This blow image is my current screen :
Now, I my above screen I have designed and added some constraints for that. Now when no data and at least one data in table view, there is more space between the title and the text field.
But what i need is :
I need to show like below image :
No space I need to show between that title and text field. And when at least one data is there in table view, the height of the table view should increase and the text field also should need to come below .
How can i handle this :
I did like this :
if currentTeams.visibleCells.count == 0 {
tableViewHeight.constant = 5
}
else{
tableViewHeight.constant = 50
}
Current team = table view name
But it din work, Please help me out. How can I do that?
Code snip :
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView.isEqual(currentTeams) {
return teamNames.count
}
return pastTeamNames.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 20
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if tableView.isEqual(currentTeams) {
return getCellForRow(indexPath)
}
return getCellForPastTeamsRow(indexPath)
}
// your table hight constrain.
#IBOutlet var current_team_tablehight: NSLayoutConstraint!
#IBOutlet var playpast_role_table_hight: NSLayoutConstraint!
#IBOutlet var past_team_table_hight: NSLayoutConstraint!
#IBOutlet var certificate_table_hight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// put it as 0 for default
current_team_tablehight.constant = 0
certificate_table_hight.constant = 0
past_team_table_hight.constant = 0
playpast_role_table_hight.constant = 0
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
if profileData.FirstName.length > 0 {
// data available for that table then .
currentTeams.reloadData()
current_team_tablehight.constant = currentTeams.contentSize.height + 5;
pastTeams.reloadData()
past_team_table_hight.constant = pastTeams.contentSize.height + 5;
playedTableView.reloadData()
playpast_role_table_hight.constant = playedTableView.contentSize.height + 5;
certificationtableview.reloadData()
certificate_table_hight.constant = certificationtableview.contentSize.height + 5;
}
}
func deleteTeamFromCurrentTeams(sender: UIButton) {
let but = sender
let view = but.superview!
let cell = view.superview as! CurrentTeamsTableViewCell
if let tblView = cell.superview?.superview as? UITableView {
if tblView.isEqual(self.currentTeams) {
let indexPath = currentTeams.indexPathForCell(cell)
teamNames.removeAtIndex((indexPath?.row)!)
currentTeams.reloadData()
if teamNames.count > 0 {
current_team_tablehight.constant = currentTeams.contentSize.height;
}else{
current_team_tablehight.constant = 0;
}
}
else if tblView.isEqual(self.pastTeams) {
let indexPath = pastTeams.indexPathForCell(cell)
pastTeamNames.removeAtIndex((indexPath?.row)!)
pastTeams.reloadData()
if pastTeamNames.count > 0 {
past_team_table_hight.constant = pastTeams.contentSize.height;
}else{
past_team_table_hight.constant = 0;
}
}
else if tblView.isEqual(self.playedTableView) {
let indexPath = playedTableView.indexPathForCell(cell)
playedTeamNames.removeAtIndex((indexPath?.row)!)
playedTableView.reloadData()
if playedTeamNames.count > 0 {
playpast_role_table_hight.constant = playedTableView.contentSize.height;
}else{
playpast_role_table_hight.constant = 0;
}
}
else
{
let indexPath = certificationtableview.indexPathForCell(cell)
ExpTeamNames.removeAtIndex((indexPath?.row)!)
certificationtableview.reloadData()
if ExpTeamNames.count > 0 {
certificate_table_hight.constant = certificationtableview.contentSize.height;
}else{
certificate_table_hight.constant = 0;
}
}
}
self.view .layoutIfNeeded()
self.view .setNeedsLayout()
}
#IBAction func addPastTeamsPressed(sender: AnyObject) {
if pastTeamName.text?.trimWhiteSpace != "" && pastTeamName.text?.trimWhiteSpace != "-" {
pastTeamNames.append(pastTeamName.textVal)
pastTeamName.text = ""
pastTeams.reloadData()
past_team_table_hight.constant = pastTeams.contentSize.height + 5;
}
}
#IBAction func addTeamsPressed(sender: AnyObject) {
if teamName.text?.trimWhiteSpace != "" && teamName.text?.trimWhiteSpace != "-" {
teamNames.append(teamName.textVal)
teamName.text = ""
currentTeams.reloadData()
current_team_tablehight.constant = currentTeams.contentSize.height + 5;
}
}
// for played role
#IBAction func addPlayedTeamsPressed(sender: AnyObject) {
if playedTeam.text?.trimWhiteSpace != "" && playedTeam.text?.trimWhiteSpace != "-" {
playedTeamNames.append(playedTeam.textVal)
playedTeam.text = ""
playedTableView.reloadData()
playpast_role_table_hight.constant = playedTableView.contentSize.height + 5;
}
}
#IBAction func addcertificatecoursePressed(sender: AnyObject) {
if Experience.text?.trimWhiteSpace != "" && Experience.text?.trimWhiteSpace != "-" {
ExpTeamNames.append(Experience.textVal)
Experience.text = ""
certificationtableview.reloadData()
certificate_table_hight.constant = certificationtableview.contentSize.height + 5;
}
}
Output :
You can achieve same by taking a fix UITableviewCell at the and of your table. If You have no data initially then in numberOfRowsInSection method return 1.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return (<Your Array>.count > 1) ? <Your Array>.count + 1 : 1;
}
This 1 is for that static cell which contains your UITextField and UIButton for Plus.
So cellForRow method will be as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (<Your Array>.count > 1)
{
if (indexPath.row < <Your Array>.count - 1)
{
// Initialize your UITableview cell which you have design to show your list
// Your Code to show the list.
// return your cell
}
if (indexPath.row==<Your Array>.count-1 ) {
// Initialize and return the cell which you have design using UITextField and "Plus" Button
}
}
else
{
// Initialize and return the cell which you have design using UITextField and "Plus" Button
}
Your design will be like
You can also check example From Github Here.
you can have dynamic cell height by setting tableView row height to UITableViewAutomaticDimension. and also you need to provide estimated height. so in your viewdidload add this code:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140 // or your estimated height
then use autolayout in your tableview cell to adjust items and container height
Setup your constraints like so-
First you should have a UILabel for "Current Teams" with a leading, trailing and top constraint to the superView.
Add the UITableView below it with a leading, trailing and top to the UILabel and a fixed height with a priority of 250, lets put it at 0 initially.
Add the UITextField with a leading, trailing, fixed height, top to the UITableView and bottom spacing to the superView with a >=0
This will allow the UITextField always stick to the bottom of your UITableView.
Make an outlet for the UITableView height constraint in your UIViewController.
Now if you can use a default UITableViewCell height, then your job is easier otherwise you will have to calculate the amount of height each string will take inside the cell and get that value. In your viewDidLoad update the UITableView height constraint to be -
//do this after you do the insertion into the tableview
tableViewHeightConstraint.constant = numberOfRows * rowHeight
view.layoutIfNeeded()
You will have to update this height every time you add or delete a row in the UITableView. As soon as your cells take up the entire screen, the height constraint will break and the UITextField will be stuck at the bottom and your UITableView will become scrollable.
Edit: You have to re-add the height constraint programmatically once it breaks and the UITableView stops taking up the entire screen.

reloadData() doesn't work with refreshControl

I have UICollectionView that manages a lot of cells. When I delete a cell, I would like the cell disappears with a refreshControl. But I don't understand why the reloadData does not act. If anyone can help me thank you in advance.
In my view didLoad :
self.collectionView!.alwaysBounceVertical = true
let refresher = UIRefreshControl()
refresher.tintColor = MyColor.Color
refresher.addTarget(self, action: #selector(PublicListController.refreshStream), forControlEvents: .ValueChanged)
refreshControl = refresher
collectionView!.addSubview(refreshControl!)
collectionView.dataSource = self
self.populateDataBest()
My simply function :
func refreshStream() {
collectionView?.reloadData()
refreshControl?.endRefreshing()
}
I complete my CollectionView with the method populateDataBest :
func populateDataBest() {
self.videosService.get(true, completionHandler: {
videosBest, error in
dispatch_async(dispatch_get_main_queue(), {
if error != nil {
if error!.code == -999 {
return
}
self.displayError(informations.LocalizedConnectionError)
return
}
self.bestClip = videosBest
for (indexBest, _) in (self.bestClip?.enumerate())! {
let videoBest:Clip = self.bestClip![indexBest]
self.pictureArrayVideo.addObject(["clip": videoBest, "group": "BEST"])
}
self.dataSource.updateData(self.pictureArrayVideo)
self.collectionView.reloadData()
})
})
}
And the first reload work at the end of my method populateDataBest..
EDIT :
I try to implement function who remove my element (I put 0 in parameters on remove method just for my test for the moment)
func refreshStream() {
dispatch_async(dispatch_get_main_queue(), {
self.remove(0)
self.collectionView.reloadData()
})
self.refreshControl.endRefreshing()
}
func remove(i: Int) {
self.listForPager.removeAtIndex(i)
let indexPath: NSIndexPath = NSIndexPath(forRow: i, inSection: 0)
self.collectionView.performBatchUpdates({
self.collectionView.deleteItemsAtIndexPaths(NSArray(object: indexPath) as! [NSIndexPath])
}, completion: {
(finished: Bool) in
self.collectionView.reloadItemsAtIndexPaths(self.collectionView.indexPathsForVisibleItems())
})
}
And I have this error after
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (8) must be equal to the number of items contained in that section before the update (8), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
Someone know why and can help me plz ?
Thx in advance.
If you are riding your collection from an array you should also delete the item for the line will disappear and finally to reload.

How can I reload UITableView when I scroll to the top with current scroll position? (like chat apps)

I'm working on a chat screen now(UITableView + input TextField).
I want my UITableView reload more chat messages staying current scroll position when I scroll to top(If I load older 20 messages more, I still seeing a 21st message.) KakaoTalk and Line Apps is doing this. In that apps, I can scroll up infinitely because the scroll position is staying.(I mean I'm still seeing the same chat message)
I'm checking the row index in tableView:willDisplayCell:forRowAtIndexPath:, so I fetch more chat messages when the index is 0. I finished the logic fetching more messages from DB, but I didn't finish polishing UI.
This is the code.
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 && !isTableViewLoading {
if photo.chatMessages.count != chatMessages.count {
var loadingChatItems: Array<ChatMessage>
let start = photo.chatMessages.count - chatMessages.count - Metric.ChatMessageFetchAmount
if start > 0 {
loadingChatItems = Database.loadChatMessages(photoId, startIndex: start, amount: Metric.ChatMessageFetchAmount)
} else {
loadingChatItems = Database.loadChatMessages(photoId, startIndex: 0, amount: Metric.ChatMessageFetchAmount + start)
}
chatMessages = loadingChatItems + chatMessages
isTableViewLoading = true
var indexPaths = Array<NSIndexPath>()
for row in 0..<loadingChatItems.count {
let indexPath = NSIndexPath(forRow: row, inSection: 0)
indexPaths.append(indexPath)
}
tableView.scrollEnabled = false
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
} else {
isTableViewLoading = false
tableView.scrollEnabled = true
}
}
Any suggestions?
Swift 3
This post is a bit old, but here a solution:
1) Detect when you are scrolling near to the top in scrollViewDidScroll,
2) Load the new messages
3) Save the contentSize, reload the tableView
4) Set the contentOffset.y of the tableView to (newContentSizeHeight - oldContentSizeHeight) which is exactly the current point
and here the code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == tableView {
if tableView.contentOffset.y < 50 {
loadMessages()
let oldContentSizeHeight = tableView.contentSize.height
tableView.reloadData()
let newContentSizeHeight = tableView.contentSize.height
tableView.contentOffset = CGPoint(x:tableView.contentOffset.x,y:newContentSizeHeight - oldContentSizeHeight)
}
}
}
In viewDidLoad:
self.refreshControl?.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
Then add the function:
func refresh(refreshControl: UIRefreshControl) {
//your code here
refreshControl.endRefreshing()
}
This will allow the user to scroll up to refresh the UITableView.
I have implemented another solution, it uses the scroll listener from the delegate, and the response of the asynchronous service.
So, it scrolls without animation to the position where the loading of more messages started, because messages are being appended to my array (It causes the transition to be barely affected).
I decided to do it in this way because the first solution has a problem, and it is that when you let your row height to be set automatically, then the new content height will not be accurate.
Here's my code.
- (void) scrollViewDidScroll:(UIScrollView*)scrollView {
if (!topReached && scrollView == self.table) {
if (!isLoadingTop && self.table.contentOffset.y < 40) {
isLoadingTop = YES;
[self loadChat];
}
}
}
// method used by the response of the async service
- (void)fillMessages: (NSArray*) messageArray {
if ([messageArray count] == 0) {
topReached = YES;
} else {
if ([messageArray count] < limit) {
topReached = YES;
}
for (int i=0; i<messageArray.count; i++) {
NSDictionary* chat = [messageArray objectAtIndex:i];
[messages insertObject:[[MessageModel alloc] initWithDictionary:chat] atIndex:i];
}
[table reloadData];
if (!isLoadingTop)
[self scrollToBottom];
else {
isLoadingTop = NO;
[self.table
scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[messageArray count] inSection:0]
atScrollPosition:UITableViewScrollPositionTop animated:false];
}
offset += limit;
}
}
As an adicional information, I like to block the access of my async services consumptions in a view controller, so one service can be called just once in the same view.

Resources