UFO ET IT

UITableView 로드 종료를 감지하는 방법

ufoet 2023. 6. 2. 21:53
반응형

UITableView 로드 종료를 감지하는 방법

로드가 완료되면 테이블의 오프셋을 변경하고 테이블에 로드된 셀 수에 따라 오프셋을 변경하려고 합니다.

SDK에서 적합한 보기 로드가 언제 완료되었는지 알 수 있습니까?대리인이나 데이터 소스 프로토콜에는 아무것도 보이지 않습니다.

보이는 셀만 로딩되기 때문에 데이터 소스의 개수를 사용할 수 없습니다.

@RichX :lastRow둘 다 될 수 있습니다[tableView numberOfRowsInSection: 0] - 1또는((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row코드는 다음과 같습니다.

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row){
        //end of loading
        //for example [activityIndicator stopAnimating];
    }
}

업데이트: 음, @htafoya의 의견이 맞습니다.이 코드가 소스에서 모든 데이터를 로드하는 종료를 감지하도록 하려면 그렇지 않지만, 원래 질문은 아닙니다.이 코드는 표시되어야 할 모든 셀이 표시되는 시기를 감지하기 위한 것입니다. willDisplayCell:매끄러운 UI를 위해 여기에 사용됩니다(단일 셀은 일반적으로 다음 시간 이후에 빠르게 표시됨).willDisplay:call를해 볼 . 또한 사용해 볼 수도 있습니다.tableView:didEndDisplayingCell:.

Swift 3 & 4 & 5 버전:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if let lastVisibleIndexPath = tableView.indexPathsForVisibleRows?.last {
        if indexPath == lastVisibleIndexPath {
            // do here...
        }
    }
}

저는 항상 매우 간단한 솔루션을 사용합니다.

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([indexPath row] == lastRow){
        //end of loading
        //for example [activityIndicator stopAnimating];
    }
}

여기 저에게 효과가 있을 것 같은 다른 옵션이 있습니다.ViewForFooter 대리자 메서드에서 마지막 섹션인지 확인하고 코드를 추가합니다.이러한 접근 방식은 willDisplayCell이 풋러를 가지고 있다면 그들을 고려하지 않는다는 것을 깨닫고 떠올랐습니다.

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section 
{
  // Perform some final layout updates
  if (section == ([tableView numberOfSections] - 1)) {
    [self tableViewWillFinishLoading:tableView];
  }

  // Return nil, or whatever view you were going to return for the footer
  return nil;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
  // Return 0, or the height for your footer view
  return 0.0;
}

- (void)tableViewWillFinishLoading:(UITableView *)tableView
{
  NSLog(@"finished loading");
}

는 이 이 만약 의 끝 합니다.UITableView그리고 단순히 보이는 세포가 아닙니다.필요에 따라 보이는 셀만 원할 수 있으며, 이 경우 Flex의 답변이 좋은 경로입니다.

개인 API 사용:

@objc func tableViewDidFinishReload(_ tableView: UITableView) {
    print(#function)
    cellsAreLoaded = true
}

공용 API 사용:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // cancel the perform request if there is another section
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];

    // create a perform request to call the didLoadRows method on the next event loop.
    [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];

    return [self.myDataSource numberOfRowsInSection:section];
}

// called after the rows in the last section is loaded
-(void)tableViewDidLoadRows:(UITableView*)tableView{
    self.cellsAreLoaded = YES;
}

더 나은 설계는 보이는 셀을 세트에 추가하는 것입니다. 그런 다음 테이블이 로드되었는지 확인해야 할 때 대신 이 세트 주위에 루프를 수행할 수 있습니다. 예를 들어,

var visibleCells = Set<UITableViewCell>()

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    visibleCells.insert(cell)
}

override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    visibleCells.remove(cell)
}

// example property you want to show on a cell that you only want to update the cell after the table is loaded. cellForRow also calls configure too for the initial state.
var count = 5 {
    didSet {
        for cell in visibleCells {
            configureCell(cell)
        }
    }
}

신속한 솔루션:

// willDisplay function
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    let lastRowIndex = tableView.numberOfRowsInSection(0)
    if indexPath.row == lastRowIndex - 1 {
        fetchNewDataFromServer()
    }
}

// data fetcher function
func fetchNewDataFromServer() {
    if(!loading && !allDataFetched) {
        // call beginUpdates before multiple rows insert operation
        tableView.beginUpdates()
        // for loop
        // insertRowsAtIndexPaths
        tableView.endUpdates()
    }
}

