UITableView загружает больше при прокрутке вниз, как приложение Facebook


96

Я разрабатываю приложение, использующее SQLite. Я хочу показать список пользователей (UITableView) с помощью механизма разбивки на страницы. Может ли кто-нибудь сказать мне, как загрузить больше данных в мой список, когда пользователь прокручивает до конца списка (например, на домашней странице в приложении Facebook)?

Ответы:


103

Вы можете сделать это, добавив проверку того, где вы находитесь в cellForRowAtIndexPath:методе. Этот метод легко понять и реализовать:

- (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: добавлена ​​проверка последнего элемента для предотвращения рекурсивных вызовов. Вам нужно будет реализовать метод, определяющий, был ли достигнут последний элемент.

EDIT2: объяснил lastItemReached


9
Что, если пользователь прокручивает вверх и вниз, поэтому cellForRowAtIndexPath вызывается МНОГО РАЗ! ??
onmyway133

В первый раз, когда он прокрутит вниз, его список будет перезагружен. И каждый раз, когда он достигает дна, будет собираться новый блок данных. Если необходимо применить какое-либо конкретное лечение launchReload, ответственность за его обработку будет
ложиться на

4
Мне пришлось добавить флаг, чтобы предотвратить проблему рекурсии при попадании последнего элемента:if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
Альберт Бори,

Какой self.launchReloadметод?
слайдер

1
@shinyuX не работает для меня, «если» всегда ложно ... но если (lastItemReached && indexPath.row == [self.dataArray count] - 1) true, ПОЧЕМУ?
Сказано

69

Swift

Метод 1: прокрутите вниз

Вот версия Swift ответа Педро Романа . Когда пользователь прекращает прокрутку, он проверяет, достиг ли он дна.

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    // UITableView only moves in one direction, y axis
    let currentOffset = scrollView.contentOffset.y
    let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height

    // Change 10.0 to adjust the distance from bottom
    if maximumOffset - currentOffset <= 10.0 {
        self.loadMore()
    }
}

Метод 2: дошел до последней строки

А вот и ответ shinyuX на Swift . Он проверяет, дошел ли пользователь до последней строки.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    // set up cell
    // ...

    // Check if the last row number is the same as the last current data element
    if indexPath.row == self.dataArray.count - 1 {
        self.loadMore()
    }

}

Пример loadMore()метода

Я установил эти три переменные класса для выборки пакетов данных.

// number of items to be fetched each time (i.e., database LIMIT)
let itemsPerBatch = 50

// Where to start fetching items (database OFFSET)
var offset = 0

// a flag for when all database items have already been loaded
var reachedEndOfItems = false

Это функция для загрузки большего количества элементов из базы данных в табличное представление.

func loadMore() {

    // don't bother doing another db query if already have everything
    guard !self.reachedEndOfItems else {
        return
    }

    // query the db on a background thread
    DispatchQueue.global(qos: .background).async {

        // determine the range of data items to fetch
        var thisBatchOfItems: [MyObjects]?
        let start = self.offset
        let end = self.offset + self.itemsPerBatch

        // query the database
        do {
            // SQLite.swift wrapper
            thisBatchOfItems = try MyDataHelper.findRange(start..<end)
        } catch _ {
            print("query failed")
        }

        // update UITableView with new batch of items on main thread after query finishes
        DispatchQueue.main.async {

            if let newItems = thisBatchOfItems {

                // append the new items to the data source for the table view
                self.myObjectArray.appendContentsOf(newItems)

                // reload the table view
                self.tableView.reloadData()

                // check if this was the last of the data
                if newItems.count < self.itemsPerBatch {
                    self.reachedEndOfItems = true
                    print("reached end of data. Batch count: \(newItems.count)")
                }

                // reset the offset for the next data query
                self.offset += self.itemsPerBatch
            }

        }
    }
}

Я использовал метод 1, потому что хотел получить больше. Отлично работает. Спасибо вам обоим!
Боб Уэйкфилд,

37

Лучше использовать willDisplayCellметод, чтобы проверить, будет ли загружена какая ячейка. Как только мы получим indexPath.rowпоследний ток, мы сможем загрузить больше ячеек. Это загрузит больше ячеек при прокрутке вниз.

 - (void)tableView:(UITableView *)tableView 
       willDisplayCell:(UITableViewCell *)cell    
       forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // check if indexPath.row is last row
    // Perform operation to load new Cell's.
}

