source

UITableView가 데이터 다시 로드를 완료했는지 어떻게 알 수 있습니까?

factcode 2023. 5. 24. 22:27
반응형

UITableView가 데이터 다시 로드를 완료했는지 어떻게 알 수 있습니까?

후.[self.tableView reloadData].

원래는.

 [self.tableView reloadData]
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];

[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

이라고 읽어서 'reloadData' 이후 .self.tableView,[self.tableView numberOfSections]그리고.[self.tableView numberOfRowsinSection모두 0입니다.

내가 사용하는 것이 이상합니다.

[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);

콘솔에서 섹션 = 1, 행 = -1;을 반환합니다.

NSLogs 행하우에서 정확히 할 때cellForRowAtIndexPath=1및 행 = (.) = 1 = 8 이다니션이다습니섹맞시표. (8행)

다시 로드는 다음 레이아웃 패스 중에 발생하며, 일반적으로 제어를 실행 루프로 되돌릴 때 발생합니다(예: 버튼 동작 또는 반환되는 모든 것 이후).

따라서 테이블 뷰가 다시 로드된 후에 무언가를 실행하는 한 가지 방법은 테이블 뷰가 레이아웃을 즉시 수행하도록 강제하는 것입니다.

[self.tableView reloadData];
[self.tableView layoutIfNeeded];
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

다른 방법은 다음을 사용하여 나중에 실행되도록 애프터레이아웃 코드를 예약하는 것입니다.dispatch_async:

[self.tableView reloadData];

dispatch_async(dispatch_get_main_queue(), ^{
     NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)];

    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});

갱신하다

추가 조사 결과, 테이블 뷰가 다음과 같이 전송합니다.tableView:numberOfSections:그리고.tableView:numberOfRowsInSection:에서 스로전데소이터에서 로.reloadData이 시행하는 tableView:heightForRowAtIndexPath: (행에 ) 를 반환하기 (으)로 합니다.reloadData.

그나테보전않지습니다되송기가러를 않습니다.tableView:cellForRowAtIndexPath:또는tableView:headerViewForSection제어를 실행 루프로 되돌릴 때 기본적으로 발생하는 레이아웃 단계까지.

나는 또한 작은 테스트 프로그램에서 당신의 질문의 코드가 내가 특별한 것을 하지 않고 테이블 뷰의 아래쪽으로 적절하게 스크롤된다는 것을 발견했습니다(예를 보내는 것과 같은.layoutIfNeeded 는사용을 사용합니다.dispatch_async).

스위프트:

extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        UIView.animate(withDuration: 0, animations: reloadData)
            { _ in completion() }
    } 
}

// ...somewhere later...

tableView.reloadData {
    print("done")
}

목표-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];

Xcode 8.2.1, iOS 10, Swift 3 기준으로

을▁of의 할 수 .tableView.reloadData()CAT 트랜잭션 블록을 쉽게 사용할 수 있습니다.

CATransaction.begin()
CATransaction.setCompletionBlock({
    print("reload completed")
    // Your completion code here
})
print("reloading")
tableView.reloadData()
CATransaction.commit()

위의 내용은 UICollectionView의 reloadData()와 UIPickerView의 reloadAllComponents()의 끝을 결정하는 데에도 사용됩니다.

dispatch_async(dispatch_get_main_queue())위의 방법이 작동하는 것을 보장하지 않습니다.시스템이 layoutSubviews 및 셀 렌더링을 완료하기 전에 완료 블록을 완료하기도 하고, 그 이후에 완료되지 않는 동작을 볼 수 있습니다.

여기 iOS 10에서 100% 작동하는 솔루션이 있습니다.UITableView 또는 UICollectionView를 사용자 지정 하위 클래스로 인스턴스화하는 기능이 필요합니다.다음은 UICollectionView 솔루션이지만 UITableView도 동일합니다.

사용자 지정 컬렉션 보기.h:

#import <UIKit/UIKit.h>

@interface CustomCollectionView: UICollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock;

@end

사용자 지정 컬렉션 보기.m:

#import "CustomCollectionView.h"

@interface CustomCollectionView ()

@property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);

@end

@implementation CustomCollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock
{
    self.reloadDataCompletionBlock = completionBlock;
    [self reloadData];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}