Swift 3에서 선택한 응답 버전의 경우:

var isLoadingTableView = true

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if tableData.count > 0 && isLoadingTableView {
        if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows, let lastIndexPath = indexPathsForVisibleRows.last, lastIndexPath.row == indexPath.row {
            isLoadingTableView = false
            //do something after table is done loading
        }
    }
}

기본 셀을 선택하기 전에 테이블 로드가 완료되었는지 확인하고 싶어서 isLoadingTableView 변수가 필요했습니다.이 옵션을 포함하지 않으면 테이블을 스크롤할 때마다 코드가 다시 호출됩니다.

제가 아는 가장 좋은 방법은 Eric의 대답입니다. UITableView에서 데이터 요청이 완료되면 알림을 받으시겠습니까?

업데이트:이하려면 이 를 업이트: 작하려이전연합니다야결해를화에 넣어야 .-tableView:cellForRowAtIndexPath:

[tableView beginUpdates];
[tableView endUpdates];

표 보기가 내용 로드를 완료하는 시기를 알기 위해서는 먼저 보기가 화면에 표시되는 방법에 대한 기본적인 이해가 필요합니다.

앱의 라이프 사이클에는 4가지 주요 순간이 있습니다.

  1. 앱이 이벤트(터치, 타이머, 블록 디스패치 등)를 수신합니다.
  2. 앱이 이벤트를 처리합니다(제약 조건 수정, 애니메이션 시작, 배경 변경 등).
  3. 앱이 새 보기 계층을 계산합니다.
  4. 앱이 보기 계층을 렌더링하고 표시합니다.

2번과 3번은 완전히 분리되어 있습니다.이유는? 성능상의 이유로 수정 작업이 수행될 때마다 3에서 수행되는 모든 계산을 수행하지 않기 때문입니다.

그래서, 저는 당신이 다음과 같은 사건에 직면해 있다고 생각합니다.

tableView.reloadData()
tableView.visibleCells.count // wrong count oO

여기 왜 그래요?

표 보기는 내용을 느리게 다시 로드합니다.사실, 당이전를하면화신,▁you하면▁call▁actually를,전▁if.reloadData여러 번에 걸쳐 성능 문제가 발생하지 않습니다.테이블 보기는 위임 구현을 기반으로 내용 크기만 재계산하고 셀을 로드하는 순간 3을 기다립니다.이 시간을 레이아웃 패스라고 합니다.

좋아요, 어떻게 하면 레이아웃 패스에 참여할 수 있을까요?

레이아웃 통과 중에 앱은 뷰 계층의 모든 프레임을 계산합니다.참여하려면 전용 메서드를 재정의할 수 있습니다.layoutSubviews,updateLayoutConstraints▁a로 된 등.UIView하위 클래스 및 뷰 컨트롤러 하위 클래스의 동등한 메서드입니다.

그것이 바로 테이블 뷰가 하는 일입니다.그것은 무시됩니다.layoutSubviews위임 구현에 따라 셀을 추가하거나 제거합니다.은 콜트이라고 .cellForRow세포를 에, 새운세포추배를직치전에기하고가하로,willDisplay직후에전화하셨다면reloadData또는 테이블 뷰를 계층에 추가하면 테이블 뷰는 필요한 만큼 셀을 추가하여 이 키 순간에 프레임을 채웁니다.

좋아요, 하지만 이제, 표 보기가 언제 내용을 다시 로드했는지 알 수 있는 방법은 무엇입니까?

이 질문을 다시 표현할 수 있습니다. 표 보기의 하위 보기 레이아웃이 언제 완료되었는지 어떻게 알 수 있습니까?

가장 쉬운 방법은 표 보기의 레이아웃으로 이동하는 것입니다.

class MyTableView: UITableView {
    func layoutSubviews() {
        super.layoutSubviews()
        // the displayed cells are loaded
    }
}

이 방법은 테이블 뷰의 수명 주기에서 여러 번 호출됩니다.스크롤과 테이블 뷰의 대기열 해제 동작으로 인해 셀이 수정, 제거 및 추가되는 경우가 많습니다. 효과가 있어요, 에 바로 로바과만가.super.layoutSubviews()셀이 로드되었습니다.이 솔루션은 대기 시간에 해당합니다.willDisplay마지막 인덱스 경로의 이벤트입니다.는 이이는의호중출다니됩에실행벤트를 실행하는 됩니다.layoutSubviews셀이 추가될 때 테이블 뷰의