16
не лучше, ведь reloadData снова вызовет этот метод, верно?
Марчин

Будет ли это работать, если у нас тоже будут разделы?
Абдул Ясин,

Да, это будет работать для разделов, indexPath предоставит вам строку и раздел.
Сурадж Мираджкар

24

подробности

  • Swift 5.1, Xcode 11.2.1

Решение

Работал с 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)
        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()
    }
}

использование

в этом

activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)

обработка

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

Полный образец

Не забудьте вставить код решения.

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

Результат

введите описание изображения здесь


Прекрасно работает. Но у меня есть заголовок в моем табличном представлении, после перетаскивания, чтобы загрузить больше, заголовок перейдет под панель навигации. UIEdgeInsetsMake в loadMoreActionFinshed должен быть установлен в (62, 0, 0, 0), учитывая 66 = navbar.height + 22
Desmond

Он должен работать в CollectionView при вертикальной прокрутке.
Василий Боднарчук

Невероятно ... Круто!
Tà Truhoada,

какая-либо версия этого объекта-c?
Сайед Али Салман

@VasilyBodnarchuk, без проблем, я сделаю это и поделюсь с другими
Сайед Али Салман

18
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
    NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1;
    if ((indexPath.section == lastSectionIndex) && (indexPath.row == lastRowIndex)) {
        // This is the last cell
        [self loadMore];
    }
}

Если вы используете Core Data и NSFetchedResultsController, это loadMoreможет выглядеть следующим образом:

// Load more
- (void)loadMore {
    [self.fetchedResultsController.fetchRequest setFetchLimit:newFetchLimit];
    [NSFetchedResultsController deleteCacheWithName:@"cache name"];
    NSError *error;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }

    [self.tableView reloadData];
}

Я пытаюсь реализовать это, но я использую массив результатов, а не sqlite, мне было интересно, как я буду добавлять больше в текущий NSMutableArray, который у меня есть, а затем перезагружать данные, потому что в противном случае данные будут перезаписаны ... Я попробовал это [имена addObjectsFromArray: [responseObject valueForKeyPath: @ "name"]]; но это не работает ... вот ссылка на мой вопрос stackoverflow.com/questions/23446780/…
Lion789

1
Какой смысл обновлять данные каждый раз, когда вы получаете новые данные? Если frc настроен правильно, достаточно однократной выборки, он будет обновляться соответствующим образом по мере необходимости. Получение его каждый раз, при условии, что запрос frc на выборку настроен для контекста основного потока, будет блокировать основной поток, когда он попадает на диск, что совершенно не подходит для взаимодействия с пользователем, когда пользователю нужны новые данные.
MANIAK_dobrii

Первая половина очень помогла мне, спасибо. (Без использования FetchedResultsVC)
weienw

@MANIAK_dobrii правильный. Одна из ключевых особенностей NSFetchedResultsController заключается в том, что он вычисляет данные подкачки, так что вы получаете бесплатную виртуальную прокрутку при подключении к UITableView. Реализация такой функции loadMore должна быть необходима только в том случае, если вы фактически заполняете хранилище CoreData дополнительными данными, и в этом случае нет необходимости выполнять еще одну функцию performFetch, если ваш NSFetchedResultsController настроен правильно.
Али Гангджи

Те же вопросы, что и другие ответы. reloadData вызывает это несколько раз.
Дайсон возвращается

11

Я реализовал одно решение, которое нашел в stackoverflow, и оно отлично работает, но я думаю, что решение shinyuX очень легко реализовать и отлично подходит для моего предложения. Если кто-то хочет другое решение, можете использовать это ниже.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{

   // UITableView only moves in one direction, y axis
    CGFloat currentOffset = scrollView.contentOffset.y;
    CGFloat maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    //NSInteger result = maximumOffset - currentOffset;

    // Change 10.0 to adjust the distance from bottom
    if (maximumOffset - currentOffset <= 10.0) {
        [self loadOneMorePage];
        //[self methodThatAddsDataAndReloadsTableView];
    }
}

Я думаю, что есть разные сценарии для представления представления, в моем случае ваше решение сработало, мне нужно было что-то вроде этого
Raheel Sadiq