@end

사용 예:

[self.collectionView reloadDataWithCompletion:^{
    // reloadData is guaranteed to have completed
}];

이 답변의 Swift 버전은 여기를 참조하십시오.

저도 타일러 쉐퍼와 같은 문제가 있었습니다.

저는 스위프트에서 그의 솔루션을 구현했고 그것은 제 문제를 해결했습니다.

Swift 3.0:

final class UITableViewWithReloadCompletion: UITableView {
  private var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    reloadDataCompletionBlock?()
    reloadDataCompletionBlock = nil
  }


  func reloadDataWithCompletion(completion: @escaping () -> Void) {
    reloadDataCompletionBlock = completion
    self.reloadData()
  }
}

스위프트 2:

class UITableViewWithReloadCompletion: UITableView {

  var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    self.reloadDataCompletionBlock?()
    self.reloadDataCompletionBlock = nil
  }

  func reloadDataWithCompletion(completion:() -> Void) {
      reloadDataCompletionBlock = completion
      self.reloadData()
  }
}

사용 예:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}

그리고UICollectionView버전, 콜라월드의 답변을 기반으로 합니다.

테스트가 필요합니다.iOS 9.2 및 Xcode 9.2 베타 2에서 작동하며, collectionView를 인덱스로 스크롤합니다.

extension UICollectionView
{
    /// Calls reloadsData() on self, and ensures that the given closure is
    /// called after reloadData() has been completed.
    ///
    /// Discussion: reloadData() appears to be asynchronous. i.e. the
    /// reloading actually happens during the next layout pass. So, doing
    /// things like scrolling the collectionView immediately after a
    /// call to reloadData() can cause trouble.
    ///
    /// This method uses CATransaction to schedule the closure.

    func reloadDataThenPerform(_ closure: @escaping (() -> Void))
    {
        CATransaction.begin()
            CATransaction.setCompletionBlock(closure)
            self.reloadData()
        CATransaction.commit()
    }
}

용도:

myCollectionView.reloadDataThenPerform {
    myCollectionView.scrollToItem(at: indexPath,
            at: .centeredVertically,
            animated: true)
}

When [tableView reloadData]테이블 뒤의 내부 데이터 구조가 업데이트되었습니다.따라서 메소드가 완료되면 안전하게 맨 아래로 스크롤할 수 있습니다.저는 이것을 제 앱에서 확인했습니다.에 의한 널리 받아들여진 대답은 용어상으로도 혼란스러울 수 있지만, 그의 마지막 업데이트에서도 동일함을 인정합니다.

만약 당신이tableView아래로 스크롤되지 않습니다. 게시하지 않은 다른 코드에 문제가 있을 수 있습니다.스크롤이 완료된 후 데이터를 변경하고 있으며 다시 로드하거나 맨 아래로 스크롤하지 않는 것이 아닐까요?

합니다.reloadData샘플 앱에 다음 코드가 있는데 완벽하게 작동합니다.

// Change the data source

NSLog(@"Before reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView reloadData];

NSLog(@"After reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]-1
                                                          inSection:[self.tableView numberOfSections] - 1]
                      atScrollPosition:UITableViewScrollPositionBottom
                              animated:YES];

저는 이 속임수를 사용합니다. 이미 이 질문의 사본에 게시했습니다.

-(void)tableViewDidLoadRows:(UITableView *)tableView{
    // do something after loading, e.g. select a cell.
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // trick to detect when table view has finished loading.
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];
    [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];

    // specific to your controller
    return self.objects.count;
}

사실 이것이 제 문제를 해결했습니다.

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}

세부 사항

  • Xcode 버전 10.2.1(10E1001), Swift 5

해결책

import UIKit

// MARK: - UITableView reloading functions

protocol ReloadCompletable: class { func reloadData() }

extension ReloadCompletable {
    func run(transaction closure: (() -> Void)?, completion: (() -> Void)?) {
        guard let closure = closure else { return }
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        closure()
        CATransaction.commit()
    }

    func run(transaction closure: (() -> Void)?, completion: ((Self) -> Void)?) {
        run(transaction: closure) { [weak self] in
            guard let self = self else { return }
            completion?(self)
        }
    }