또 다른 방법은 앱이 레이아웃 패스를 마쳤을알림을 받는 것입니다.

설명서에 설명된 대로 의 옵션을 사용할 수 있습니다.UIView.animate(withDuration:completion):

tableView.reloadData()
UIView.animate(withDuration: 0) {
    // layout done
}

이 솔루션은 작동하지만 레이아웃이 완료된 시간과 블록이 실행된 시간 사이에 화면이 한 번 새로 고쳐집니다.은 이는다같다니습과음▁▁toent다에 해당합니다.DispatchMain.async솔루션이지만 지정되었습니다.

또는 테이블 뷰의 레이아웃을 강제로 적용하는 것이 좋습니다.

프레임을 이 있습니다.layoutIfNeeded:

tableView.reloadData()
table.layoutIfNeeded()
// layout done

그러나 이렇게 하면 시스템에서 사용하는 게으른 로드가 제거됩니다.이러한 메서드를 반복적으로 호출하면 성능 문제가 발생할 수 있습니다.테이블 뷰 프레임이 계산되기 전에 호출되지 않는지 확인합니다. 그렇지 않으면 테이블 뷰가 다시 로드되고 사용자에게 알림이 표시되지 않습니다.


완벽한 해결책은 없다고 생각합니다.하위 분류 수업은 문제를 일으킬 수 있습니다.레이아웃 패스는 위에서 시작해서 아래로 가기 때문에 모든 레이아웃이 완료되면 알림을 받기가 쉽지 않습니다.그리고.layoutIfNeeded()성능 문제 등이 발생할 수 있습니다.

Swift 3에서 수행하는 방법은 다음과 같습니다.

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

    if indexPath.row == 0 {
        // perform your logic here, for the first row in the table
    }

    // ....
}

스위프트 3에서 하는 방법은 다음과 같습니다.

let threshold: CGFloat = 76.0 // threshold from bottom of tableView

internal func scrollViewDidScroll(_ scrollView: UIScrollView) {

    let contentOffset = scrollView.contentOffset.y
    let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    if  (!isLoadingMore) &&  (maximumOffset - contentOffset <= threshold) {
        self.loadVideosList()
    }
}

제가 할 일은 이렇습니다.

  1. 기본 클래스(rootVC BaseVc 등일 수 있음)에서,

    A. 프로토콜을 작성하여 "DidFinishReloading" 콜백을 보냅니다.

    @protocol ReloadComplition <NSObject>
    @required
    - (void)didEndReloading:(UITableView *)tableView;
    @end
    

    B. 테이블 뷰를 다시 로드하는 일반적인 방법을 작성합니다.

    -(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController;
    
  2. 기본 클래스 메서드 구현에서 reloadData를 호출한 다음 delegateMethod를 지연시킵니다.

    -(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController{
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [tableView reloadData];
            if(aViewController && [aViewController respondsToSelector:@selector(didEndReloading:)]){
                [aViewController performSelector:@selector(didEndReloading:) withObject:tableView afterDelay:0];
            }
        }];
    }
    
  3. 콜백이 필요한 모든 뷰 컨트롤러에서 다시 로드 완료 프로토콜을 확인합니다.

    -(void)didEndReloading:(UITableView *)tableView{
        //do your stuff.
    }
    

참조: https://discussions.apple.com/thread/2598339?start=0&tstart=0

앤드류의 코드를 복사하여 표에 한 줄만 있는 경우를 고려하여 확장하고 있습니다.지금까지 효과가 있어요!

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// detect when all visible cells have been loaded and displayed
// NOTE: iOS7 workaround used - see: http://stackoverflow.com/questions/4163579/how-to-detect-the-end-of-loading-of-uitableview?lq=1
NSArray *visibleRows = [tableView indexPathsForVisibleRows];
NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
BOOL isPreviousCallForPreviousCell = self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
BOOL isFinishedLoadingTableView = isLastCell && ([tableView numberOfRowsInSection:0] == 1 || isPreviousCallForPreviousCell);

self.previousDisplayedIndexPath = indexPath;

if (isFinishedLoadingTableView) {
    [self hideSpinner];
}
}

참고: 앤드류 코드의 한 섹션만 사용하고 있으니 참고하세요.

