如何判断UITableView绘制完成?

本文介绍了通过引入第三方库MJRefresh来实现UITableView的下拉刷新和上拉加载功能,建议使用CocoaPods进行集成,并在桥接文件中进行相关配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     现在互联网app的列表页大都支持下拉刷新和上拉加载功能, 下面列举几个常见问题。


 1、UITableView下面会显示空白行,该如何隐藏?
 答: 设置footerView的宽高为0。 
 tableView?.tableFooterView = UIView(frame: CGRect.zero)  //添加该行后不显示空白行

2、引用三方库MJRefresh实现下拉刷新、上拉加载功能, 建议使用cocoapod方式, 如:

  use_frameworks!
  pod 'MJRefresh'               #下拉刷新 上划加载

然后添加桥接文件并在该文件中添加

#import "MJRefresh/MJRefresh.h"   //注意:如果没配置Build Path需要带前缀模块名称


3、如果屏幕能够显示10条, 但是只有6条数据时, 我们不希望显示MJRefresh的mj_footer, 即不想显示下图中的“No more data”

                             
  
  动态显示/隐藏footer就需要监听UITableView绘制完成的事件,判断UITableView和父容器的高度的大小关系。


有2个方法可以判断UITableView绘制完成:
1、  reloadData方法会在下个执行周期运行,它是异步函数,即UITableView并没有真正的绘制; layoutIfNeeded函数可以强制刷新控件,并且是阻塞运行的。
self.tableView?.reloadData()   //向RunLoop发送一个消息,这时并没有真正的绘制tableview
self.tableView!.layoutIfNeeded()   //强制刷新且阻塞等待列表刷新完成
.....    //执行逻辑


2、根据RunLoop的队列特性, reloadData方法可以视为向RunLoop队列添加一个任务; 那么在主线程RunLoop再添加一个任务,等待该任务运行时上一个任务及列表已经绘制完成。 PS: 这是我对RunLoop的理解, 不对之处请指正。 注意是DispatchQueue.main,不是DispatchQueue.global。


对比Android的ListView,在执行完notifyDataChanged函数(异步操作,并没有真正刷新界面)后监听listview的布局变化判断绘制完成。

       listview.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                //绘制每个item后都会执行该语句, 判断bottom是否超出父ViewGroup的边界


            }
        });


附Swift测试代码:
// 测试下拉和上滑效果
// 调用UITableView reloadData绘制完成有2种方法。 1、 调用tableView.layoutIfNeeded强制执行; 2、 在主RunLoop队列里添加闭包事件。


import UIKit
import MJRefresh
import XCGLogger


class MJRefreshViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    var items: [String]!    //列表数据
    var tableView: UITableView?
    let CELL_NAME = "MJRefresh_Cell"
    let log = XCGLogger.default
    
    override func viewDidLoad() {
        super.viewDidLoad()


        // Do any additional setup after loading the view.
        
        refreshUI()
        
        tableView = UITableView(frame: view.frame, style: .plain)
        tableView?.delegate = self
        tableView?.dataSource = self
        tableView?.tableFooterView = UIView(frame: CGRect.zero)  //添加该行后不显示空白行
        
        tableView?.register(UITableViewCell.self, forCellReuseIdentifier: CELL_NAME)
        self.view.addSubview(tableView!)
        
        
        self.tableView!.mj_header = MJRefreshNormalHeader(refreshingBlock: {
            self.log.debug("下拉刷新")
            
            self.refreshUI()
            self.tableView!.reloadData()
            
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
                if self.tableView!.mj_header.isRefreshing() {
                    print("停止刷新")
                    self.tableView!.mj_header.endRefreshing()
                }
            })
            
        })
        
        /*
        let header = MJRefreshNormalHeader()
        header.setTitle("下拉加载", for: .idle)
        header.setTitle("松开刷新", for: .pulling)
        header.setTitle("刷新", for: .refreshing)
        header.lastUpdatedTimeText = getDateStr  //修改更新时间的文案
        header.refreshingBlock = {
            print("下拉刷新")
            
            sleep(2)
            header.endRefreshing()
        }
        
        tableView?.mj_header = header
        */
        
        /*
        tableView?.mj_footer = MJRefreshAutoFooter(refreshingBlock: {
            print("上拉加载")
            
            sleep(2)
            
            self.refreshUI()
            self.tableView!.reloadData()
            self.tableView?.mj_footer.endRefreshing()
        })
        */
        
        let footer = MJRefreshAutoNormalFooter()
        footer.setRefreshingTarget(self, refreshingAction: #selector(footerLoad))
        footer.isAutomaticallyRefresh = false  //自动加载
        self.tableView!.mj_footer = footer


    }
    
    func footerLoad() {
        print("上拉加载")
        
        sleep(2)  //模拟http网络延时
        
        self.refreshUI()
        self.tableView?.reloadData()   //向RunLoop发送一个消息,这时并没有真正的绘制tableview
        //self.tableView!.layoutIfNeeded()   //强制刷新且阻塞等待列表刷新完成
        
        self.log.debug("调用完成刷新数据API")
        
        //在RunLoop队列发送个消息,在reloadData之后运行。 可以判定tableview绘制完成
        DispatchQueue.main.async {
            //模拟HTTP请求返回数据后json解析并刷新列表的过程。
            self.log.debug("刷新完成,高度:\(String(describing: self.tableView?.contentSize.height))")
            
            self.tableView!.mj_footer.endRefreshingWithNoMoreData()
            //self.tableView!.mj_footer.endRefreshing()  //结束刷新动画
            
            if (self.tableView?.contentSize.height)! > self.view.frame.height {
                print("显示footer")
                self.tableView?.mj_footer.isHidden = false
            } else {
                print("隐藏footer")
                self.tableView?.mj_footer.isHidden = true
            }
        }
        
  
    }
    
    func getDateStr(_ date: Date?) -> String? {
        return "自定义更新时间:\(tableView!.mj_header.lastUpdatedTime)"
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
          }
    
    //刷新界面
    func refreshUI() {
        items = []
        for _ in 0...5 {
            items.append("Item \(Int(arc4random()%100))")
        }
    }
    
    //只有一种布局
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        log.debug("tableView numberOfRowsInSection")
        return self.items.count
    }


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CELL_NAME, for: indexPath)
        
        cell.accessoryType = .disclosureIndicator
        cell.textLabel?.text = self.items[indexPath.row]
        
        log.debug("刷新第\(indexPath.row)条")
        return cell
    }
    


    
    
    /*
    // MARK: - Navigation


    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */


}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值