SQLite를 사용하는 응용 프로그램을 개발 중입니다. 페이지 매기기 메커니즘을 사용하여 사용자 목록 (UITableView)을 표시하고 싶습니다. 사용자가 목록 끝으로 스크롤 할 때 내 목록에 더 많은 데이터를로드하는 방법을 알려주시겠습니까 (예 : Facebook 애플리케이션의 홈 페이지)?
답변
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];
}
}
편집 : 재귀 호출을 방지하기 위해 마지막 항목에 대한 검사를 추가했습니다. 마지막 항목에 도달했는지 여부를 정의하는 메서드를 구현해야합니다.
EDIT2 : lastItemReached 설명
답변
빠른
방법 1 : 아래로 스크롤 했습니까?
다음은 Pedro Romão의 답변 의 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
}
}
}
}
답변
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.
}
답변
세부
- 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()
}
}
}
}
}
결과
답변
- (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];
}
}
당신이 코어 데이터를 사용하는 경우 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];
}
답변
나는 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];
}
}
답변
세부
- Swift 5.1, Xcode 11.3.1
해결책
Loadmore를위한 유전 UITableView 확장.
이 UITableView + Extension을 새 파일에 추가하십시오.
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
}
}
이제 ViewController의 UITableViewDelegate 메서드 willDisplay Cell에 다음 코드 줄을 추가하고 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
}
}
결과
그게 다야 .. 도움이 되었기를 바랍니다. 감사합니다