Objective-C: асинхронно заполнить UITableView - как это сделать? - PullRequest
18 голосов
/ 21 сентября 2011

Похоже, я не могу найти информацию по этому вопросу, поэтому я решил спросить сообщество.

По сути, у меня есть UITableView, и я хочу показать индикатор активности во время загрузки данных с моего сервера.

Вот пример кода того, что я пытаюсь сделать (я использую ASIHttpRequest).

    //self.listData = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", @"Indigo", @"Violet", nil];   //this works
    NSString *urlStr=[[NSString alloc] initWithFormat:@"http://www.google.com"];   //some slow request
    NSURL *url=[NSURL URLWithString:urlStr];

    __block ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url];
    [request setDelegate:self];
    [request setCompletionBlock:^{
        self.listData = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", @"Indigo", @"Violet", nil];   //this doesn't work...
        [table reloadData];

    }];
    [request setFailedBlock:^{

    }];
    [request startAsynchronous];

Пустой запрос к google.com ничего не делает - он просто создает задержку, и в ответ я надеюсь заполнить таблицу некоторым JSON-ответом с моего собственного сайта.

Но когда я пытаюсь заполнить таблицу цветами, ничего не происходит! Я просто получаю пустую таблицу ... Если я раскомментирую вышеприведенную строку, она работает нормально, просто на http-ответах у меня ничего не получается.

Любые предложения с благодарностью.

Edit:

Я сделал [self.tableView reloadData]; и теперь это работает ...

Ответы [ 3 ]

64 голосов
/ 21 сентября 2011
  1. Прекратить использование ASIHTTPRequest. NSURLConnection не сложен в использовании и даст лучший, более производительный код.
  2. Ваш ответ JSON должен быть введен в структуру данных, а не в пользовательский интерфейс. Я рекомендую Core Data.
  3. Структура данных должна кормить ваш UITableView. Опять же, я рекомендую Core Data.

Я бы посоветовал проверить, как работает MVC, вы замыкаете схему, и это основная проблема.

СПОЙЛЕР

Вот более подробно как. Сначала вы хотите, чтобы получение данных было асинхронным. Самый простой и удобный способ сделать это - создать простой подкласс NSOperation.

@class CIMGFSimpleDownloadOperation;

@protocol CIMGFSimpleDownloadDelegate <NSObject>

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;

@end

@interface CIMGFSimpleDownloadOperation : NSOperation

@property (nonatomic, assign) NSInteger statusCode;

- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate;

@end

Этот подкласс является наиболее простым способом загрузки чего-либо из URL. Создайте его с NSURLRequest и делегатом. Он перезвонит в случае успеха или неудачи. Реализация только немного дольше.

#import "CIMGFSimpleDownloadOperation.h"

@interface CIMGFSimpleDownloadOperation()

@property (nonatomic, retain) NSURLRequest *request;
@property (nonatomic, retain) NSMutableData *data;
@property (nonatomic, assign) id<CIMGFSimpleDownloadDelegate> delegate;

@end

@implementation CIMGFSimpleDownloadOperation

- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate
{
  if (!(self = [super init])) return nil;

  [self setDelegate:delegate];
  [self setRequest:request];

  return self;
}

- (void)dealloc
{
  [self setDelegate:nil];
  [self setRequest:nil];
  [self setData:nil];

  [super dealloc];
}

- (void)main
{
  [NSURLConnection connectionWithRequest:[self request] delegate:self];
  CFRunLoopRun();
}

- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)resp
{
  [self setStatusCode:[resp statusCode]];
  [self setData:[NSMutableData data]];
}

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)newData
{
  [[self data] appendData:newData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
  [[self delegate] operation:self didCompleteWithData:[self data]];
  CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
  [[self delegate] operation:self didFailWithError:error];
  CFRunLoopStop(CFRunLoopGetCurrent());
}

@synthesize delegate;
@synthesize request;
@synthesize data;
@synthesize statusCode;

@end

Теперь этот класс ОЧЕНЬ пригоден для повторного использования. Существуют и другие методы делегирования для NSURLConnection, которые вы можете добавить в зависимости от ваших потребностей. NSURLConnection может обрабатывать перенаправления, аутентификацию и т. Д. Я настоятельно советую вам ознакомиться с его документацией.

Отсюда вы можете либо выделить CIMGFSimpleDownloadOperation из вашего UITableViewController, либо из другой части вашего приложения. Для этой демонстрации мы сделаем это в UITableViewController. В зависимости от потребностей вашего приложения вы можете начать загрузку данных там, где это имеет смысл. Для этого примера мы запустим его, когда появится представление.

- (void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];

  NSURLRequest *request = ...;
  CIMGFSimpleDownloadOperation *op = [[CIMGFSimpleDownloadOperation alloc] initWithURLRequest:request andDelegate:self];
  [[NSOperationQueue mainQueue] addOperation:op];
  [self setDownloadOperation:op]; //Hold onto a reference in case we want to cancel it
  [op release], op = nil;
}

Теперь, когда появится представление, произойдет асинхронный вызов и загрузится содержимое URL. В этом коде это либо пройдет, либо провалится. Сначала сбой:

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;
{
  [self setDownloadOperation:nil];
  NSLog(@"Failure to download: %@\n%@", [error localizedDescription], [error userInfo]);
}

В случае успеха нам нужно проанализировать возвращенные данные.

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
{
  [self setDownloadOperation:nil];
  NSLog(@"Download complete");

  //1. Massage the data into whatever we want, Core Data, an array, whatever
  //2. Update the UITableViewDataSource with the new data

  //Note: We MIGHT be on a background thread here.
  if ([NSThread isMainThread]) {
    [[self tableView] reloadData];
  } else {
    dispatch_sync(dispatch_get_main_queue(), ^{
      [[self tableView] reloadData];
    });
  }
}

И готово. Вам нужно написать еще несколько строк кода, но он заменяет 13 000+ строк кода, импортируемых с ASI, что приводит к меньшему, более компактному и быстрому приложению. И что более важно, это приложение, которое вы понимаете каждая строка кода .

12 голосов
/ 21 сентября 2011

Это проблема

request setCompletionBlock:^{
        self.listData = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", @"Indigo", @"Violet", nil];   //this doesn't work...
        [table performSelectorOnMainThread:@selector(reloadTable) withObject:nil waitUntilDone:NO];    
    }];

Таблица перезагрузки должна быть выполнена в главном потоке.

0 голосов
/ 07 марта 2013

Я попробовал решение NWCoder, и оно не сработало, потому что он вызывал неправильный метод. Это то, что я использовал.
[self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...