[self.tableView reloadData]
の実行が完了した後、UITableViewの一番下までスクロールしようとしています。
私はもともと持っていた
[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を返します。
cellForRowAtIndexPath
でまったく同じNSLogを実行すると、セクション= 1および行= 8になります。 (8が正しい)
再ロードは、次のレイアウトパス中に発生します。これは通常、制御を実行ループに戻すときに発生します(たとえば、ボタンアクションなどが戻った後)。
そのため、Table Viewのリロード後に何かを実行する1つの方法は、Table Viewにただちにレイアウトを実行させることです:
[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
name__から戻ることがわかりました。デリゲートがtableView:heightForRowAtIndexPath:
を実装している場合、Table ViewはreloadData
name__から戻る前に(行ごとに)それも送信します。
ただし、テーブルビューは、レイアウトフェーズまでtableView:cellForRowAtIndexPath:
またはtableView:headerViewForSection
を送信しません。レイアウトフェーズは、実行ループに制御を戻すときにデフォルトで発生します。
また、小さなテストプログラムでは、質問のコードがテーブルビューの下部に適切にスクロールし、without私は特別なこと( layoutIfNeeded
name__を送信するか、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以降
CATransactionブロックを使用すると、tableView.reloadData()
の終わりを簡単に判断できます。
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;
[super reloadData];
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.reloadDataCompletionBlock) {
self.reloadDataCompletionBlock();
self.reloadDataCompletionBlock = nil;
}
}
@end
使用例:
[self.collectionView reloadDataWithCompletion:^{
// reloadData is guaranteed to have completed
}];
この回答のSwiftバージョンについては here をご覧ください
タイラー・シェファーと同じ問題を抱えていました。
his solution を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
super.reloadData()
}
}
スイフト2:
class UITableViewWithReloadCompletion: UITableView {
var reloadDataCompletionBlock: (() -> Void)?
override func layoutSubviews() {
super.layoutSubviews()
self.reloadDataCompletionBlock?()
self.reloadDataCompletionBlock = nil
}
func reloadDataWithCompletion(completion:() -> Void) {
reloadDataCompletionBlock = completion
super.reloadData()
}
}
使用例:
tableView.reloadDataWithCompletion() {
// reloadData is guaranteed to have completed
}
人々はまだこの質問と答えを読んでいるようです。 B/c、私は答えを編集してWordを削除しています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];
そして、kolaworldの答えに基づいたUICollectionView
バージョン:
https://stackoverflow.com/a/43162226/1452758
テストが必要です。これまでのところ、iOS 9.2、Xcode 9.2 beta 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をサブクラス化することです
ショーンのソリューションのバリエーションを使用することになりました。
デリゲートを使用してカスタム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に設定してください。
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
}
}
完了が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
}
考えられる問題の1つは、lastIndexPathToDisplay
が設定される前にreloadData()
が終了した場合、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()
Swift 3.0 +では、以下のようにescaped Closure
を使用してUITableView
の拡張機能を作成できます。
extension UITableView {
func reloadData(completion: @escaping () -> ()) {
UIView.animate(withDuration: 0, animations: { self.reloadData()})
{_ in completion() }
}
}
そして、以下のように使用してください:
Your_Table_View.reloadData {
print("reload done")
}
これが誰かに役立つことを願っています。乾杯!
これを試して:
tableView.backgroundColor = .black
tableView.reloadData()
DispatchQueue.main.async(execute:{
tableView.backgroundColor = .green
})
// reloadData()関数が完了した後にのみ、tableViewの色が黒から緑に変わります。