Если пользователь сильно бросает, то есть на высоту 1,5 экрана, дно может быть достигнуто без запуска обновления.
Дайсон возвращается

но он прокручивает список вверх
Mansuu ....

7

подробности

  • Swift 5.1, Xcode 11.3.1

Решение

Расширение Genetic UITableView для Loadmore.

добавьте это расширение UITableView + в свой новый файл

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

Теперь просто добавьте следующую строку кода в UITableViewDelegate Method willDisplay Cell в вашем ViewController и убедитесь, что 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
    }
}

Результат

введите описание изображения здесь

Вот и все .. Надеюсь, это поможет. Спасибо


Что нужно учитывать. Просто добавьте 'tableFooterView = nil' внутрь функции stoploading, иначе вращение индикатора не прекратит анимацию. Также в ActivityIndicator есть свойство hidesWhenStopped, поэтому вам не нужно вручную устанавливать скрытый true / false для индикатора. Но в целом выглядит отлично :)
zramled

1
Спасибо за предложение, я проверю один раз и отредактирую этот ответ :-)
Йогеш Патель

6

Используйте limit и offset в своих запросах и заполните таблицу этим контентом. Когда пользователь прокручивает вниз, загрузите следующее смещение.

Реализуйте tableView:willDisplayCell:forRowAtIndexPath:метод в своем UITableViewDelegateи проверьте, последняя ли строка


5

Ссылка ниже предоставит образец кода. # Swift3

Пользователь должен открыть последнюю ячейку табличного представления, по крайней мере, высотой 2 ячейки, чтобы получить больше данных с сервера.

Вы также найдете ячейку «Процесс», чтобы показать процесс загрузки, как в последней ячейке.

Это в Swift3

https://github.com/yogendrabagoriya/YBTableViewPullData


3

Еще один вариант использования ( Swift 3 и iOS 10+):

class DocumentEventsTableViewController: UITableViewController, UITableViewDataSourcePrefetching {

     var currentPage: Int = 1
     let pageSize: Int = 10 // num of items in one page

     override func viewDidLoad() {
         super.viewDidLoad()

         self.tableView.prefetchDataSource = self
     }

     func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
         let upcomingRows = indexPaths.map { $0.row }

         if let maxIndex = upcomingRows.max() {

            let nextPage: Int = Int(ceil(Double(maxIndex) / Double(pageSize))) + 1

            if nextPage > currentPage {
                 // Your function, which attempts to load respective page from the local database
                 loadLocalData(page: nextPage)

                 // Your function, which makes a network request to fetch the respective page of data from the network
                 startLoadingDataFromNetwork(page: nextPage) 

                 currentPage = nextPage
             }
         }
     }
 }

Для довольно небольших страниц (~ 10 элементов) вы можете захотеть вручную добавить данные для страниц 1 и 2, потому что nextPage может быть где-то около 1-2, пока в таблице не появится несколько элементов, которые нужно хорошо прокрутить. Но он отлично подойдет для всех следующих страниц.


1
Это работает только для данных только для чтения. Не работает, если у вас есть такие функции, как удаление какой-то строки и загрузка дополнительных, поскольку pageSize здесь исправлен и не может загружать больше, даже если после обновления источника данных будет больше.
EI Captain v2.0

2
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    if (news.count == 0) {
        return 0;
    } else {
        return news.count +  1 ;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    @try {

        uint position = (uint) (indexPath.row);
        NSUInteger row = [indexPath row];
        NSUInteger count = [news count];

        //show Load More
        if (row == count) {
            UITableViewCell *cell = nil;

            static NSString *LoadMoreId = @"LoadMore";
            cell = [tableView dequeueReusableCellWithIdentifier:LoadMoreId];
            if (cell == nil) {
                cell = [[UITableViewCell alloc]
                        initWithStyle:UITableViewCellStyleDefault
                      reuseIdentifier:LoadMoreId];
            }
            if (!hasMoreLoad) {
                cell.hidden = true;
            } else {

                cell.textLabel.text = @"Load more items...";
                cell.textLabel.textColor = [UIColor blueColor];
                cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
                NSLog(@"Load more");
                if (!isMoreLoaded) {
                    isMoreLoaded = true;
                    [self performSelector:@selector(loadMoreNews) withObject:nil afterDelay:0.1];
                }
            }

            return cell;

        } else {
            NewsRow *cell = nil;

            NewsObject *newsObject = news[position];
            static NSString *CellIdentifier = @"NewsRow";
            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

            if (cell == nil) {
                // Load the top-level objects from the custom cell XIB.
                NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
                // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
                cell = topLevelObjects[0];
                // Configure the cell...

            }

            cell.title.text = newsObject.title;             
            return cell;
        }

    }
    @catch (NSException *exception) {
        NSLog(@"Exception occurred: %@, %@", exception, [exception userInfo]);
    }
    return nil;
}

