UITableView가 ReloadData를 완료 한 시점을 알리는 방법?
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);
콘솔에서 Sections = 1, Row = -1을 반환합니다.
정확히 동일한 NSLog를 수행하면 cellForRowAtIndexPath
Sections = 1 및 Row = 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: ()->()) {
UIView.animateWithDuration(0, animations: { self.reloadData() })
{ _ in completion() }
}
}
...somewhere later...
tableView.reloadData {
println("done")
}
목표 -C :
[UIView animateWithDuration:0 animations:^{
[myTableView reloadData];
} completion:^(BOOL finished) {
//Do something after that...
}];
Xcode 8.2.1, iOS 10 및 swift 3부터
tableView.reloadData()
CATransaction 블록을 사용하여 쉽게 끝을 결정할 수 있습니다 .
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와 동일합니다.
CustomCollectionView.h :
#import <UIKit/UIKit.h>
@interface CustomCollectionView: UICollectionView
- (void)reloadDataWithCompletion:(void (^)(void))completionBlock;
@end
CustomCollectionView.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에서 구현 했으며 내 문제를 해결했습니다.
스위프트 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
}
사람들은 여전히이 질문과 답변을 읽고있는 것 같습니다. 그것의 B / C, 나는 실제로 이것과 관련이없는 Synchronous 라는 단어를 제거하기 위해 대답을 편집하고 있습니다.
When [tableView reloadData]
반환하면 tableView 뒤의 내부 데이터 구조가 업데이트되었습니다. 따라서 방법이 완료되면 하단으로 안전하게 스크롤 할 수 있습니다. 내 앱에서 이것을 확인했습니다. @ rob-mayoff가 널리 받아 들인 대답은 용어를 혼동하지만 마지막 업데이트에서도 동일하게 인정합니다.
당신이 경우 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];
그리고 UICollectionView
kolaworld의 답변을 기반으로 한 버전 :
https://stackoverflow.com/a/43162226/1452758
테스트가 필요합니다. 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)
}
이 트릭을 사용합니다.이 질문의 복제본에 이미 게시했습니다.
-(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
}}
이 방법으로 시도해보십시오.
[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를 서브 클래스 화 할 수 있다는 것입니다
나는 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!!
}
}
또한 인터페이스 빌더에서 테이블보기를 CustomTableView로 설정했는지 확인하십시오.
스위프트 3.0 + 우리에 대한 확장을 만들 수 있습니다 UITableView
A를 escaped Closure
다음과 같은 :
extension UITableView {
func reloadData(completion: @escaping () -> ()) {
UIView.animate(withDuration: 0, animations: { self.reloadData()})
{_ in completion() }
}
}
그리고 원하는 곳에서 아래처럼 사용하십시오 :
Your_Table_View.reloadData {
print("reload done")
}
이것이 누군가에게 도움이되기를 바랍니다. 건배!
완료의 아이디어가 전송 될 '마지막 표시'셀이라는 아이디어를 기반으로 다른 접근 방식을 제공하기 위해 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
반대로 바꾸면 before로 스크롤하여 완료가 트리거 될 수 reloadData()
있습니다.
self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
// cellForRowAt could trigger the completion by scrolling here since we arm 'lastIndexPathToDisplay' before 'reloadData()'
self.mainTable.reloadData()
이 시도:
tableView.backgroundColor = .black
tableView.reloadData ()
DispatchQueue.main.async (execute : {
tableView.backgroundColor = .green
})
// reloadData () 함수가 완료된 후에 만 tableView 색상이 검은 색에서 녹색으로 변경됩니다.
세부
- Xcode 버전 10.2.1 (10E1001), 스위프트 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)
}
}
용법
UITableView
// 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) }
UICollectionView
// 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
}
}
결과
데이터를 다시로드 한 후 무언가를 위해 사용할 수 있습니다.
[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-to-tell-when-uitableview-has-completed-reloaddata
'Programming' 카테고리의 다른 글
Intellij-최신 Java 8 클래스를 사용할 수 없음-오류 :“@since 1.6 이상으로 문서화 된 API 사용 ..” (0) | 2020.06.01 |
---|---|
SQL Server 로그인이 이미 있는지 확인 (0) | 2020.06.01 |
underscore.js를 사용하여 asc 및 desc 정렬을 수행하려면 어떻게해야합니까? (0) | 2020.06.01 |
MKMapView라는 클래스를 인스턴스화 할 수 없습니다. (0) | 2020.06.01 |
UITableViewCell 사이에 간격을 추가하는 방법 (0) | 2020.06.01 |