    func reloadData(completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadData() }, completion: closure)
    }
}

// MARK: - UITableView reloading functions

extension ReloadCompletable where Self: UITableView {
    func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadRows(at: indexPaths, with: animation) }, completion: closure)
    }

    func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections, with: animation) }, completion: closure)
    }
}

// MARK: - UICollectionView reloading functions

extension ReloadCompletable where Self: UICollectionView {

    func reloadSections(_ sections: IndexSet, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections) }, completion: closure)
    }

    func reloadItems(at indexPaths: [IndexPath], completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadItems(at: indexPaths) }, completion: closure)
    }
}

사용.

UI 테이블 보기

// Activate
extension UITableView: ReloadCompletable { }

// ......
let tableView = UICollectionView()

// reload data
tableView.reloadData { tableView in print(collectionView) }

// or
tableView.reloadRows(at: indexPathsToReload, with: rowAnimation) { tableView in print(tableView) }

// or
tableView.reloadSections(IndexSet(integer: 0), with: rowAnimation) { _tableView in print(tableView) }

UI 컬렉션 보기

// Activate
extension UICollectionView: ReloadCompletable { }

// ......
let collectionView = UICollectionView()

// reload data
collectionView.reloadData { collectionView in print(collectionView) }

// or
collectionView.reloadItems(at: indexPathsToReload) { collectionView in print(collectionView) }

// or
collectionView.reloadSections(IndexSet(integer: 0)) { collectionView in print(collectionView) }

전체샘플

여기에 솔루션 코드를 추가하는 것을 잊지 마십시오.

import UIKit

class ViewController: UIViewController {

    private weak var navigationBar: UINavigationBar?
    private weak var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationItem()
        setupTableView()
    }
}
// MARK: - Activate UITableView reloadData with completion functions

extension UITableView: ReloadCompletable { }

// MARK: - Setup(init) subviews

extension ViewController {

    private func setupTableView() {
        guard let navigationBar = navigationBar else { return }
        let tableView = UITableView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.dataSource = self
        self.tableView = tableView
    }

    private func setupNavigationItem() {
        let navigationBar = UINavigationBar()
        view.addSubview(navigationBar)
        self.navigationBar = navigationBar
        navigationBar.translatesAutoresizingMaskIntoConstraints = false
        navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        let navigationItem = UINavigationItem()
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "all", style: .plain, target: self, action: #selector(reloadAllCellsButtonTouchedUpInside(source:)))
        let buttons: [UIBarButtonItem] = [
                                            .init(title: "row", style: .plain, target: self,
                                                  action: #selector(reloadRowButtonTouchedUpInside(source:))),
                                            .init(title: "section", style: .plain, target: self,
                                                  action: #selector(reloadSectionButtonTouchedUpInside(source:)))
                                            ]
        navigationItem.leftBarButtonItems = buttons
        navigationBar.items = [navigationItem]
    }
}

// MARK: - Buttons actions

extension ViewController {

    @objc func reloadAllCellsButtonTouchedUpInside(source: UIBarButtonItem) {
        let elementsName = "Data"
        print("-- Reloading \(elementsName) started")
        tableView?.reloadData { taleView in
            print("-- Reloading \(elementsName) stopped \(taleView)")
        }
    }

    private var randomRowAnimation: UITableView.RowAnimation {
        return UITableView.RowAnimation(rawValue: (0...6).randomElement() ?? 0) ?? UITableView.RowAnimation.automatic
    }

    @objc func reloadRowButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Rows"
        print("-- Reloading \(elementsName) started")
        let indexPathToReload = tableView.indexPathsForVisibleRows?.randomElement() ?? IndexPath(row: 0, section: 0)
        tableView.reloadRows(at: [indexPathToReload], with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }

    @objc func reloadSectionButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Sections"
        print("-- Reloading \(elementsName) started")
        tableView.reloadSections(IndexSet(integer: 0), with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(Date())"
        return cell
    }
}

결과.

여기에 이미지 설명 입력

저는 Shawn의 솔루션을 변형해서 사용하게 되었습니다.

대리자가 있는 사용자 지정 UITableView 클래스를 만듭니다.

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

    override func layoutSubviews() {
        super.layoutSubviews()
        self.customDelegate?.CustomTableViewDidLayoutSubviews()
    }
}

그리고 내 코드에서는,

class SomeClass: UIViewController, CustomTableViewDelegate {

