В Java все находится в форме класса.
Если вы хотите использовать любой объект, тогда у вас есть две фазы:
Пример:
Object a;
a=new Object();
То же самое для концепции массива
Item i[]=new Item[5];
i[0]=new Item();
Если вы не дают секцию инициализации, тогда возникает NullpointerException
.
Вы можете сделать это, добавив проверку, где вы находитесь в методе 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
Просто хочу поделиться этим подходом:
- (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,:)
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
}
}
}
Способ 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: Достиг последней строки
И вот версия Swift shinyuX . Он проверяет, достиг ли пользователь последней строки.
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
}
}
}
}
-(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];
});
}
}
}
}
Лучший способ решить эту проблему - добавить ячейку в нижней части таблицы, и эта ячейка будет содержать индикатор.
В swift вам нужно добавить это:
смотрите код ниже:
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
}
Если вы заметили, что моя загрузочная ячейка создана из файла nib. Это видео объяснит, что я сделал.
Я реализовал одно решение, которое я нашел в 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];
}
}
- (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];
}
Используйте лимит и смещение в ваших запросах и заполните таблицу с этим контентом. Когда пользователь прокручивается вниз, загрузите следующее смещение.
Внесите метод tableView:willDisplayCell:forRowAtIndexPath:
в ваш UITableViewDelegate
и проверьте, является ли это последней строкой
xCode 8.3.1, Swift 3.1
import UIKit
class LoadMoreActivityIndicator {
let spacingFromLastCell: CGFloat
let spacingFromLastCellWhenLoadMoreActionStart: CGFloat
let activityIndicatorView: UIActivityIndicatorView
weak var tableView: UITableView!
private var defaultY: CGFloat {
return tableView.contentSize.height + spacingFromLastCell
}
init (tableView: UITableView, spacingFromLastCell: CGFloat, spacingFromLastCellWhenLoadMoreActionStart: CGFloat) {
self.tableView = tableView
self.spacingFromLastCell = spacingFromLastCell
self.spacingFromLastCellWhenLoadMoreActionStart = spacingFromLastCellWhenLoadMoreActionStart
let size:CGFloat = 40
let frame = CGRect(x: (tableView.frame.width-size)/2, y: tableView.contentSize.height + spacingFromLastCell, width: size, height: size)
activityIndicatorView = UIActivityIndicatorView(frame: frame)
activityIndicatorView.color = .black
activityIndicatorView.isHidden = false
activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
tableView.addSubview(activityIndicatorView)
activityIndicatorView.isHidden = isHidden
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var isHidden: Bool {
if tableView.contentSize.height < tableView.frame.size.height {
return true
} else {
return false
}
}
func scrollViewDidScroll(scrollView: UIScrollView, loadMoreAction: ()->()) {
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 < tableView.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()
loadMoreAction()
}
}
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 = UIEdgeInsetsMake(0, 0, bottom, 0)
}
}
}
}
}
}
func loadMoreActionFinshed(scrollView: UIScrollView) {
let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
let offsetDelta = scrollView.contentOffset.y - contentDelta
if offsetDelta >= 0 {
// Animate hiding when activity indicator displaying
UIView.animate(withDuration: 0.3) {
scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
} else {
// Hiding without animation when activity indicator displaying
scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
activityIndicatorView.stopAnimating()
activityIndicatorView.isHidden = false
}
}
extension ViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
activityIndicator.scrollViewDidScroll(scrollView: scrollView) {
DispatchQueue.global(qos: .utility).async {
for i in 0..<3 {
print(i)
sleep(1)
}
DispatchQueue.main.async { [weak self] in
self?.activityIndicator.loadMoreActionFinshed(scrollView: scrollView)
}
}
}
}
}
ViewController.swift
import UIKit class ViewController: UIViewController { var count = 30 @IBOutlet weak var tableView: UITableView! fileprivate var activityIndicator: LoadMoreActivityIndicator! override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self tableView.tableFooterView = UIView() activityIndicator = LoadMoreActivityIndicator(tableView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60) print(tableView.frame) } } extension ViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return count } 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.scrollViewDidScroll(scrollView: scrollView) { DispatchQueue.global(qos: .utility).async { for i in 0..<3 { print("!!!!!!!!! \(i)") sleep(1) } DispatchQueue.main.async { [weak self] in self?.activityIndicator.loadMoreActionFinshed(scrollView: scrollView) } } } } }
Main.storyboard
<?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> <dependencies> <deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> <!--View Controller--> <scene sceneID="tne-QT-ifu"> <objects> <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_20269474" customModuleProvider="target" sceneMemberID="viewController"> <layoutGuides> <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> </layoutGuides> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="6la-L6-Fo3"> <rect key="frame" x="0.0" y="20" width="375" height="647"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> </tableView> </subviews> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <constraints> <constraint firstItem="6la-L6-Fo3" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="GvO-lp-VW8"/> <constraint firstAttribute="trailing" secondItem="6la-L6-Fo3" secondAttribute="trailing" id="aGF-Ie-T6Y"/> <constraint firstItem="6la-L6-Fo3" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="obO-Sn-WYo"/> <constraint firstItem="6la-L6-Fo3" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="zp5-B1-sWe"/> </constraints> </view> <connections> <outlet property="tableView" destination="6la-L6-Fo3" id="4lR-IX-vtT"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> </objects> <point key="canvasLocation" x="48.799999999999997" y="37.331334332833585"/> </scene> </scenes> </document>
Результат
Еще одна опция для использования (Swift 3 и iOS 10 +):
class DocumentEventsTableViewController: UITableViewController, UITableViewDataSourcePrefetching {
var currentPage: Int = 1
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(APICall.defaultPageSize))) + 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, пока в таблице не будет прокручено несколько элементов. Но он будет отлично работать для всех следующих страниц.
- (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
просто вам нужно добавить последнюю строку и скрыть ее, а когда строка таблицы попадет в последнюю строку, чем показать строку и загрузить больше элементов.
Ниже ссылки будет приведен пример кода. # Swift3
Пользователь должен поднять последнюю ячейку представления таблицы, по крайней мере, высоту 2 ячейки, чтобы получить больше данных с сервера.
Вы также обнаружите, что ячейка процесса также показывает процесс загрузки, как в последней ячейке.
Его в Swift3
launchReload
должен обрабатывать его (например, только одно асинхронное действие перезагрузки за раз) – shinyuX 19 March 2014 в 10:58if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
– Albert Bori 22 November 2014 в 19:51self.launchReload
? – chicobermuda 22 February 2016 в 19:23launchReload
- это ваш собственный метод сбора данных, которые будут заполнять ваш dataArray. Может быть вызов веб-службы, какой-то локальный синтаксический анализ json, .. – shinyuX 25 February 2016 в 11:33