I am developing an application that uses SQLite. I want to show a list of users (UITableView) using a paginating mechanism. Could any one please tell me how to load more dat
You can do that by adding a check on where you're at in the cellForRowAtIndexPath:
method. This method is easy to understand and to implement :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Classic start method
static NSString *cellIdentifier = @"MyCell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MainMenuCellIdentifier];
}
MyData *data = [self.dataArray objectAtIndex:indexPath.row];
// Do your cell customisation
// cell.titleLabel.text = data.title;
BOOL lastItemReached = [data isEqual:[[self.dataArray] lastObject]];
if (!lastItemReached && indexPath.row == [self.dataArray count] - 1)
{
[self launchReload];
}
}
EDIT : added a check on last item to prevent recursion calls. You'll have to implement the method defining whether the last item has been reached or not.
EDIT2 : explained lastItemReached
Just wanna share this approach:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSLog(@"%@", [[YourTableView indexPathsForVisibleRows] lastObject]);
[self estimatedTotalData];
}
- (void)estimatedTotalData
{
long currentRow = ((NSIndexPath *)[[YourTableView indexPathsForVisibleRows] lastObject]).row;
long estimateDataCount = 25;
while (currentRow > estimateDataCount)
{
estimateDataCount+=25;
}
dataLimit = estimateDataCount;
if (dataLimit == currentRow+1)
{
dataLimit+=25;
}
NSLog(@"dataLimit :%ld", dataLimit);
[self requestForData];
// this answers the question..
//
if(YourDataSource.count-1 == currentRow)
{
NSLog(@"LAST ROW"); //loadMore data
}
}
NSLog(...);
output would be something like:
<NSIndexPath: 0xc0000000002e0016> {length = 2, path = 0 - 92}
dataLimit :100
<NSIndexPath: 0xc000000000298016> {length = 2, path = 0 - 83}
dataLimit :100
<NSIndexPath: 0xc000000000278016> {length = 2, path = 0 - 79}
dataLimit :100
<NSIndexPath: 0xc000000000238016> {length = 2, path = 0 - 71}
dataLimit :75
<NSIndexPath: 0xc0000000001d8016> {length = 2, path = 0 - 59}
dataLimit :75
<NSIndexPath: 0xc0000000001c0016> {length = 2, path = 0 - 56}
dataLimit :75
<NSIndexPath: 0xc000000000138016> {length = 2, path = 0 - 39}
dataLimit :50
<NSIndexPath: 0xc000000000120016> {length = 2, path = 0 - 36}
dataLimit :50
<NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}
dataLimit :25
<NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}
dataLimit :25
This is good for displaying data stored locally. Initially I declare the dataLimit to 25, that means uitableview will have 0-24 (initially).
If the user scrolled to the bottom and the last cell is visible dataLimit
will be added with 25...
Note: This is more like a UITableView data paging, :)
Better to use willDisplayCell
method to check if which cell will be loaded.
Once we get the current indexPath.row
is last we can load more cells.
This will load more cells on scrolling down.
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
// check if indexPath.row is last row
// Perform operation to load new Cell's.
}
Worked with UIScrollView / UICollectionView / UITableView
import UIKit
class LoadMoreActivityIndicator {
private let spacingFromLastCell: CGFloat
private let spacingFromLastCellWhenLoadMoreActionStart: CGFloat
private weak var activityIndicatorView: UIActivityIndicatorView?
private weak var scrollView: UIScrollView?
private var defaultY: CGFloat {
guard let height = scrollView?.contentSize.height else { return 0.0 }
return height + spacingFromLastCell
}
deinit { activityIndicatorView?.removeFromSuperview() }
init (scrollView: UIScrollView, spacingFromLastCell: CGFloat, spacingFromLastCellWhenLoadMoreActionStart: CGFloat) {
self.scrollView = scrollView
self.spacingFromLastCell = spacingFromLastCell
self.spacingFromLastCellWhenLoadMoreActionStart = spacingFromLastCellWhenLoadMoreActionStart
let size:CGFloat = 40
let frame = CGRect(x: (scrollView.frame.width-size)/2, y: scrollView.contentSize.height + spacingFromLastCell, width: size, height: size)
let activityIndicatorView = UIActivityIndicatorView(frame: frame)
if #available(iOS 13.0, *)
{
activityIndicatorView.color = .label
}
else
{
activityIndicatorView.color = .black
}
activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
activityIndicatorView.hidesWhenStopped = true
scrollView.addSubview(activityIndicatorView)
self.activityIndicatorView = activityIndicatorView
}
private var isHidden: Bool {
guard let scrollView = scrollView else { return true }
return scrollView.contentSize.height < scrollView.frame.size.height
}
func start(closure: (() -> Void)?) {
guard let scrollView = scrollView, let activityIndicatorView = activityIndicatorView else { return }
let offsetY = scrollView.contentOffset.y
activityIndicatorView.isHidden = isHidden
if !isHidden && offsetY >= 0 {
let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
let offsetDelta = offsetY - contentDelta
let newY = defaultY-offsetDelta
if newY < scrollView.frame.height {
activityIndicatorView.frame.origin.y = newY
} else {
if activityIndicatorView.frame.origin.y != defaultY {
activityIndicatorView.frame.origin.y = defaultY
}
}
if !activityIndicatorView.isAnimating {
if offsetY > contentDelta && offsetDelta >= spacingFromLastCellWhenLoadMoreActionStart && !activityIndicatorView.isAnimating {
activityIndicatorView.startAnimating()
closure?()
}
}
if scrollView.isDecelerating {
if activityIndicatorView.isAnimating && scrollView.contentInset.bottom == 0 {
UIView.animate(withDuration: 0.3) { [weak self] in
if let bottom = self?.spacingFromLastCellWhenLoadMoreActionStart {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
}
}
}
}
}
}
func stop(completion: (() -> Void)? = nil) {
guard let scrollView = scrollView , let activityIndicatorView = activityIndicatorView else { return }
let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
let offsetDelta = scrollView.contentOffset.y - contentDelta
if offsetDelta >= 0 {
UIView.animate(withDuration: 0.3, animations: {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}) { _ in completion?() }
} else {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
completion?()
}
activityIndicatorView.stopAnimating()
}
}
init
activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
handling
extension ViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
activityIndicator.start {
DispatchQueue.global(qos: .utility).async {
sleep(3)
DispatchQueue.main.async { [weak self] in
self?.activityIndicator.stop()
}
}
}
}
}
Do not forget to paste the solution code.
import UIKit
class ViewController: UIViewController {
fileprivate var activityIndicator: LoadMoreActivityIndicator!
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView(frame: view.frame)
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.dataSource = self
tableView.delegate = self
tableView.tableFooterView = UIView()
activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "\(indexPath)"
return cell
}
}
extension ViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
activityIndicator.start {
DispatchQueue.global(qos: .utility).async {
for i in 0..<3 {
print("!!!!!!!!! \(i)")
sleep(1)
}
DispatchQueue.main.async { [weak self] in
self?.activityIndicator.stop()
}
}
}
}
}
Genetic UITableView Extension For Loadmore.
add this UITableView + Extension in your new file
extension UITableView{
func indicatorView() -> UIActivityIndicatorView{
var activityIndicatorView = UIActivityIndicatorView()
if self.tableFooterView == nil{
let indicatorFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 40)
activityIndicatorView = UIActivityIndicatorView(frame: indicatorFrame)
activityIndicatorView.isHidden = false
activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
activityIndicatorView.isHidden = true
self.tableFooterView = activityIndicatorView
return activityIndicatorView
}else{
return activityIndicatorView
}
}
func addLoading(_ indexPath:IndexPath, closure: @escaping (() -> Void)){
indicatorView().startAnimating()
if let lastVisibleIndexPath = self.indexPathsForVisibleRows?.last {
if indexPath == lastVisibleIndexPath && indexPath.row == self.numberOfRows(inSection: 0) - 1 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
}
}
}
indicatorView().isHidden = false
}
func stopLoading(){
indicatorView().stopAnimating()
indicatorView().isHidden = true
}
}
Now, just add following line of code in UITableViewDelegate Method willDisplay Cell in your ViewController and make sure tableView.delegate = self
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// need to pass your indexpath then it showing your indicator at bottom
tableView.addLoading(indexPath) {
// add your code here
// append Your array and reload your tableview
tableView.stopLoading() // stop your indicator
}
}
That's it.. Hope this helpful. Thank You
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger sectionsAmount = [tableView numberOfSections];
NSInteger rowsAmount = [tableView numberOfRowsInSection:[indexPath section]];
if ([indexPath section] == sectionsAmount - 1 && [indexPath row] == rowsAmount - 1) {
//get last row
if (!isSearchActive && !isFilterSearchActive) {
if (totalRecords % 8 == 0) {
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[yourTableView beginUpdates];
[yourTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
[yourTableView endUpdates];
});
}
}
}
}