I've got a UITableView that loads an image asynchronously and places it in the UITableViewCell once it's loaded (I'm using almost the exact same code as in the "LazyTableImages" tutorial). This works fine for all images when I scroll the table, but it's not loading the images that are first in the view.
The code is definitely working fine as the class that actually sends the NSURLConnection request is being called correctly (I added an NSLog and it reached the console). The NSURLConnection is just not calling the delegate methods (didReceiveData, connectionDidFinishLoading, etc).
Here's my code:
HomeController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
NSArray *feed = [feeds objectAtIndex: indexPath.row];
/**
* Name of person
*/
[...]
/**
* Feed entry
*/
[...]
/**
* Misc work
*/
[...]
}
FeedRecord *feedRecord = [self.entries objectAtIndex:indexPath.row];
if( !feedRecord.image ) {
if (self.table.dragging == NO && self.table.decelerating == NO)
{
[self startIconDownload:feedRecord forIndexPath:indexPath];
}
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
return cell;
}
- (void)startIconDownload:(FeedRecord *)feedRecord forIndexPath:(NSIndexPath *)indexPath
{
IconDownloader *iconDownloader = [imageDownloadsInProgress objectForKey:indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[IconDownloader alloc] init];
iconDownloader.feedRecord = feedRecord;
iconDownloader.indexPathInTableView = indexPath;
iconDownloader.delegate = self;
[imageDownloadsInProgress setObject:iconDownloader forKey:indexPath];
[iconDownloader startDownload];
[iconDownloader release];
}
}
IconDownload.m
#import "IconDownloader.h"
#import "FeedRecord.h"
#define kAppIconHeight 48
#implementation IconDownloader
#synthesize feedRecord;
#synthesize indexPathInTableView;
#synthesize delegate;
#synthesize activeDownload;
#synthesize imageConnection;
#pragma mark
- (void)dealloc
{
[feedRecord release];
[indexPathInTableView release];
[activeDownload release];
[imageConnection cancel];
[imageConnection release];
[super dealloc];
}
- (void)startDownload
{
NSLog(#"%# %#",#"Started downloading", feedRecord.profilePicture); // this shows in log
self.activeDownload = [NSMutableData data];
// alloc+init and start an NSURLConnection; release on completion/failure
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:feedRecord.profilePicture]] delegate:self];
self.imageConnection = conn;
NSLog(#"%#",conn); // this shows in log
[conn release];
}
- (void)cancelDownload
{
[self.imageConnection cancel];
self.imageConnection = nil;
self.activeDownload = nil;
}
#pragma mark -
#pragma mark Download support (NSURLConnectionDelegate)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"%# %#",#"Got data for", feedRecord.profilePicture);
[self.activeDownload appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"%#",#"Fail!");
// Clear the activeDownload property to allow later attempts
self.activeDownload = nil;
// Release the connection now that it's finished
self.imageConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"%# %#",#"Done", feedRecord.profilePicture);
// Set appIcon and clear temporary data/image
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
self.feedRecord.image = image;
self.activeDownload = nil;
[image release];
// Release the connection now that it's finished
self.imageConnection = nil;
NSLog(#"%# %#",#"Our delegate is",delegate);
// call our delegate and tell it that our icon is ready for display
[delegate feedImageDidLoad:self.indexPathInTableView];
}
#end
Has anyone else experienced anything like this or can identify an issue with my code? Thanks!
you can use this code
[tableView performSelector:#selector(reloadData) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];
instead
[tableView reloadData];
You don't call the start method of the NSURLConnection object you create in your startDownload method.
Be sure to do it :
- (void)startDownload
{
NSLog(#"%# %#",#"Started downloading", feedRecord.profilePicture); // this shows in log
self.activeDownload = [NSMutableData data];
// alloc+init and start an NSURLConnection; release on completion/failure
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:feedRecord.profilePicture]] delegate:self];
self.imageConnection = conn;
NSLog(#"%#",conn); // this shows in log
[conn start];
[conn release];
}
You can also use the constructor : initWithRequest:delegate:startImmediately:
Also, your download will be blocked because of the run loop they are running if the user scrolls. Simply register your connection in the "common modes" :
[conn scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
Extracted from : how-to-avoid-blocked-downloads-during-scrolling
You do not start our NSURLConnection. Either initialize it with -[NSURLConnection initWithRequest:delegate:startImmediately:] or manually call -[NSURLConnection start] after initialization.
I have the same issue. Also, I nearly use the same code as you (it is from Apple sample LazyTableImages).
While the code in Apple's test project works, it did not work in my project - although I just made a copy of Apple's code.
What I found is: When I used
NSLog(#"Is%# main thread", ([NSThread isMainThread] ? #"" : #" NOT"));
in cellForRowAtIndexPath: as well as IconDownload.m's startDownload: (in both projects), I found out that it is the main thread in Apple's sample, but NOT main thread in my code.
This might be the problem.
Any idea how to solve?
EDIT !!! Solved !
I just forced main thread using
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:entry.imageURL, #"imageURL", indexPath, #"indexPath", nil];
[self performSelectorOnMainThread:#selector(startIconDownload:) withObject:info waitUntilDone:NO];
in cellForRowAtIndexPath: You will need a dictionary to send more than one argument to the method.
You can do a similar solution in your code. Replace the line:
[self startIconDownload:feedRecord forIndexPath:indexPath];
with my code and modify startIconDownload: like this
- (void)startIconDownload:(NSDictionary *)info
{
NSString *url = [info objectForKey:#"imageURL"];
NSIndexPath *indexPath = [info objectForKey:#"indexPath"];
...
}
Some variables my be different in your app.
But I just can't understand why it works in Apple's sample without forcing main thread.
Any idea?
Have a look here: http://www.depl0y.com/?p=345
Maybe will help.
Edit: Yep, is working for me. Let me know if is working for you too or you need more information.
Related
I have tried this a few times and I still don't get how to go into a JSON feed and retrieve what I need to grab.
The feed looks like this, in my code i'm trying to pull out all the titles. I dont know how to get down into the Json Feed.
- (void)viewDidLoad
{
[super viewDidLoad];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:#"http://api.storageroomapp.com/accounts/511a4f810f66026b640007b8/collections/511a51580f66023bff000ce9/entries.json?auth_token=Zty6nKsFyqpy7Yp5DP1L&preview_api=1"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
// Do any additional setup after loading the view from its nib.
}
-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
data = [[NSMutableData alloc] init];
}
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData{
[data appendData:theData];
}
-(void)connectionDidFinishLoading:(NSURLConnection *) connection{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
news = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
[mainTableView reloadData];
}
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"The data could not be downloaded - please make sure you're connected to either 3G or Wi-FI" delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
-(int)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [news count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MainCell"];
cell.textLabel.text = [[news objectAtIndex:indexPath.row] objectForKey:#"title"];
}
return cell;
}
and In my .H i have NSArray *news; and NSMutableData *data.
Any help would be great, could you please explain your self clearly as I'm a total newbie to this language.
Looking at the logic that you have in your code and the sample JSON that you posted, it doesn't look like your array is being populated with what you would want.
Initially, the JSON is in the form of a dictionary (hence the curly braces in the image you provided). Therefore, you should adjust your initial parsing of the JSON to something like so:
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
From here, you can receive a key from the dictionary. The one you'd be looking for is "array", which also actually is a dictionary despite its name.
NSDictionary *arrayDictionary = dictionary[#"array"];
Moving right along, you can finally access the "resources" array that you are looking for, and you can store that within the instance variable that you created in your .h file.
news = arrayDictionary[#"resources"];
Now, in the cellForRowAtIndexPath method you can access various elements of this array based on the index path row that is provided to you.
NSDictionary *newsItem = news[[indexPath row]];
Finally, you can access various properties like the titles from that news item, and set the text label's text.
NSString *title = newsItem[#"title"];
[[cell textLabel] setText:title];
atm i can successfully loading data from a WFC, read the json and put it on the right objects.
But my problem comes when i need to show a table with this data, cuz i don't know where to play the method or when should i call it. Atm looks like the table is created and after that i get the data from the web. Should i reload the table or can i get the info before the class calls cellForRowAtIndexPath: ?
Is there a way to make a connection synchronic and not synchronic? because in this case, if i cant get the list of eventos form wfc its has not point showing a table. So
Thx in advance!
my code:
-(id)init{
//call superclass designated inizialzer
self= [super initWithStyle:UITableViewStyleGrouped];
if(self){
[[self navigationItem] setTitle:#"Eventos"];
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://xxx.xxx.xxx.xxx/..."]];
[[[NSURLConnection alloc] initWithRequest:request delegate:self]autorelease];
}
return self;
}
about connection:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// label.text = [NSString stringWithFormat:#"Connection failed: %#", [error description]];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSError *error;
SBJSON *json = [[SBJSON new] autorelease];
NSDictionary *luckyNumbers = [json objectWithString:responseString error:&error];
[responseString release];
if (luckyNumbers == nil)
// label.text = [NSString stringWithFormat:#"JSON parsing failed: %#", [error localizedDescription]];
[luckyNumbers release];
else {
for (NSDictionary *object in [luckyNumbers objectForKey:#"EResult"]) {
Evento *e=[[Evento alloc] init];
e.nombre= [object objectForKey:#"nombre"];
e._id= (int)[object objectForKey:#"id"];
e.fecha= [object objectForKey:#"fecha"];
[[EventoStore defaultStore]addEvento:e];
[e release];
}
}
}
about the table it self:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [[[EventoStore defaultStore] allEventos]count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//check for reusable cell first and use it
UITableViewCell *cell= [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
//if there is no reusable cell, we create one
if(!cell){
cell= [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:#"UITableViewCell"]autorelease];
}
Evento *e=[[[EventoStore defaultStore] allEventos] objectAtIndex:[indexPath row]];
[[cell textLabel] setText:[e nombre]];
return cell;
}
-(void)tableView:(UITableView *) aTableView didSelectRowAtIndexPath:(NSIndexPath *) indexPax{
LotesViewController *loteViewController= [[[LotesViewController alloc]init]autorelease];
NSArray *eventos=[[EventoStore defaultStore]allEventos];
[loteViewController setEvento: [eventos objectAtIndex:[indexPax row]]];
[[self navigationController]pushViewController:loteViewController animated:YES];
}
you should reload the table after getting the data. you can show the activity indicator on the table till you get the data and once you get the data, you can remove the activity indicator and reload the table. this way you can find the solution to your problem. No need to go for synchronous connection. Just add the activity indicator once the connection is satrted and remove it when data comes.
I am trying to add Loading data option in viewDidLoad() function before [self.tableview reloaddata]. I am not sure how to add it and is there a way to make the user know that there is data getting loaded.
I am parsing JSON file and the data gets loaded on 3G pretty slow, so its the better way to allow user to know that data is being loaded with loading option. Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Add the view controller's view to the window and display.
responseData = [[NSMutableData data] retain];
self.twitterArray = [NSMutableArray array];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:#"http://search.twitter.com/search.json?q=mobtuts&rpp=5"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
[super viewWillAppear:animated];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSDictionary *results = [responseString JSONValue];
self.twitterArray = [results objectForKey:#"results"];
[self.tableView reloadData]; // How to add loading view before this statement
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self.twitterArray count];
}
I'm not quite sure what you mean by "loading view" but i guess you mean an activity indicator or something else what should be presented while you are loading data.
make an ivar UIActivityIndicatorView *myLoadingView;
Init it in viewDidLoad
myLoadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
myLoadingView.hidesWhenStopped = YES;
[myLoadingView stopAnimating];
[self.view addSubView:myLoadingView];
show the view before you start your connection [myLoadingView startAnimating];
hide it again when the download was finished by stoping it in the delegate method connectionDidFinishLoading: after [self.tableView reloadData]; [myLoadingView stopAnimating];
release it in viewDidUnload [myLoadingView release];
Feel free to ask if you have questions or if i had misunderstood you.
my question depends on my other question
NSMutableArray vs NSArray
i have created a navigationController and load a TableView inside with data from the other question. Now a get a detailview and get new data from xml, so i copy my methods and modifide them.
but it is the same stucture, i does not change a lot.
But now i get the same error.
i have in detailview.h
NSMutableArray *seminareArray;
and
#property (nonatomic, retain) NSMutableArray *seminareArray;
in detailview.m
#synthesize SeminareListeTabelle, selectedSeminar, seminareArray, receivedData;
i add this code
seminareArray = [[NSMutableArray alloc] init];
self.seminareArray = [NSMutableArray arrayWithCapacity:10];
before i add the data. and i get the error here
cell.textLabel.text = [seminareArray objectAtIndex:row];
EXC_BAD_ACCESS again some type problem
i add data to array like this
if([elementName isEqualToString:#"seminar"])
{
//NSLog(#"%#", [attributeDict objectForKey:#"name"]);
NSString *seminarName = [NSString stringWithFormat:#"%#", [attributeDict objectForKey:#"name"]];
[seminareArray addObject:seminarName];
[seminarName release];
}
the array is filled with data, but after tableView reload, i get this error.
//
// SeminareListingView.m
// Seminar App2
//
// Created by Alexander Frischbutter on 05.07.11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "SeminareListingView.h"
//#import "SeminareView.h"
#implementation SeminareListingView
#synthesize SeminareListeTabelle, selectedSeminar, seminareArray, receivedData;
- (void) parseData:(NSString *)url
{
if(receivedData)
{
receivedData = nil;
}
NSLog(#"Parsing... url: %#", url);
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#", url]] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if(theConnection)
{
receivedData = [[NSMutableData data] retain];
}
else
{
//label.text = #"XML nicht geladen";
NSLog(#"XML nicht gefunden");
}
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
SeminareListeTabelle = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
SeminareListeTabelle.delegate = self;
SeminareListeTabelle.dataSource = self;
SeminareListeTabelle.autoresizesSubviews = YES;
seminareArray = [[NSMutableArray alloc] init];
self.seminareArray = [NSMutableArray arrayWithCapacity:10];
[self parseData:[NSString stringWithFormat:#"http://akademie.kunden.fincha.com/semapp/sem_kat_arbtechnik.xml", selectedSeminar]];
self.navigationItem.title = [NSString stringWithFormat:#"%#", selectedSeminar];
self.view = SeminareListeTabelle;
// Do any additional setup after loading the view from its nib.
}
- (void)startParsingData
{
NSLog(#"Parsing started");
NSXMLParser *dataParser = [[NSXMLParser alloc] initWithData:receivedData];
dataParser.delegate = self;
[dataParser parse];
[dataParser release];
[receivedData release];
NSLog(#"Received Data in seminareArray");
/*
for(int i = 0; i < [seminareArray count]; i++)
{
NSLog(#"%d is %#", i, [seminareArray objectAtIndex:i]);
//NSLog(#"Count %d", [kategorienArray count]);
}
*/
//[seminareArray release];
NSLog(#"Reload data in TableView");
[self.SeminareListeTabelle reloadData];
NSLog(#"Data reloaded");
}
- (void)viewDidUnload
{
//[seminareArray release];
//[SeminareListeTabelle release];
NSLog(#"Vew unloaded");
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: SimpleTableIdentifier];
if (cell == nil) { cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:SimpleTableIdentifier] autorelease];
}
if([seminareArray count] != 0)
{
NSLog(#"Adding data to cell");
NSUInteger row = [indexPath row];
//cell.textLabel.text = [NSString stringWithFormat:#"bla, %d", row]; //[seminareArray objectAtIndex:row];
cell.textLabel.text = [seminareArray objectAtIndex:row];
NSLog(#"Added data to cell");
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//NSLog(#"Count %d", [self.seminareArray count]);
return [seminareArray count];
}
-(NSInteger) tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 0;
}
//Anzeige mit Seminaren öffnen bei Click auf die Zeile
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//gehe zurück zum ersten View
//NSLog(#"Received Data in seminareArray");
[[self navigationController] popViewControllerAnimated:YES];
}
- (void)connection:(NSURLConnection *)connection didReceiveResonse:(NSURLResponse *)response
{
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if(receivedData)
{
[receivedData appendData:data];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[connection release];
[receivedData release];
//label.text = #"Connection failed";
NSLog(#"Verbindung fehlgeschlagen!");
//[[self navigationController] popViewControllerAnimated:YES];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self startParsingData];
[connection release];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
//NSLog(#"Parser was called. Element: %#", elementName);
if([elementName isEqualToString:#"seminar"])
{
//NSLog(#"%#", [attributeDict objectForKey:#"name"]);
NSString *seminarName = [NSString stringWithFormat:#"%#", [attributeDict objectForKey:#"name"]];
[seminareArray addObject:seminarName];
[seminarName release];
}
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSLog(#"Parse Error %#", parseError);
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The problem stems from this code:
seminareArray = [[NSMutableArray alloc] init]; // owned
seminareArray = [NSMutableArray arrayWithCapacity:10]; // autoreleased
You're first initializing the semiareArray as an owned object, but then are re-setting it as an autoreleased object.
Meaning, it will be released after the run-loop terminates.
Remove the second (autoreleased) statement but keep the first, and everything should work fine.
The reason why you're getting the EXC_BAD_ACCESS error is because the seminareArray object is released at some point before it being used again.
Additionally, try to debug your
cell.textLabel.text = [seminareArray objectAtIndex:row];
Try setting it as id var = [seminareArray objectAtIndex:row]; and then setting cell.textLabel.text = var; This will tell you if the error occurs due to array being dealloc'd too early, or improper cell/textLabel.
Updated:
There's an additional problem is with the code:
NSString *seminarName = [NSString stringWithFormat:#"%#", [attributeDict objectForKey:#"name"]];
[seminareArray addObject:seminarName];
[seminarName release]; // <--
You're creating an auto-released object seminarName, which technically has retain count 0. You're adding it to the semiareArray, which ups the object retain count to 1. Then you're releasing it again. Which causes it to be dealloc'd at runtime. The problem is that when you're assigning the value to the textLabel, the object no longer exists.
Solution: remove the [seminarName release]; Don't worry about releasing the seminarName, since it's auto-released, it will be released when the array is dealloc'd, or when the object it removed from the array.
David's answer is correct but I would advice reading up on your memory management.
If you are synthesizing properties then it is a lot easier to use the getters and setters and let them do the memory management for you. The exception being in your init/dealloc methods where you should try to directly use the ivars to avoid any potential side effects of using the getters/setters.
With the two lines david highlighted
seminareArray = [[NSMutableArray alloc] init]; // owned
seminareArray = [NSMutableArray arrayWithCapacity:10]; // autoreleased
You could potentially use either if the memory management was done correctly.
The first line on its own is correct as it creates an instance of NSMutableArray with a retain count of +1 then assigns it straight to the ivar.
Then as David pointed out the second line replaces this with an autoreleased NSMutableArray so this line is superflous and crashes your program. The method arrayWithCapacity: is not simply setting the capacity of the array it is giving you a new autoreleased array.
If you wanted to use an autoreleased NSMutableArray then you would need to use the setter either with dot notation of passing a message:
self.seminareArray = [NSMutableArray arrayWithCapactiy:10];
OR
[self setSeminareArray:[NSMutableArray arrayWithCapcity:10]];
By simply referencing things straight to seminareArray you are avoiding the getters/setters you synthesized and therefore are responsible for all of your memory management.
A hint at a memory leak:
self.seminareArray = [[NSMutableArray alloc] init];
this will leak memory because seminare is declared as retained property:
#property (nonatomic, retain) NSMutableArray *seminareArray;
This is not, anyway, the cause of your other issue.
The error you are having is caused by row being greater that the count of your array. So either you don't add sufficient objects to the array, or you try to use an incorrect value for row. Inspect that line with a debugger and ensure that row never goes beyond [seminare count]; you will find out easily why it happens.
I have an app that uses a segmentedControl. First item is an "All" item, where the rest is created from an array based on result from webservice. When "All" is selected I want to request all the request.
How can I go about this,
NSArray *urls = [NSArray arrayWithObjects:#"http://service/group/1/",
#"http://service/group/2/", nil];
I want to collect all result from the calls into a collection and display it in a UITableView when the "All" item is selected and probably in viewDidLoad.
For the other segments only one of the request is issued and callback with an array that then is used in:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
I have tried to look at this example for making the request from the array MultipleDownloads
Thanks,
The method in my viewController to initiate the multiple download:
- (void)requestChildrenInBackground {
queue = [[NSOperationQueue alloc] init];
//Todo remove hard coded and get from previous request respons
NSArray *urls = [NSArray arrayWithObjects: #"http://service/1/children",
#"http://service/2/children",
#"http://service/3/children", nil];
NSLog(#"%#", urls);
for (NSString * url in urls)
{
GetSchedule *operation =
[GetSchedule urlDownloaderWithUrlString:url];
[queue addOperation:operation];
}
}
This is how the multiple request gets handled:
#import "GetSchedule.h"
#import "JSON.h"
#import "Authentication.h"
#import "AttendanceReportViewController.h"
#interface GetSchedule ()
- (void)finish;
#end
#implementation GetSchedule
#synthesize appDelegate;
#synthesize username;
#synthesize password;
#synthesize authenticationString;
#synthesize encodedLoginData;
#synthesize schedulesArray;
#synthesize url = _url;
#synthesize statusCode = _statusCode;
#synthesize data = _data;
#synthesize error = _error;
#synthesize isExecuting = _isExecuting;
#synthesize isFinished = _isFinished;
+ (id)urlDownloaderWithUrlString:(NSString *)urlString {
NSURL * url = [NSURL URLWithString:urlString];
GetSchedule *operation = [[self alloc] initWithUrl:url];
return [operation autorelease];
}
- (id)initWithUrl:(NSURL *)url {
self = [super init];
if (self == nil)
return nil;
_url = [url copy];
_isExecuting = NO;
_isFinished = NO;
return self;
}
- (void)dealloc
{
[username release];
[password release];
[encodedLoginData release];
[_url release];
[_connection release];
[_data release];
[_error release];
[super dealloc];
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
self.username = appDelegate.username;
self.password = appDelegate.password;
Authentication *auth = [[Authentication alloc] init];
authenticationString = (NSMutableString*)[#"" stringByAppendingFormat:#"%#:%#", username, password];
self.encodedLoginData = [auth encodedAuthentication:authenticationString];
[auth release];
NSLog(#"operation for <%#> started.", _url);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
// Setup up the request with the url
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:_url];
[request setHTTPMethod:#"GET"];
[request setValue:[NSString stringWithFormat:#"Basic %#", encodedLoginData] forHTTPHeaderField:#"Authorization"];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
else {
_data = [[NSMutableData alloc] init];
}
}
- (void)finish
{
NSLog(#"operation for <%#> finished. "
#"status code: %d, error: %#, data size: %u",
_url, _statusCode, _error, [_data length]);
[_connection release];
_connection = nil;
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
#pragma mark -
#pragma mark NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
//[_data release];
//_data = [[NSMutableData alloc] init];
[_data setLength:0];
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
_statusCode = [httpResponse statusCode];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Parse the responseData of json objects retrieved from the service
SBJSON *parser = [[SBJSON alloc] init];
NSString *jsonString = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding];
NSDictionary *jsonData = [parser objectWithString:jsonString error:nil];
NSMutableArray *array = [jsonData objectForKey:#"Children"];
schedulesArray = [NSMutableArray array];
[schedulesArray addObject:array];
// Callback to AttendanceReportViewController that the responseData finished loading
[attendanceReportViewController loadSchedule];
[self finish];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
_error = [error copy];
[self finish];
}
#end
When all data is received I want to call back to my ViewController and get an array with all data from all request made.
Create a downloader
Create a downloader class using NSURLConnection.
Keep a member variable called downloaderObjectId.
Write a delegate method for this object. This method will pass the downloaded data and downloaderObjectId back to the delegate.
In the delegate.
Create multiple downloader objects(As per your ncessity) with unique value for the downloaderObjectId.
Store these objects in a NSMutableDictionary
Key for each object will be downloaderObjectId. so that when the delegate method is called after download you take the exact object back from the NSMutableDictionary using this key.
Main point.
Each time delegate is called. You should remove the object from dictionary(The object who is done with the download and called his delgate. You can identify this object by the key downloaderObjectId he holds. )
Then check the count of dictionary. If it is zero you can make sure that your downloads are completed. So you can call your viewcontroller.