[ios] UITableView가 데이터 요청을 완료하면 알림을 받습니까?

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:항상 실행됩니다 .tableViewdataSource 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
            }
        }


    }
}

변경 확인시 약간의 수정이 필요할 수 있습니다. 이것은 나를 위해 일했습니다.

건배! 🙂