Reputation: 335
I have a UITableView
with data, that parsed from url.
UITableView
will load more data, when scrolled right to the bottom (or got some more space to scroll, but close to the end - did both, same results)
When more data is loaded - i simple append it to array of my class, that contains data for TableView
and then list scrolls back more than half of list (e.g. got 40 items, loading 10 more -> scrolling back to 20-25).
Calling TableView.reloadData()
after append is complete.
Is there some mistakes in plan of doing it? I can share code, but it's pretty common.
class TabAllTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, XMLParserDelegate {
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
@IBOutlet weak var BlogAllTableView: UITableView!
var loadMoreStatus = false
var maxPage = 1.0
var currentPageLoad = 1.0
var blogList: [DetailAndRenderBlogObject] = []
var eName: String = String()
var category = String()
var full_text = String()
var short_text = String()
var blog_title = String()
var avatar = String()
var full_name = String()
var url = String()
var created_at = String()
private let CATEGORY = ConfigData.CATEGORY_ALL
let cellIdentifier = "BlogTableViewCell"
override func viewDidLoad() {
super.viewDidLoad()
setupNavMenuButtons()
self.BlogAllTableView.insertSubview(refreshControl, at: 0)
self.BlogAllTableView.tableFooterView?.isHidden = true
downloadBlogData(1, true, true)
BlogAllTableView.delegate = self
BlogAllTableView.dataSource = self
}
var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action:
#selector(TabAllTableViewController.handleRefresh(_:)),
for: UIControlEvents.valueChanged)
refreshControl.tintColor = Colors.ColorLoadingIndicator
return refreshControl
}()
@objc func handleRefresh(_ refreshControl: UIRefreshControl) {
downloadBlogData(1, true, false)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
let deltaOffset = maximumOffset - currentOffset
if deltaOffset <= 0 && currentPageLoad < maxPage {
loadMore()
}
}
func loadMore() {
if ( !loadMoreStatus ) {
self.loadMoreStatus = true
self.activityIndicator.startAnimating()
self.BlogAllTableView.tableFooterView?.isHidden = false
downloadBlogData(currentPageLoad, false, false)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.sideMenuController?.isLeftViewEnabled = true
AppDelegate.tabBarReference.isHidden = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.navigationBar.topItem?.title = "all".localized()
}
private func setupNavMenuButtons() {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(handleMenuRefresh))
let image = UIImage(named:"menu_ham.png")
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = true
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
imageView.image = image
let imageHeight = navigationController?.navigationBar.frame.size.height
let wrapperView = UIView(frame: CGRect(x: 0, y: 0, width: imageHeight!, height: imageHeight!))
wrapperView.addSubview(imageView)
imageView.center = CGPoint(x: imageView.frame.size.width / 2, y: wrapperView.frame.size.height / 2)
let tap = UITapGestureRecognizer(target: self, action: #selector(TabAllTableViewController.menuButtonClick))
wrapperView.addGestureRecognizer(tap)
let btnHamburgerMenu: UIBarButtonItem = UIBarButtonItem(customView: wrapperView)
navigationItem.setLeftBarButton(btnHamburgerMenu, animated: false)
}
@objc private func menuButtonClick()
{
self.sideMenuController?.showLeftViewAnimated()
}
@objc private func handleMenuRefresh() {
downloadBlogData(1, true, true)
}
func numberOfSections(in tableView: UITableView) -> Int {
return blogList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if (section != 0) {
return 12
} else {
return CGFloat.leastNonzeroMagnitude
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.0)
return view
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(ConfigData.CELL_HEIGHT)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure the cell...
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BlogTableViewCell else {
fatalError("The dequeued cell is not an instance of BlogTableViewCell.")
}
cell.layer.cornerRadius = 10
let blogItem = blogList[indexPath.section]
cell.avatarBackground.contentMode = .scaleAspectFill
cell.avatarBackground.layer.cornerRadius = cell.avatarBackground.frame.size.width / 2;
cell.avatarBackground.clipsToBounds = true;
if let authorImgUrl = URL(string: blogItem.authorImg) {
cell.authorRoundImage.contentMode = .scaleAspectFill
cell.authorRoundImage.layer.cornerRadius = cell.authorRoundImage.frame.size.width / 2;
cell.authorRoundImage.clipsToBounds = true;
//Helper.downloadImage(url: authorImgUrl, imageview: cell.authorRoundImage)
cell.authorRoundImage.sd_setImage(with: authorImgUrl)
}
if let headerImageUrl = URL(string: blogItem.image) {
cell.bigImage.contentMode = .scaleToFill
//Helper.downloadImage(url: headerImageUrl, imageview: cell.bigImage)
cell.bigImage.sd_setImage(with: headerImageUrl)
}
cell.authorLabel.text = blogItem.author
cell.dateLabel.text = blogItem.date
cell.title.text = blogItem.title
cell.titleDescription.text = blogItem.shortDescription
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let secondViewController = self.storyboard!.instantiateViewController(withIdentifier: "ShowArticleViewController") as! ShowArticleViewController
secondViewController.blogItem = blogList[indexPath.section]
self.navigationController!.pushViewController(secondViewController, animated: true)
}
private func downloadBlogData(_ page : Double, _ refresh : Bool, _ showOverlay : Bool) {
// print(InAppProperties.sharedInstance.getDefaulLang())
if showOverlay {
LoadingOverlay.shared.showOverlay(view: self.view)
}
let req = NSMutableURLRequest(url: NSURL(string: Helper.getBlogUrl(CATEGORY, Int(page)))! as URL)
req.httpMethod = "GET"
req.httpBody = "key=\"value\"".data(using: String.Encoding.utf8) //This isn't for GET requests, but for POST requests so you would
URLSession.shared.dataTask(with: req as URLRequest) { data, response, error in
if error != nil {
//Your HTTP request failed.
print(error?.localizedDescription)
} else {
//Your HTTP request succeeded
if refresh {
self.currentPageLoad = 1
} else {
self.currentPageLoad += 1
}
let stringData = String(data: data!, encoding: String.Encoding.utf8)!
//print(stringData)
let xml = SWXMLHash.parse(stringData)
if page == 1 {
self.blogList = [DetailAndRenderBlogObject]()
}
for elem in xml["response"]["provider"].all {
let itemList = elem["item"]
for article in itemList.all {
let blogItem = DetailAndRenderBlogObject()
blogItem.articleUrl = article["url"].element!.text
blogItem.author = article["full_name"].element!.text
blogItem.authorImg = article["avatar"].element!.text
blogItem.text = article["full_text"].element!.text
blogItem.shortDescription = article["short_text"].element!.text
blogItem.title = article["title"].element!.text
blogItem.categoryUrl = article["category"].element!.text
blogItem.image = self.repairLink(article["thumbnail"].element!.text)
blogItem.date = self.formatDate(article["created_at"].element!.text)
if (blogItem.categoryUrl.lowercased().range(of:"video") == nil &&
blogItem.title.lowercased().range(of: "видео") == nil) {
self.blogList.append(blogItem)
}
}
if let totalItemsCount = xml["response"]["pagination"]["totalCount"].element?.text {
self.maxPage = (Double(totalItemsCount)! / 10).rounded(.up)
}
}
DispatchQueue.main.async {
self.BlogAllTableView.reloadData()
self.refreshControl.endRefreshing()
self.loadMoreStatus = false
self.activityIndicator.stopAnimating()
self.BlogAllTableView.tableFooterView?.isHidden = true
if showOverlay {
LoadingOverlay.shared.hideOverlayView()
}
if (page == 1) {
let indexPath = NSIndexPath(row: 0, section: 0)
self.BlogAllTableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
}.resume()
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
eName = elementName
if elementName == "item" {
category = String()
full_text = String()
short_text = String()
blog_title = String()
avatar = String()
full_name = String()
url = String()
created_at = String()
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "item" {
let blogListItem = DetailAndRenderBlogObject()
blogListItem.categoryUrl = category
blogListItem.text = full_text
blogListItem.shortDescription = short_text
blogListItem.title = blog_title
blogListItem.authorImg = avatar
blogListItem.author = full_name
blogListItem.articleUrl = url
blogListItem.date = created_at
print("WRITING DATA")
blogList.append(blogListItem)
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
//print("ENAME: " + eName)
switch eName {
case "category":
print("writing category: " + string)
category = string;
case "full_text":
full_text = string;
case "short_text":
short_text = string;
case "title":
title = string;
case "thumbnail":
avatar = string;
case "avatar":
avatar = string;
case "full_name":
full_name = string;
case "url":
url = string;
case "created_at":
created_at = string;
default:
()
}
}
func parserDidEndDocument(_ parser: XMLParser) {
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func repairLink(_ link : String) -> String { //sometimes links looks like https://asdasd.comhttps://asdasd.com/...
let newLink = link.suffix(from: link.index(link.startIndex, offsetBy: 1))
if let range = newLink.range(of: "http") {
let substring = newLink[range.lowerBound...]
return String(substring)
}
return link
}
func formatDate(_ date : String) -> String { //date String "2018-01-22 08:59:43"
if let dividerIndex = date.index(of: " ") {
return String(date[..<dividerIndex])
}
return date
}
}
Upvotes: 3
Views: 1679
Reputation: 8327
I had the same issue and found another solution, after long time search.
It seems that whenever you call the endRefreshing()
function of an UIRefreshControl
that is attached to a UITableView
scrollview, it stops the decelerating process of the scroll view.
So, if your code, that fetches the next page, is calling endRefreshing()
function (with no reason at all) and the user started the scrolling and lifted his/her finger from the device's screen (scrollview is decelerating) this will have as a result to stop the scrolling.
The solution is simple, replace this line in your code:
self.refreshControl.endRefreshing()
with these lines:
if self.refreshControl.isRefreshing {
self.refreshControl.endRefreshing()
}
I even created a handy extension that does this check for me, to call it whenever I need it:
extension UIRefreshControl {
func endIfRefreshing() {
if isRefreshing { endRefreshing() }
}
}
Upvotes: 0
Reputation: 3236
Reloading the entire table is quite an intensive operation. The better way might be to use the tableView.insertRows(at:with:)
method. An example would be something like -
func didFinishLoadingData(newBlogs: [DetailAndRenderBlogObject]) {
tableView.beginUpdates()
var indexPaths: [IndexPath] = []
for row in (blogList.count..<(blogList.count + newBlogs.count)) {
indexPaths.append(IndexPath(row: row, section: 0))
}
blogList.append(contentsOf: newBlogs)
tableView.insertRows(at: indexPaths, with: .fade)
tableView.endUpdates()
}
Upvotes: 4