очень хорошее объяснение этого поста.

http://useyourloaf.com/blog/2010/10/02/dynamically-loading-new-rows-into-a-table.html

просто вам нужно добавить последнюю строку и скрыть ее, и когда строка таблицы попадет в последнюю строку, чем отобразить строку и загрузить больше элементов.


1

вам следует проверить ios UITableViewDataSourcePrefetching.

class ViewController: UIViewController {
    @IBOutlet weak var mytableview: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        mytableview.prefetchDataSource = self
    }

 func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        print("prefetchdRowsAtIndexpath \(indexPaths)")
    }

    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        print("cancelPrefetchingForRowsAtIndexpath \(indexPaths)")
    }


}

1

для загрузки из API, у меня работает, Xcode 10 , swift 4.2 :

1- создайте новый файл Swift и сделайте так:

//
//  apiTVCController.swift
//  ApiTestingTableView
//
//  Created by Hooma7n on 4/7/19.
//  Copyright © 2019 Hooma7n. All rights reserved.
//

import Foundation
import Alamofire

class apiget {

    var tableData : [Datum] = []
    var loadin : [Datum] = []
    var testfortotal : Int?


    func getfromapi(completionHandler : ((_ isSucess : Bool) -> Void)?) {
        let url = "https://reqres.in/api/users?page=1"
        Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
            .responseJSON(completionHandler : { response in
                switch response.result {
                case .success(let data):
                    guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                    let decoder = JSONDecoder()
                    guard let result = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                    self.tableData = result.data ?? []
                    self.testfortotal = result.total ?? 0
                    completionHandler?(true)

                //                    print(result)
                case .failure(let error):
                    print(error)
                }
            })
    }

    var pagecounter : Int = 2


    func loadmore(completionHandler : ((_ isSucess : Bool) -> Void)?){

        let url = "https://reqres.in/api/users?page=\(pagecounter)"
        Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
            .responseJSON(completionHandler : { response in
                switch response.result {
                case .success(let data):
                    guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                    let decoder = JSONDecoder()
                    guard let myresult = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                    self.loadin = myresult.data ?? []
                    self.tableData.append(contentsOf: myresult.data ?? [])
                    completionHandler?(true)
                    print(self.pagecounter)
                    self.pagecounter += 1

                //                    print(myresult)
                case .failure(let error):
                    print(error)
                }
            })

    }

}

extension apiget {

    struct Welcome: Codable {
        let page, perPage, total, totalPages: Int?
        var data: [Datum]?

        enum CodingKeys: String, CodingKey {
            case page
            case perPage = "per_page"
            case total
            case totalPages = "total_pages"
            case data
        }
    }

    struct Datum: Codable {
        let id: Int?
        let firstName, lastName: String?
        let avatar: String?

        enum CodingKeys: String, CodingKey {
            case id
            case firstName = "first_name"
            case lastName = "last_name"
            case avatar
        }
    }


}

2- в вашем файле ViewController (tableView Controller):

//
//  apiTVC.swift
//  ApiTestingTableView
//
//  Created by Hooma7n on 4/7/19.
//  Copyright © 2019 Hooma7n. All rights reserved.
//

import UIKit
import Alamofire

class apiTVC: UITableViewController {

    var datamodel = apiget()

    override func viewDidLoad() {
        super.viewDidLoad()

        datamodel.getfromapi(completionHandler: {finish in
            if finish {self.tableView.reloadData()
            }

        })

    }


    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datamodel.tableData.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! apiTableViewCell
        cell.firstNameLabel.text = datamodel.tableData[indexPath.row].firstName
        cell.lastNameLabel.text = datamodel.tableData[indexPath.row].lastName
        cell.dateLabel.text = "\(datamodel.tableData[indexPath.row].id ?? 0)"
        cell.profileImageView.loadImage(fromURL: datamodel.tableData[indexPath.row].avatar ?? "")

