Cải thiện UI/UX loading data trên tableview

1. Tạo information view

Information view(iv) nằm dưới tableview, chức năng của iv

  • Khi vừa vào viewcontroller iv sẽ hiện lên đồn thời indicator view sẽ thực hiện animation.
  • Nếu internet bị mất kết nối iv sẽ hiển thị thông tin cho người dùng biết kèm theo button cho phép reload data.
  • Nếu data trả về từ server không có thì cũng sẽ hiện thị tại đây cho người dùng biết
  • Nếu data trả về từ server có dữ liệu thì iv sẽ bị ẩn đi
class InformationView: UIView {
    open var titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.backgroundColor = UIColor.clear
        label.font = UIFont.systemFont(ofSize: 16.0)
        label.textColor = UIColor.gray
        label.textAlignment = .center
        label.numberOfLines = 0
        return label
    }()
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.addSubview(titleLabel)
        self.setConstraints()
    }
    
    override func didMoveToSuperview() {
        
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let uiview = super.hitTest(point, with: event)
        
        if uiview == self {
            return nil
        }
        
        return uiview
    }
    
    func setConstraints() {
        let centerX = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0)
        let centerY = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0)
        NSLayoutConstraint.activate([centerX, centerY])
    }
}

2. Loading data trên tableview

loading data ở đây sẽ có 2 phần

  • loading khi mới vào viewcontroller (loading ở viewDidLoad )
  • loading thêm data khi scroll xuống dưới
var isNodata = false
var isLoadingData = false
var isNoMoreData = false

Nếu ngay trong lần đầu load data dữ liệu trả về là 0 thì trường hợp này isNodata = true, nếu dữ liệu trả về < limit (số lượng dữ liệu trên mỗi request) isNoMoreData = true ( không còn data để load thêm nữa), ngoài ra trong khi đang load dữ liệu isLoadingData để người dùng không thể tiến hành load dữ liệu được nữa. Vì ở đây mình demo bằng dữ liệu fake nên mình sẽ sử dụng hàm delay để trả về data thay vì gọi api lên server (fake mà)

func delay(time: TimeInterval, completionHandler: @escaping () -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + time) { 
            completionHandler()
        }
}

hàm load data

func loadData() {
        if isLoadingData {
            print("loading data")
            return
        }
        
        delay(time: 3.0) {[weak self] in
            guard let weakSelf = self else {
                return
            }
            
            if weakSelf.activityIndicator.isAnimating {
                weakSelf.activityIndicator.stopAnimating()
            }

            guard !weakSelf.isNodata else {
                self?.noDataView.isHidden = false
                self?.tableView.reloadData()
                return
            }
            
            for i in 0..<20 {
                weakSelf.array.append("\(i)")
            }
            
            if weakSelf.array.count > 0 {
                weakSelf.noDataView.isHidden = true
            }
            
            weakSelf.tableView.reloadData()
            
            if weakSelf.refreshControl.isRefreshing {
                weakSelf.refreshControl.endRefreshing()
            }
        }
}

3. Loading more data

Loading more data được thực hiện khi chúng ta kéo tableview xuống cuối cùng.

3.1 Loading more cell

Nằm ở cuối cùng có chức năng để người dùng biết đang load more data

hay là no more data.

3.2 Loading more data trên tableview

sử dụng delegate của UIScrollView, khi người dùng cuộn đến cuối tableview sẽ cho activity indicator chạy animation và thực hiện load more data

extension NoDataViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if (scrollView.contentOffset.y == scrollView.contentSize.height - scrollView.frame.size.height) {
            print("load more data.")
            loadMoreData()
        }
    }
}

trong đó hàm load more data

func loadMoreData() {
        if self.isLoadingData {
            print("loading data break.")
            return
        }
        
        if isNoMoreData {
            print("have no more data")
            return
        }
        
        isLoadingData = true
        
        delay(time: 3.0) {[weak self] in
            guard let weakSelf = self else {
                return
            }
            
            guard weakSelf.loadingMoreDataTime != 2 else {
                weakSelf.isNoMoreData = true
                weakSelf.tableView.reloadRows(at: [IndexPath(row: weakSelf.array.count, section: 0)], with: .none)
                weakSelf.isLoadingData = false
                return
            }
            
            for i in 0..<20 {
                weakSelf.array.append("\(i)")
                weakSelf.tableView.beginUpdates()
                weakSelf.tableView.insertRows(at: [IndexPath(row: weakSelf.array.count - 1, section: 0)], with: .automatic)
                weakSelf.tableView.endUpdates()
            }
            
            weakSelf.isLoadingData = false
            weakSelf.loadingMoreDataTime += 1
        }
    }

3.3 cell trên tableview

khi thêm cell loading more chúng ta sẽ thêm nếu data có dữ liệu

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return (array.count > 0 ? (array.count + 1) : array.count)
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let lastIndex = array.count
    
    if indexPath.row == lastIndex {
        let cell = tableView.dequeueReusableCell(withIdentifier: "LoadMoreCell", for: indexPath) as! LoadMoreCell
        if isNoMoreData {
            if cell.activityIndicator.isAnimating {
                cell.activityIndicator.stopAnimating()
            }
            
            cell.titleLabel.text = "No More Data."
        } else {
            cell.activityIndicator.startAnimating()
            cell.titleLabel.text = ""
        }
        return cell
    }
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    cell.textLabel?.text = array[indexPath.row]
    return cell
}