가 UITableView
데이터 소스에서 데이터 요청을 완료 한시기를 확인할 수있는 방법이 있습니까?
관련된 뷰 컨트롤러 ( ) 의 viewDidLoad
/ viewWillAppear
/ viewDidAppear
메소드는 UITableViewController
모두 너무 일찍 실행되므로 여기서는 사용되지 않습니다. 이들 중 어느 것도 (완전히 이해할 수있는) 데이터 소스에 대한 쿼리가 당분간 (예 :보기가 스크롤 될 때까지) 완료되었음을 보장하지 않습니다.
내가 찾은 한 가지 해결 방법은를 호출 reloadData
하는 것 입니다.을 호출 viewDidAppear
하면 reloadData
반환 될 때 테이블보기 가 당분간 필요한만큼 데이터 소스 쿼리를 완료 할 수 있기 때문입니다.
그러나 데이터 소스 reloadData
가 처음로드 될 때 데이터 소스에 동일한 정보를 두 번 (자동으로 한 번, 호출로 인해 한 번) 요청하는 것으로 가정하므로 다소 불쾌 해 보입니다 .
이 작업을 수행하려는 이유는 UITableView
– 의 스크롤 위치를 유지하고 싶지만 가장 가까운 행뿐만 아니라 픽셀 수준까지 바로 아래로 유지하고 싶기 때문 입니다.
스크롤 위치를 복원 할 때 (를 사용하여 scrollRectToVisible:animated:
) 이미 충분한 데이터가있는 테이블 뷰가 필요합니다. 그렇지 않으면 scrollRectToVisible:animated:
메서드 호출이 아무 작업도 수행하지 않습니다 ( viewDidLoad
, viewWillAppear
또는 에서 자체적으로 호출하면 발생합니다 viewDidAppear
).
답변
이 답변은 답변이 작성된 이후 UITableView 구현에 대한 일부 변경 사항으로 인해 더 이상 작동하지 않는 것 같습니다. 이 주석을 참조하십시오 : UITableView가 데이터 요청을 마쳤을 때 알림을 받습니까?
나는 며칠이 문제와 함께 연주하고 그 하위 클래스 생각했습니다 UITableView
의는 reloadData
가장 좋은 방법입니다 :
- (void)reloadData {
NSLog(@"BEGIN reloadData");
[super reloadData];
NSLog(@"END reloadData");
}
reloadData
테이블이 데이터를 다시로드하기 전에 끝나지 않습니다. 따라서 두 번째 NSLog
가 실행되면 테이블 뷰가 실제로 데이터 요청을 완료합니다.
UITableView
전과 후에 대리자에게 메서드를 보내도록 서브 클래 싱 했습니다 reloadData
. 매력처럼 작동합니다.
답변
나는 내 앱에서 동일한 시나리오를 가지고 있었고 여기에 언급 된 다른 답변이 iOS7 이상에서 작동하지 않기 때문에 여러분에게 내 답변을 게시 할 것이라고 생각했습니다.
마지막으로 이것이 나를 위해 일한 유일한 것입니다.
[yourTableview reloadData];
dispatch_async(dispatch_get_main_queue(),^{
NSIndexPath *path = [NSIndexPath indexPathForRow:yourRow inSection:yourSection];
//Basically maintain your logic to get the indexpath
[yourTableview scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
});
신속한 업데이트 :
yourTableview.reloadData()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let path : NSIndexPath = NSIndexPath(forRow: myRowValue, inSection: mySectionValue)
//Basically maintain your logic to get the indexpath
yourTableview.scrollToRowAtIndexPath(path, atScrollPosition: UITableViewScrollPosition.Top, animated: true)
})
그래서 이것이 어떻게 작동하는지.
기본적으로 재로드를 수행하면 주 스레드가 바쁘기 때문에 디스패치 비동기 스레드를 수행 할 때 블록은 주 스레드가 완료 될 때까지 대기합니다. 따라서 tableview가 완전히로드되면 메인 스레드가 완료되고 메서드 블록을 전달합니다.
iOS7 및 iOS8에서 테스트되었으며 훌륭하게 작동합니다.)
iOS9 업데이트 : iOS9에서도 잘 작동합니다. POC로 github에 샘플 프로젝트를 만들었습니다.
https://github.com/ipraba/TableReloadingNotifier
여기에 테스트 스크린 샷을 첨부하고 있습니다.
테스트 환경 : Xcode7의 iOS9 iPhone6 시뮬레이터
답변
편집 :이 답변은 실제로 해결책이 아닙니다. 재로드가 매우 빠르게 발생할 수 있기 때문에 처음에는 작동하는 것처럼 보이지만 실제로는 데이터가 완전히 다시로드 된 후에 완료 블록이 반드시 호출되는 것은 아닙니다. reloadData가 차단되지 않기 때문입니다. 더 나은 솔루션을 찾아야 할 것입니다.
@Eric MORAND의 답변을 확장하려면 완료 블록을 넣으십시오. 누가 블록을 좋아하지 않습니까?
@interface DUTableView : UITableView
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock;
@end
과…
#import "DUTableView.h"
@implementation DUTableView
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock {
[super reloadData];
if(completionBlock) {
completionBlock();
}
}
@end
용법:
[self.tableView reloadDataWithCompletion:^{
//do your stuff here
}];
답변
reloadData는 보이는 셀에 대한 데이터 만 요청합니다. 테이블의 특정 부분이로드 될 때 알림을 받으려면 tableView: willDisplayCell:
메소드 를 후크하십시오 .
- (void) reloadDisplayData
{
isLoading = YES;
NSLog(@"Reload display with last index %d", lastIndex);
[_tableView reloadData];
if(lastIndex <= 0){
isLoading = YES;
//Notify completed
}
- (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row >= lastIndex){
isLoading = NO;
//Notify completed
}
답변
그것이 제 해결책입니다. 100 % 작동하며 많은 프로젝트에서 사용됩니다. 간단한 UITableView 하위 클래스입니다.
@protocol MyTableViewDelegate<NSObject, UITableViewDelegate>
@optional
- (void)tableViewWillReloadData:(UITableView *)tableView;
- (void)tableViewDidReloadData:(UITableView *)tableView;
@end
@interface MyTableView : UITableView {
struct {
unsigned int delegateWillReloadData:1;
unsigned int delegateDidReloadData:1;
unsigned int reloading:1;
} _flags;
}
@end
@implementation MyTableView
- (id<MyTableViewDelegate>)delegate {
return (id<MyTableViewDelegate>)[super delegate];
}
- (void)setDelegate:(id<MyTableViewDelegate>)delegate {
[super setDelegate:delegate];
_flags.delegateWillReloadData = [delegate respondsToSelector:@selector(tableViewWillReloadData:)];
_flags.delegateDidReloadData = [delegate respondsToSelector:@selector(tableViewDidReloadData:)];
}
- (void)reloadData {
[super reloadData];
if (_flags.reloading == NO) {
_flags.reloading = YES;
if (_flags.delegateWillReloadData) {
[(id<MyTableViewDelegate>)self.delegate tableViewWillReloadData:self];
}
[self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];
}
}
- (void)finishReload {
_flags.reloading = NO;
if (_flags.delegateDidReloadData) {
[(id<MyTableViewDelegate>)self.delegate tableViewDidReloadData:self];
}
}
@end
한 가지 예외를 제외 하면 Josh Brown의 솔루션 과 유사합니다 . performSelector 메소드에는 지연이 필요하지 않습니다. 아무리 오래 reloadData
걸리 더라도 . 요청이 끝나면 tableViewDidLoadData:
항상 실행됩니다 .tableView
dataSource
cellForRowAtIndexPath
서브 클래스 UITableView
를 원하지 않더라도 간단히 호출 할 수 있으며 [performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f]
테이블이 다시로드 된 직후 선택기가 호출됩니다. 그러나 selector는 다음을 호출 할 때마다 한 번만 호출되도록해야합니다 reloadData
.
[self.tableView reloadData];
[self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];
즐겨. 🙂
답변
이것은 약간 다른 질문에 대한 답입니다. 언제 UITableView
에게 전화 를 걸 었는지 알아야했습니다 cellForRowAtIndexPath()
. 나는 layoutSubviews()
(@Eric MORAND에게 감사드립니다) 서브 클래 싱 하고 델리게이트 콜백을 추가했습니다.
SDTableView.h :
@protocol SDTableViewDelegate <NSObject, UITableViewDelegate>
@required
- (void)willReloadData;
- (void)didReloadData;
- (void)willLayoutSubviews;
- (void)didLayoutSubviews;
@end
@interface SDTableView : UITableView
@property(nonatomic,assign) id <SDTableViewDelegate> delegate;
@end;
SDTableView.m :
#import "SDTableView.h"
@implementation SDTableView
@dynamic delegate;
- (void) reloadData {
[self.delegate willReloadData];
[super reloadData];
[self.delegate didReloadData];
}
- (void) layoutSubviews {
[self.delegate willLayoutSubviews];
[super layoutSubviews];
[self.delegate didLayoutSubviews];
}
@end
용법:
MyTableViewController.h :
#import "SDTableView.h"
@interface MyTableViewController : UITableViewController <SDTableViewDelegate>
@property (nonatomic) BOOL reloadInProgress;
MyTableViewController.m :
#import "MyTableViewController.h"
@implementation MyTableViewController
@synthesize reloadInProgress;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if ( ! reloadInProgress) {
NSLog(@"---- numberOfSectionsInTableView(): reloadInProgress=TRUE");
reloadInProgress = TRUE;
}
return 1;
}
- (void)willReloadData {}
- (void)didReloadData {}
- (void)willLayoutSubviews {}
- (void)didLayoutSubviews {
if (reloadInProgress) {
NSLog(@"---- layoutSubviewsEnd(): reloadInProgress=FALSE");
reloadInProgress = FALSE;
}
}
참고 :UITableView
이것은 이미 위임 속성을 가리키는
하위 클래스이므로 MyTableViewController
다른 속성 을 추가 할 필요가 없습니다. “@dynamic delegate”는 컴파일러에게이 속성을 사용하도록 지시합니다. (다음은 이것을 설명하는 링크입니다 : http://farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/ )
새 클래스 를 사용하려면 의 UITableView
속성을 MyTableViewController
변경해야합니다 SDTableView
. 이것은 Interface Builder Identity Inspector에서 수행됩니다. UITableView
내부를 선택 UITableViewController
하고 “Custom Class”를로 설정합니다 SDTableView
.
답변
나는 변화에 대한 알림을받을 비슷한 것을 발견했다 contentSize
의를 TableView
. contentSize도 데이터로드에 따라 변경되기 때문에 여기서도 작동해야한다고 생각합니다.
이 시도:
에서 viewDidLoad
쓰기,
[self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:NULL];
이 메소드를 viewController에 추가하십시오.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentSize"]) {
DLog(@"change = %@", change.description)
NSValue *new = [change valueForKey:@"new"];
NSValue *old = [change valueForKey:@"old"];
if (new && old) {
if (![old isEqualToValue:new]) {
// do your stuff
}
}
}
}
변경 확인시 약간의 수정이 필요할 수 있습니다. 이것은 나를 위해 일했습니다.
건배! 🙂