@flex 정답은 맞습니다.

그러나 테이블 뷰에 한 번에 둘 이상의 섹션이 표시되면 실패합니다.

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
   if([indexPath isEqual:((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject])]){
    //end of loading

 }
}

스위프트에서는 이런 것을 할 수 있습니다.테이블 끝에 도달할 때마다 다음 조건이 참이 됩니다. 보기

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row+1 == postArray.count {
            println("came to last row")
        }
}

섹션이 여러 개인 경우 마지막 섹션의 마지막 행을 가져오는 방법은 다음과 같습니다(Swift 3).

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if let visibleRows = tableView.indexPathsForVisibleRows, let lastRow = visibleRows.last?.row, let lastSection = visibleRows.map({$0.section}).last {
        if indexPath.row == lastRow && indexPath.section == lastSection {
            // Finished loading visible rows

        }
    }
}

우연히 이 솔루션에 부딪혔습니다.

tableView.tableFooterView = UIView()
tableViewHeight.constant = tableView.contentSize.height

viewDidLoad와 같은 내용 크기를 가져오기 전에 바닥글 보기를 설정해야 합니다.btw. footView를 설정하면 "사용하지 않는" 구분 기호를 삭제할 수 있습니다.

UITableView + 페이징을 활성화하고 스크롤ToRow(...)를 호출하여 해당 페이지에서 시작합니다.

지금까지 최고의 못생긴 해결책 :/

override func viewDidLoad() {
    super.viewDidLoad()
    
    <#UITableView#>.reloadData()
    <#IUTableView#>.alpha = .zero
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
        self?.<#IUTableView#>.scrollToRow(at: <#IndexPath#>, at: .none, animated: true)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
             self?.<#IUTableView#>.alpha = 1
        }
    }
}

표에 표시될 총 항목 수 또는 현재 표시되는 총 항목 수를 찾고 계십니까?어느 쪽이든..나는 'viewDidLoad' 메소드는 모든 데이터 소스 메소드가 호출된 후에 실행된다고 생각합니다.그러나 이것은 데이터의 첫 번째 로드에서만 작동합니다(단일 할당 보기 컨트롤러를 사용하는 경우).

답변이 된 것으로 알고 있습니다. 추천을 추가하는 것입니다.

다음 설명서에 따라

https://www.objc.io/issues/2-concurrency/thread-safe-class-design/

dispatch_async로 타이밍 문제를 해결하는 것은 좋지 않습니다.저는 우리가 플래그 같은 것을 추가해서 이것을 처리해야 한다고 제안합니다.

iOS 7.0x에서는 솔루션이 약간 다릅니다.여기 제가 생각해 낸 것이 있습니다.

    - (void)tableView:(UITableView *)tableView 
      willDisplayCell:(UITableViewCell *)cell 
    forRowAtIndexPath:(NSIndexPath *)indexPath
{
    BOOL isFinishedLoadingTableView = [self isFinishedLoadingTableView:tableView  
                                                             indexPath:indexPath];
    if (isFinishedLoadingTableView) {
        NSLog(@"end loading");
    }
}

- (BOOL)isFinishedLoadingTableView:(UITableView *)tableView 
                         indexPath:(NSIndexPath *)indexPath
{
    // The reason we cannot just look for the last row is because 
    // in iOS7.0x the last row is updated before
    // looping through all the visible rows in ascending order 
    // including the last row again. Strange but true.
    NSArray * visibleRows = [tableView indexPathsForVisibleRows];   // did verify sorted ascending via logging
    NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
    // For tableviews with multiple sections this will be more complicated.
    BOOL isPreviousCallForPreviousCell = 
             self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
    BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
    BOOL isFinishedLoadingTableView = isLastCell && isPreviousCallForPreviousCell;
    self.previousDisplayedIndexPath = indexPath;
    return isFinishedLoadingTableView;
}

목표 C

[self.tableView reloadData];
[self.tableView performBatchUpdates:^{}
                              completion:^(BOOL finished) {
                                  /// table-view finished reload
                              }];

스위프트

self.tableView?.reloadData()
self.tableView?.performBatchUpdates({ () -> Void in

                            }, completion: { (Bool finished) -> Void in
                                /// table-view finished reload
                            })

언급URL : https://stackoverflow.com/questions/4163579/how-to-detect-the-end-of-loading-of-uitableview

반응형