    @IBOutlet weak var myTableView: CustomTableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.myTableView.customDelegate = self
    }

    func CustomTableViewDidLayoutSubviews() {
        print("didlayoutsubviews")
        // Do other cool things here!!
    }
}

또한 Interface Builder에서 테이블 보기를 CustomTableView로 설정해야 합니다.

여기에 이미지 설명 입력

이쪽으로 오세요.그건 작동할 것이다.

[tblViewTerms performSelectorOnMainThread:@selector(dataLoadDoneWithLastTermIndex:) withObject:lastTermIndex waitUntilDone:YES];waitUntilDone:YES];

@interface UITableView (TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex;

@end

@implementation UITableView(TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex
{
    NSLog(@"dataLoadDone");

NSIndexPath* indexPath = [NSIndexPath indexPathForRow: [lastTermIndex integerValue] inSection: 0];

[self selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

}
@end

테이블이 완전히 로딩되면 실행하겠습니다.

또 다른 솔루션은 UITableView를 하위 분류할 수 있습니다.

접근법을 하자면, 이 가전마송는 '료으로볼수있는막셀지이다'로' 에 기초합니다.cellForRow.

// Will be set when reload is called
var lastIndexPathToDisplay: IndexPath?

typealias ReloadCompletion = ()->Void

var reloadCompletion: ReloadCompletion?

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

    // Setup cell

    if indexPath == self.lastIndexPathToDisplay {

        self.lastIndexPathToDisplay = nil

        self.reloadCompletion?()
        self.reloadCompletion = nil
    }

    // Return cell
...

func reloadData(completion: @escaping ReloadCompletion) {

    self.reloadCompletion = completion

    self.mainTable.reloadData()

    self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
}

한 가지 가능한 문제는 다음과 같습니다.reloadData()이전에 완료되었습니다.lastIndexPathToDisplay볼 수' 셀이 설된경우마, '막으로볼수있는셀' 앞에 됩니다.lastIndexPathToDisplay설정되었으며 완료가 호출되지 않습니다(및 '오프라인' 상태).

self.mainTable.reloadData()

// cellForRowAt could be finished here, before setting `lastIndexPathToDisplay`

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

만약 우리가 반대로 한다면, 우리는 완료가 전에 스크롤에 의해 트리거될 수 있습니다.reloadData().

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

// cellForRowAt could trigger the completion by scrolling here since we arm 'lastIndexPathToDisplay' before 'reloadData()'

self.mainTable.reloadData()

를 다시 로드할 때 데이터를 다시 로드하는 경우viewDidLoad당신은 당신의 코드를 에 넣을 수 있습니다.viewDidLayoutSubviews방법.하지만 당신은 조심해야 합니다.viewDidLayoutSubviews여러 번 호출될 수 있습니다.

사용해 보십시오.

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

테이블 보기 색상은 다음 작업 후에만 검은색에서 녹색으로 변경됩니다.reloadData()기능이 완료됩니다.

재사용 가능한 CAT 트랜잭션 확장 만들기:

public extension CATransaction {
    static func perform(method: () -> Void, completion: @escaping () -> Void) {
        begin()
        setCompletionBlock {
            completion()
        }
        method()
        commit()
    }
}

이제 CAT 트랜잭션의 확장 메서드를 사용하는 UITableView 확장을 만드는 중입니다.

public extension UITableView {
    func reloadData(completion: @escaping (() -> Void)) {
       CATransaction.perform(method: {
           reloadData()
       }, completion: completion)
    }
}

용도:

tableView.reloadData(completion: {
    //Do the stuff
})

데이터를 다시 로드한 후 작업에 사용할 수 있습니다.

[UIView animateWithDuration:0 animations:^{
    [self.contentTableView reloadData];
} completion:^(BOOL finished) {
    _isUnderwritingUpdate = NO;
}];

지연 설정 시도:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];

언급URL : https://stackoverflow.com/questions/16071503/how-can-i-tell-when-uitableview-has-completed-reloaddata

반응형