        return cell

    }

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        let lastElement = datamodel.tableData.count - 1
        let total = datamodel.testfortotal ?? 12
        if indexPath.row == lastElement && datamodel.tableData.count < total{

            datamodel.loadmore(completionHandler: {finish in
                if finish {

                    self.tableView.reloadData()

                }})
        }
    }
}

при использовании Tableview в вашем ViewController набора делегата , источника данных я в viewDidLoad.


0

Просто хочу поделиться этим подходом:

- (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(...); вывод будет примерно таким:

<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

Это хорошо для отображения данных, хранящихся локально. Первоначально я объявляю dataLimit равным 25, это означает, что uitableview будет иметь значение 0-24 (изначально).

Если пользователь прокручивает вниз и отображается последняя ячейка, dataLimitбудет добавлено 25 ...

Примечание: это больше похоже на подкачку данных UITableView :)


0
-(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];
            });
        }
    }
}
}

после отображения последней строки вставьте строки, то есть beginUpdates .. и используйте некоторую задержку, чтобы избежать сбоев.
Sahila Mirajkar

0

Лучший способ решить эту проблему - добавить ячейку внизу таблицы, и эта ячейка будет содержать индикатор.

В быстром темпе вам нужно добавить это:

  1. Создайте новую ячейку типа cellLoading, в которой будет находиться индикатор. Посмотрите на код ниже
  2. Посмотрите на количество строк и добавьте к нему 1 (это для загрузки ячейки).
  3. вам нужно проверить rawAtIndex, если idexPath.row == yourArray.count, затем вернуть ячейку загрузки.

посмотрите на код ниже:

import UIKit

class LoadingCell: UITableViewCell {

@IBOutlet weak var indicator: UIActivityIndicatorView!


}

Для табличного представления: numOfRows:

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

cellForRawAt indexPath:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    if indexPath.row == users.count  {
        // need to change
        let loading = Bundle.main.loadNibNamed("LoadingCell", owner: LoadingCell.self , options: nil)?.first as! LoadingCell
        return loading

    }

    let yourCell = tableView.dequeueReusableCell(withIdentifier: "cellCustomizing", for: indexPath) as! UITableViewCell

    return yourCell

}

Если вы заметили, что моя ячейка загрузки создана из файла пера. Это видео объяснит, что я сделал.


0
let threshold = 100.0 // threshold from bottom of tableView
var isLoadingMore = false // flag


func scrollViewDidScroll(scrollView: UIScrollView) {
    let contentOffset = scrollView.contentOffset.y
    let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    if !isLoadingMore && (maximumOffset - contentOffset <= threshold) {
        // Get more data - API call
        self.isLoadingMore = true

        // Update UI
        dispatch_async(dispatch_get_main_queue()) {
            tableView.reloadData()
            self.isLoadingMore = false
        }
    }
  }

0

Для Xcode 10.1, Swift 4.2

Это видео похоже на отличное руководство!

Начальный / полный проект: https://github.com/RobCanton/Swift-Infinite-Scrolling-Example

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var tableView:UITableView!

    var fetchingMore = false
    var items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        initTableView()
    }

    func initTableView() {
        tableView = UITableView(frame: view.bounds, style: .plain)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
        tableView.delegate = self
        tableView.dataSource = self

        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false

        let layoutGuide = view.safeAreaLayoutGuide
        tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true

        tableView.reloadData()
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
            cell.textLabel?.text = "Item \(items[indexPath.row])"
            return cell
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height

        if offsetY > contentHeight - scrollView.frame.height * 4 {
            if !fetchingMore {
                beginBatchFetch()
            }
        }
    }

    func beginBatchFetch() {
        fetchingMore = true
        print("Call API here..")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.50, execute: {
            print("Consider this as API response.")
            let newItems = (self.items.count...self.items.count + 12).map { index in index }
            self.items.append(contentsOf: newItems)
            self.fetchingMore = false
            self.tableView.reloadData()
        })
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.