Objective-C: asynchronicznie wypełniać UITableView-jak to zrobić ?

Nie mogę znaleźć żadnych informacji na ten temat, więc pomyślałem, że zapytam społeczność.

Zasadniczo mam UITableView i chcę pokazać wskaźnik aktywności podczas ładowania danych z mojego serwera.

Oto przykładowy kod tego, co próbuję zrobić(używam 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];

Dummy prośba do google.com nic nie robi-po prostu tworzy opóźnienie i w odpowiedzi mam nadzieję ponownie wypełnić tabelę z jakąś odpowiedzią JSON z mojej własnej strona internetowa.

Ale kiedy próbuję zapełnić stół kolorami, nic się nie dzieje! Mam tylko pusty stolik... Jeśli odkomentuję powyższą linię, działa dobrze, tylko na odpowiedziach http rzeczy nie działają dla mnie. Wszelkie sugestie bardzo mile widziane.

Edit:

Zrobiłem [self.tableView reloadData]; i teraz to działa...

Author: Lorenzo B, 2011-09-21

3 answers

  1. przestań używać ASIHTTPRequest. NSURLConnection nie jest trudny w użyciu i skutkuje lepszym, bardziej wydajnym kodem.
  2. odpowiedź JSON powinna być przekazywana do struktury danych, a nie do interfejsu użytkownika. Polecam Core Data.
  3. struktura danych powinna zasilać twoje UITableView. Ponownie polecam Core Data.

Sugerowałbym sprawdzenie, jak działa MVC, zwarcie w projekcie i to jest główny problem.

SPOILER

Oto bardziej szczegółowe jak to zrobić. Najpierw chcesz, aby pobieranie danych było asynchroniczne. Najprostszym i najbardziej wielokrotnego użytku sposobem na to jest zbudowanie prostej podklasy 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

Ta podklasa jest najbardziej podstawowym sposobem na pobranie czegoś z adresu URL. Zbuduj go za pomocą NSURLRequest i delegata. Będzie odwoływać się do sukcesu lub porażki. Realizacja jest tylko nieco dłuższa.

#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

Teraz ta klasa jest bardzo wielokrotnego użytku. Istnieją inne metody delegowania dla NSURLConnection, które możesz dodać w zależności od potrzeb. NSURLConnection może obsługiwać przekierowania, uwierzytelnianie itp. I zdecydowanie sugeruję przyjrzeć się jego dokumentacji.

Stąd możesz albo spin off CIMGFSimpleDownloadOperation z UITableViewController lub z innej części aplikacji. Dla tej demonstracji zrobimy to w UITableViewController. W zależności od potrzeb aplikacji można rozpocząć pobieranie danych wszędzie tam, gdzie ma to sens. W tym przykładzie rozpoczniemy go, gdy pojawi się widok.

- (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;
}

Teraz, gdy Widok pojawi się jako asynchroniczne połączenie przejdzie i pobierze zawartość adresu URL. W tym kodzie, który przejdzie lub nie. Porażka pierwsza:

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

Na sukces musimy przeanalizować dane, które wróciły.

- (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];
    });
  }
}
I gotowe. Kilka kolejnych linii kodu do napisania, ale zastępuje 13K + linii kodu, który jest importowany za pomocą ASI, co skutkuje mniejszą, szczuplejszą, szybszą aplikacją. A co ważniejsze, jest to aplikacja, którą rozumiesz w każdym wierszu kodu.
 64
Author: Marcus S. Zarra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-23 10:58:11

To jest problem

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];    
    }];

Tabela przeładowania musi być wykonana w głównym wątku.

 12
Author: NWCoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-20 22:25:37

Próbowałem rozwiązania Nwcodera i nie zadziałało, ponieważ wywołało złą metodę. Tego użyłem.
[self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];

 0
Author: SirrDon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-07 06:47:43