草庐IT

ios - Swift,将更多数据加载到 TableView 中会导致滚动滞后

coder 2023-09-13 原文

我有一个包含数据的 UITableView,它是从 url 解析的。

UITableView 将加载更多数据,当向右滚动到底部时(或有更多空间滚动,但接近尾部 - 两者都执行,结果相同)

当加载更多数据时 - 我简单地将它附加到我的类的数组,其中包含 TableView 的数据,然后列表向后滚动超过列表的一半(例如,有 40 个项目,再加载 10 个-> 滚动回 20-25)。

追加完成后调用 TableView.reloadData()

做的计划有没有错误? 我可以共享代码,但这很常见。

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
}


}

最佳答案

重新加载整个表是一项相当密集的操作。更好的方法可能是使用 tableView.insertRows(at:with:) 方法。一个例子是这样的-

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()
}

关于ios - Swift,将更多数据加载到 TableView 中会导致滚动滞后,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49027412/

有关ios - Swift,将更多数据加载到 TableView 中会导致滚动滞后的更多相关文章

  1. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  4. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  5. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  6. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

  7. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  8. Ruby 守护进程导致 ActiveRecord 记录器 IOError - 2

    我目前正在用Ruby编写一个项目,它使用ActiveRecordgem进行数据库交互,我正在尝试使用ActiveRecord::Base.logger记录所有数据库事件具有以下代码的属性ActiveRecord::Base.logger=Logger.new(File.open('logs/database.log','a'))这适用于迁移等(出于某种原因似乎需要启用日志记录,因为它在禁用时会出现NilClass错误)但是当我尝试运行包含调用ActiveRecord对象的线程守护程序的项目时脚本失败并出现以下错误/System/Library/Frameworks/Ruby.frame

  9. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  10. ruby - 从另一个私有(private)方法中使用 self.xxx() 调用私有(private)方法 xxx,导致错误 "private method ` xxx' called” - 2

    我正在尝试获得良好的Ruby编码风格。为防止意外调用具有相同名称的局部变量,我总是在适当的地方使用self.。但是现在我偶然发现了这个:classMyClass上面的代码导致错误privatemethodsanitize_namecalled但是当删除self.并仅使用sanitize_name时,它会起作用。这是为什么? 最佳答案 发生这种情况是因为无法使用显式接收器调用私有(private)方法,并且说self.sanitize_name是显式指定应该接收sanitize_name的对象(self),而不是依赖于隐式接收器(也是

随机推荐