I used one tutorial to include My twitter home page in my code, but for me does not works.
This is the code
#implementation VSViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self twitterTimeline];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)twitterTimeline {
ACAccountStore *account = [[ACAccountStore alloc] init]; // Creates AccountStore object.
// Asks for the Twitter accounts configured on the device.
ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[account requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error)
{
// If we have access to the Twitter accounts configured on the device we will contact the Twitter API.
if (granted == YES){
NSArray *arrayOfAccounts = [account accountsWithAccountType:accountType]; // Retrieves an array of Twitter accounts configured on the device.
// If there is a leat one account we will contact the Twitter API.
if ([arrayOfAccounts count] > 0) {
ACAccount *twitterAccount = [arrayOfAccounts lastObject]; // Sets the last account on the device to the twitterAccount variable.
NSURL *requestAPI = [NSURL URLWithString:#"http://api.twitter.com/1.1/statuses/user_timeline.json"]; // API call that returns entires in a user's timeline.
// The requestAPI requires us to tell it how much data to return so we use a NSDictionary to set the 'count'.
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setObject:#"100" forKey:#"count"];
[parameters setObject:#"1" forKey:#"include_entities"];
// This is where we are getting the data using SLRequest.
SLRequest *posts = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:requestAPI parameters:parameters];
posts.account = twitterAccount;
// The postRequest: method call now accesses the NSData object returned.
[posts performRequestWithHandler:
^(NSData *response, NSHTTPURLResponse
*urlResponse, NSError *error)
{
// The NSJSONSerialization class is then used to parse the data returned and assign it to our array.
self.array = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&error];
if (self.array.count != 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData]; // Here we tell the table view to reload the data it just recieved.
});
}
}];
}
} else {
// Handle failure to get account access
NSLog(#"%#", [error localizedDescription]);
}
}];
}
#pragma mark Table View Data Source Mehtods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Returns the number of rows for the table view using the array instance variable.
return [_array count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Creates each cell for the table view.
static NSString *cellID = #"CELLID" ;
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// Creates an NSDictionary that holds the user's posts and then loads the data into each cell of the table view.
IT CRASHES HERE with the error -[__NSCFDictionary objectAtIndexedSubscript:]: unrecognized selector sent to instance
NSDictionary *tweet = _array[indexPath.row];
cell.textLabel.text = tweet[#"text"];
return cell;
}
The problem is that _array is not an array (it's a dictionary).
You only have to read the error message to see this.
You can't make any assumptions about what kind of object JSONObjectWithData:options:error: will give you; it depends on what the JSON data looks like. In this case, the JSON structure has resulted in the Objective-C equivalent being an NSDictionary. The fact that you may have called your array instance variable an NSArray makes no difference; in Objective-C, an object is what it is (polymorphism).
You didn't catch the problem earlier because count is a method both of NSDictionary and of NSArray, so you didn't crash when you said self.array.count.
Related
I'm retrieving entity data from an endpoint with the following code (I've built my app on Drupal's iOS SDK), and I'm trying to display it in my TableView cell.
That said, when I use the following code to display it in my cell, it doesn't seem to want to work (the label just appears empty)? See console data below as well.
TableViewController.h
#property (strong, nonatomic) NSMutableArray *messages;
TableViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *entityData = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"1"] forKey:#"uid"];
[DIOSEntity
entityGet:entityData
name:#"entity_message"
eid:#"uid"
success:^(AFHTTPRequestOperation *op, id response) {
self.messages = (NSMutableArray *)response;
NSLog(#"This is all of the data from response %#", response); }
failure:^(AFHTTPRequestOperation *op, NSError *err) { NSLog(#"failed to get data"); }
];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *PointsTableIdentifier = #"MyMessagesCell";
MyMessagesCell *cell = (MyMessagesCell *)[tableView dequeueReusableCellWithIdentifier:PointsTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyMessagesCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSDictionary *receivedMessages = [self.messages objectAtIndex:indexPath.row];
[[cell subjectLine] setText:[receivedMessages objectForKey:#"field_message_body"]];
NSLog(#"Received message subject are here %#", receivedMessages);
return cell;
}
CONSOLE DATA returns as:
2015-11-23 18:26:47.998 [624:153845] This is all of the data from response {
arguments = (
);
data = (
);
"field_message_body" = {
und = (
{
format = "<null>";
"safe_value" = "Testing message center";
value = "Testing message center";
}
);
};
"field_message_group_ref" = (
);
"field_message_subject" = {
und = (
{
format = "<null>";
"safe_value" = Testing;
value = Testing;
}
);
};
The tableView finished loading before you got the response. You can log something in
numberOfRowsInSection: and cellForRowAtIndexPath method see if it was before "This is all of the data from response ...".
You can call [self.tableview reloadData] on main thread to reload the tableview after you get the messages.
You need to make a better management of the response data. As #limfinity said, the responseData is a NSDictinary, not a NSMutableArray.
Also, you should reload the tableview after setting the messages in self.messages, so the table gets the new data from the 'new' self.messages:
[DIOSEntity
entityGet:entityData
name:#"entity_message"
eid:#"uid"
success:^(AFHTTPRequestOperation *op, id response) {
self.messages = (NSMutableArray *)response;
NSLog(#"This is all of the data from response %#", response); }
dispatch_async(dispatch_get_main_queue(), ^(void){
[self.tableView reloadData];
});
failure:^(AFHTTPRequestOperation *op, NSError *err) { NSLog(#"failed to get data"); }
];
EDIT
Just to make a simple (but not so correct) change to make this work and don't crash, you should say:
self.messages = [NSMutableArray arrayWithObject:(NSDictionary*)response];
instead of:
self.messages = (NSMutableArray *)response;
But I think you should go deeper and make the response be a NSArray, not just one NSDictionary (multiple NSDictionaries, instead of just one)
Based on your console logs:
2015-11-23 18:26:47.998 [624:153845] This is all of the data from response {
arguments = (
);
data = (
);
"field_message_body" = {//This is not a String!
und = (
{
format = "<null>";
"safe_value" = "Testing message center";
value = "Testing message center";
}
);
};
[receivedMessages objectForKey:#"field_message_body"] is not a string but a dictionary.
You need to parse that object a little more:
NSString *value = receivedMessages[#"field_message_body"][#"und"][#"value"];
[[cell subjectLine] setText:value];
And also don't forget to reload the tableview after you fetch the data.
in server side data i am getting 17000 users names.when i get 1000 user names ofter Xcode is crashing giving these error message from debugger:terminated due to memory pressure please help me how to get 17000 user names data how to handle without memory presser.i got these problem in real device.
why i am taking array in app delegate i need to use these 17000 user names in so many view controllers
I am declare array
AppDelegate.h
#property (nonatomic,retain) NSMutableArray *userNamesGettingArrayObj;
I am declare string
ModelClass.h
#property (nonatomic,strong) NSString *nameString;
ModelClass.m
// i am getting server data in these dictionary object
NSDictionary *dictobj=[NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&err];
for (int i = 0; i<=[[dictobj valueForKey:#"name"] count]-1;i++)
{
_nameString=[[dictobj valueForKey:#"name"]objectAtIndex:i];
[delegate.userNamesGettingArrayObj addObject:_nameString];
}
viewController.m
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [delegate.userNamesGettingArrayObj count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (_tableViewObj == tableView) {
static NSString *ide=#"ide";
UITableViewCell *cell=[_tableViewObj dequeueReusableCellWithIdentifier:ide];
if (cell==nil) {
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ide];
}
cell.textLabel.text=[delegate.userNamesGettingArrayObj objectAtIndex:indexPath.row];
return cell;
}
}
I suspect that your repeated repeated repeated use of valueForKey may be the problem. Whether it is the problem or not, it is so inefficient it hurts my eyes. About 100 times faster:
NSArray* names = dictObj [#"name"];
NSMutableArray* userNames = delegate.userNamesGettingArrayObj;
for (NSString* nameString in names)
[names addObject:nameString];
17,000 names should be no problem whatsoever unless there's something wrong with your code.
valueForKey is a high-level method that is usually entirely inappropriate for processing JSON, or for accessing anything stored in a dictionary. Unless you have a very good reason (one that you could explain if asked about it), use objectForKey or just [#"someKey"].
Have to tried??
make response dict golable and
cell.textlable.text=[dictobj valueForKey:#"name"]objectAtIndex:indexpath.row];
Dont get that much data from sever at same time, use load more button or auto hit functions when your table reach at end of list.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;{
if(nearByPlacesTableView.contentOffset.y >= (nearByPlacesTableView.contentSize.height - nearByPlacesTableView.bounds.size.height)) {
if (isPageRefresing) {
// [self performSelector:#selector(getRecords:) withObject:nextPageTokenString afterDelay:0.0f];
[self performSelectorInBackground:#selector(getRecords:) withObject:nextPageTokenString];
}
}
}
// HTTP Utility class
-(void)getRecords:(NSString *)token{
NSString *serverUrl;
if ([self.headStr isEqualToString:#"Nearby"])
{
serverUrl = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/nearbysearch/json?pagetoken=%#&key=AIzaSyCd2",token];
}else{
serverUrl = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/textsearch/json?pagetoken=%#&key=AIzaSyCd2",token];
}
//Create URL and Request
NSURL * url = [NSURL URLWithString:serverUrl];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSURLResponse * response;
NSError * error = nil;
//Send Request
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error == nil)
{
NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSMutableArray *nextpageArr = [NSMutableArray new];
nextpageArr = [[json valueForKey:#"results"] mutableCopy];
nextPageTokenString=[json valueForKey:#"next_page_token"];
for (id dict in [json valueForKey:#"results"])
{
NSMutableDictionary *mutableDict;
if ([dict isKindOfClass:[NSDictionary class]]) {
mutableDict=[dict mutableCopy];
}
else{
mutableDict=dict;
}
[mutableDict setValue:#"0" forKey:#"checked"];
[mutableDict setValue:#"0" forKey:#"is_favourite"];
[nextpageArr addObject:mutableDict];
}
NSString *status=[json valueForKey:#"status"];
if ([status isEqualToString:#"INVALID_REQUEST"]) {
isPageRefresing=NO;
}else if ([status isEqualToString:#"OK"]){
isPageRefresing=YES;
}
if (nextpageArr.count) {
[self.nearbyVenues addObjectsFromArray:nextpageArr];
}
[nearByPlacesTableView reloadData];
[SVProgressHUD dismiss];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No Internet" message:#"Please check your internet connection." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
//i store all data into at time when i am using these code
delegate.userNamesGettingArrayObj=[[[NSArray arrayWithObject:dictobj]valueForKey:#"name"]objectAtIndex:0];
This question already has answers here:
Table View with Images, slow load and scroll
(4 answers)
Closed 7 years ago.
I'm creating a UITableViewController to display the roster of a hockey team. The tableViewController makes calls to the web to get the player's stats and a small picture to display in the tableViewCell. However, when I scroll through the TableView, it isn't smooth. It's incredibly jagged. How can I make it so (if this will decrease its work load) the player's pictures don't load until they're on-screen? Here is my current code (I've subclassed UITableViewCell):
EDIT: I've edited my code to follow a comment below. The property imagesCache is actually a UIMutableDictionary (confusing, sorry). However, now I get the error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: http://app-assets3.sportngin.com/app_images/noPhoto-square.jpg?1428933774)'
*** First throw call stack:
(0x1865f6530 0x1975cc0e4 0x1864e1348 0x1000496a8 0x185f87168 0x1874d3be8 0x187425374 0x187414ecc 0x1874d694c 0x1000acf94 0x1000b7db8 0x1000b02c4 0x1000ba5d4 0x1000bc248 0x197dfd22c 0x197dfcef0)
libc++abi.dylib: terminating with uncaught exception of type NSException
Here is my code:
#import "RosterTableTableViewController.h"
#import "TFHpple.h"
#import "RosterListing.h"
#import "RosterListingCellTableViewCell.h"
#interface RosterTableTableViewController ()
#property (nonatomic, strong) NSMutableArray *rosters;
#property (nonatomic, strong) NSMutableDictionary *imagesDictionary;
#property NSMutableDictionary *imageCache;
#end
#implementation RosterTableTableViewController
- (void) loadRoster
{
NSURL *RosterURL = [NSURL URLWithString:#"http://www.lancers.com/roster/show/1502650?subseason=197271"];
NSData *RosterHTMLData = [NSData dataWithContentsOfURL:RosterURL];
TFHpple *RosterParser = [TFHpple hppleWithHTMLData:RosterHTMLData];
// Get the data
NSString *RosterNumberPathQueryString = #"//tbody[#id='rosterListingTableBodyPlayer']/tr/td[#class='number']";
NSArray *RosterNumberNodes = [RosterParser searchWithXPathQuery:RosterNumberPathQueryString];
NSString *RosterNamePathQueryString = #"//tbody[#id='rosterListingTableBodyPlayer']/tr/td[#class='name']/a";
NSArray *RosterNameNodes = [RosterParser searchWithXPathQuery:RosterNamePathQueryString];
NSString *RosterImagePathQueryString = #"//tbody[#id='rosterListingTableBodyPlayer']/tr/td[#class='photo']/a/img";
NSArray *RosterImageNodes = [RosterParser searchWithXPathQuery:RosterImagePathQueryString];
NSMutableArray *rosterItems = [[NSMutableArray alloc] initWithCapacity:0];
for (int i = 0; i < RosterNumberNodes.count; ++i) {
RosterListing *thisRosterListing = [[RosterListing alloc] init];
thisRosterListing.playerNumber = [[[RosterNumberNodes objectAtIndex:i] firstChild] content];
thisRosterListing.playerName = [[[RosterNameNodes objectAtIndex:i] firstChild] content];
thisRosterListing.playerURL = [[RosterNameNodes objectAtIndex:i] objectForKey:#"href"];
#try {
thisRosterListing.playerImageURL = [[RosterImageNodes objectAtIndex:i] objectForKey:#"src"];
}
#catch (NSException *e) {}
/*
NSLog(#"%#", thisRosterListing.playerNumber);
NSLog(#"%#", thisRosterListing.playerName);
NSLog(#"%#", thisRosterListing.playerURL);
NSLog(#"%#", thisRosterListing.playerImageURL);
*/
[rosterItems addObject:thisRosterListing];
}
self.rosters = rosterItems;
}
- (instancetype) initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
self.navigationItem.title = #"Roster";
self.imageCache = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self loadRoster];
// Load the Cell NIB file
UINib *nib = [UINib nibWithNibName:#"RosterListingCellTableViewCell" bundle:nil];
// Register this NIB, which contains the cell
[self.tableView registerNib:nib forCellReuseIdentifier:#"RosterCell"];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 54;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.rosters.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Get a new or recycled cell
RosterListingCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"RosterCell" forIndexPath:indexPath];
RosterListing *thisRosterListing = [self.rosters objectAtIndex:indexPath.row];
cell.playerNumberLabel.text = thisRosterListing.playerNumber;
cell.playerNameLabel.text = thisRosterListing.playerName;
__block UIImage *image = [self.imageCache objectForKey:thisRosterListing.playerImageURL];
cell.imageView.image = image;
if(image == nil) {
//If nil it's not downloaded, so we download it,
//We MUST download in a separate thread otherwise the scroll will be really slow cause the main queue will try to download each cell as they show up and every time they show up
NSURL *imageURL = [NSURL URLWithString: thisRosterListing.playerImageURL];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:imageURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Completion Handler is executed in an async way
if([self.imageCache objectForKey:thisRosterListing.playerImageURL] == nil)
self.imageCache[thisRosterListing.playerImageURL] = image;
//We need to execute the image update in the main queue otherwise it won't work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
RosterListingCellTableViewCell *aCell = (RosterListingCellTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
aCell.imageView.image = image;
}];
}];
[dataTask resume];
}
return cell;
}
Working with images in UITableViewCells can be a bit tricky at first, I do have a code that might help you, give me a second while I search it.
Basically what you want to do is check that the row you downloaded the image for is still been displayed (as the user can scroll faster than images are downloaded) and after download ended storage it locally so you won't have to download it again.
EDIT: Here is the code, sorry the lateness
#propery NSMutableDictionary *imagesDictionary;
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
//...
//We load the model information for that cell
NSDictionary *cellInfo = self.dataModel[indexPath.row];
__block UIImage *image = [self.imagesDictionary objectForKey:[cellInfo objectForKey:#"avatar"]];
cell.avatarView.image = image;
if(image == nil) {
//If nil it's not downloaded, so we download it,
//We MUST download in a separate thread otherwise the scroll will be really slow cause the main queue will try to download each cell as they show up and every time they show up
NSURL *imageURL = [NSURL URLWithString:URL];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:imageURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Completion Handler is executed in an async way
if([self.imagesDictionary objectForKey:[cellInfo objectForKey:#"avatar"]] == nil)
if(error == nil) {
// no error
image = [UIImage imageWithData:data];
if (image == nil) {
//nil image, in my case I use a default undefined image
image = [UIImage imageNamed:#"undefined_user"];
}
//Now we are sure image is never nil
[self.imagesDictionary setObject:image forKey:[cellInfo objectForKey:#"avatar"]];
//We need to execute the image update in the main queue otherwise it won't work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UITableviewCell *aCell = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
aCell.avatarView.image = image;
}];
}];
[dataTask resume];
return cell;
I am creating an app that takes a JSON text from a server and translates it into a match schedule for a robotics tournament. I have custom cells that are filled with the data I receive from the site and they display the data for each individual match just fine. The problem is that when I put the matches into a .plist file ("data.plist") so that once the internet connection is established initially, the user doesn't necessarily need to reconnect to the internet once the app is killed in order to view the match schedule for the day. My code works perfectly until I don't connect to the internet. For some reason, the app never goes into the function that creates the cells once the internet connection fails. Please help!! Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
aRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://jrl.teamdriven.us/source/scripts/getElimMatchResultsJSON.php"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
aConnection = [[NSURLConnection alloc] initWithRequest:aRequest delegate:self];
if(aConnection){
receivedData = [NSMutableData data];
}
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No Connection!!" message:nil delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
jrlDirectory = [paths objectAtIndex:0];
path = [jrlDirectory stringByAppendingPathComponent:#"data.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:#"data.plist"]) {
[[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle]pathForResource:#"data" ofType:#"plist"] toPath:path error:nil];
}
dataDict = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
// Prevents data from repeating itself
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[receivedData appendData: data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Failed" message:nil delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
NSLog(#"Connection failed! Error - %# %#", [error localizedDescription], [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"Success! Received %d bits of data", [receivedData length]);
// Must allocate and initialize all mutable arrays before changing them
sweet16 = [[NSMutableArray alloc]init];
quarterfinals = [[NSMutableArray alloc]init];
semi = [[NSMutableArray alloc]init];
finals = [[NSMutableArray alloc]init];
dict = [[NSMutableDictionary alloc]init];
// The error is created and can be referred to if the code screws up (example in the "if(dict)" loop)
NSError *error;
dict = [NSJSONSerialization JSONObjectWithData:receivedData options:NSJSONReadingMutableLeaves error:&error];
matchNames = [[dict objectForKey:#"ElimMatchListResults"]allKeys];
matchNames = [matchNames sortedArrayUsingSelector:#selector(compare:)];
// Categorize the matches based on the first two letters of their match names
for (NSString *name in matchNames) {
if([[name substringToIndex:2]isEqual:#"16"]){
[sweet16 insertObject:[[dict objectForKey:#"ElimMatchListResults"] objectForKey:name] atIndex:sweet16.count];
}
else if([[name substringToIndex:2]isEqual:#"Q."]){
[quarterfinals insertObject:[[dict objectForKey:#"ElimMatchListResults"]objectForKey:name] atIndex:quarterfinals.count];
}
else if([[name substringToIndex:2]isEqual:#"S."]){
[semi insertObject:[[dict objectForKey:#"ElimMatchListResults"]objectForKey:name] atIndex:semi.count];
}
else if([[name substringToIndex:2]isEqual:#"F."]){
[finals insertObject:[[dict objectForKey:#"ElimMatchListResults"]objectForKey:name] atIndex:finals.count];
}
}
headers = [NSArray arrayWithObjects:#"Sweet 16", #"Quarterfinals", #"Semifinals", #"Finals", nil];
sections = [NSArray arrayWithObjects:sweet16, quarterfinals, semi, finals, nil];
// If the dictionary "dict" gets filled with data...
if (dict) {
[[self tableView]setDelegate:self];
[[self tableView]setDataSource:self];
// Now uses data storage so that the user only needs to initially connect to the internet and then they can keep the schedule afterwords
[dataDict setObject: [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithArray:sections], #"sections",
[NSArray arrayWithArray:headers], #"headers",
nil]
forKey:#"Matches"];
[dataDict writeToFile:path atomically:YES];
}
else{
NSLog(#"%#", error);
}
}
// Set the number of sections based on how many arrays the sections array has within it
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] count];
}
// Set the number of rows in each individual section based on the amount of objects in each array
// within the sections array
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:section] count];
}
// Set headers of sections from the "headers" array
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
return [[[dataDict objectForKey:#"Matches"] objectForKey:#"headers"] objectAtIndex:section];
}
// Create cells as the user scrolls
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"Entered Final Loop");
static NSString *cellIdentifier = #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(!cell){
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.matchNum.text = [[[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] objectForKey:#"MatchID"];
cell.red1.text = [NSString stringWithFormat:#"%#",[[[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] objectForKey:#"Red 1"]];
cell.red2.text = [NSString stringWithFormat:#"%#",[[[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] objectForKey:#"Red 2"]];
cell.redScore.text = [NSString stringWithFormat:#"%#", [[[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] objectForKey:#"Red Score"]];
cell.blue1.text = [NSString stringWithFormat:#"%#",[[[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] objectForKey:#"Blue 1"]];
cell.blue2.text = [NSString stringWithFormat:#"%#",[[[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] objectForKey:#"Blue 2"]];
cell.bluescore.text = [NSString stringWithFormat:#"%#", [[[[[dataDict objectForKey:#"Matches"] objectForKey:#"sections"] objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] objectForKey:#"Blue Score"]];
return cell;
}
I apologize for the lengthy code, I'm just trying to make sure I got all the details in. Please ask any questions you need to in order to clarify, I'm just really stuck and flustered right now as to why it never enters the function that creates cells if it doesn't have an internet connection.
A couple of reactions:
Your fileExistsAtPath doesn't look right. Surely you should have fully qualified path, e.g.:
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle]pathForResource:#"data" ofType:#"plist"] toPath:path error:nil];
}
I'd also probably suggest you check the success of copyItemAtPath (either the return value or use the error parameter).
I assume you know that the data.plist will never be successfully refreshed before viewDidLoad finishes (because the connection is initiated asynchronously and you return immediately from initWithRequest). This code is just loading the last data.plist values while retrieving the new data proceeds (the previous error notwithstanding). Is that your expectation?
On top of my prior point, your connectionDidFinishLoading does not appear to be issuing a reloadData call for the table, so it seems like you'll always see the previous data. When the connection is done, connectionDidFinishLoading should call reloadData for the UITableView.
Minor, unrelated detail, but I'd probably initialize receivedData in the NSURLConnectionDataDelegate method didReceiveResponse (and only if that response was successful, too). It doesn't really belong in viewDidLoad.
I might also encourage you to check for failure of JSONObjectWithData. Some networking failures manifest themselves as a successful NSURLConnection request that returns an HTML page (!) reporting an error. That HTML page will fail JSONObjectWithData processing. You might want to abort your parsing routine if JSONObjectWithData returns nil or if the error object is not nil.
Found my solution. The
[[self tableView]setDelegate:self];
[[self tableView]setDataSource:self];
Was only located in the "connectionDidFinishLoading:" function, so the data sources were never set if there was no internet connection.
I really appreciated everyone's help in this problem and I have edited a lot of my code to make it work better based off of those suggestions. Thanks!
I have tableview where is name and status. Status is changed when come apple push notification (APNS).
But I have this problem. What can I do, if notification didn't come? Or if user tap on close button of this message.
I try to update table by using ASIHTTPRequest:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
HomePageTableCell *cell = (HomePageTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
cell.nameLabel.text = [device valueForKey:#"name"];
if ([[device valueForKey:#"status"] isEqualToNumber:#1])
{
cell.status.text = #"Not configured";
cell.stav.image = [UIImage imageNamed:#"not_configured.png"];
}
if ([[device valueForKey:#"status"] isEqualToNumber:#2])
{
//some other states
}
return cell;
}
I try this to change status before cell is loading...
- (void) getStatus:(NSString *)serialNumber
{
NSURL *url = [NSURL URLWithString:#"link to my server"];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
__weak ASIHTTPRequest *request_b = request;
request.delegate = self;
[request setPostValue:#"updatedevice" forKey:#"cmd"];
[request setPostValue:serialNumber forKey:#"serial_number"]; //get status of this serial number
[request setCompletionBlock:^
{
if([self isViewLoaded])
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
if([request_b responseStatusCode] != 200)
{
ShowErrorAlert(#"Comunication error", #"There was an error communicating with the server");
}
else
{
NSString *responseString = [request_b responseString];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *result = [parser objectWithString:responseString error:nil];
status = [result objectForKey:#"status"];
NSInteger statusInt = [status intValue]; //change to int value
//here I want to change cell status in SQLite, but don't know how
//something with indexPath.row? valueForKey:#"status"???
}
}
}];
[request setFailedBlock:^
{
if ([self isViewLoaded])
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
ShowErrorAlert(#"Error", [[request_b error] localizedDescription]);
}
}];
[request startAsynchronous];
}
Or it is better way to change status in my table view if apple notification didn't come or user didn't tap on notification message? Thanks
EDIT:
I don't know how to store data to NSManagedObject *device. Can you help me with this?
I try this, but it didn't works: (on place where you write)
NSInteger statusInt = [status intValue]; //change to int value
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[device setValue:statusInt forKey:#"status"];
EDIT2:
I get it, but problem is with reload table data
NSString *responseString = [request_b responseString];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *result = [parser objectWithString:responseString error:nil];
NSString *status = [result objectForKey:#"status"];
NSInteger statusInt = [status intValue]; //change to int value
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[device setValue:statusInt forKey:#"status"]; //there is problem in save statusInt
// [device setValue:#5 forKey:#"status"]; //if I do this it is ok status is integer16 type
and second problem is in that reload table data. I put there this
[self.tableView reloadData]
but It reloading again and again in loop, what is wrong? I thing there is infinite loop, if I didn't reload table data changes will be visible in next app load. I think problem is that I call
- (void) getStatus:(NSString *)serialNumber atIndexPath:(NSIndexPath *)indexPath
{}
in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
}
Better should be in viewDidLoad or viewDidApper, but I don't know how make loop for all devices and call
[self getStatus:[device valueForKey:#"serialNumber"] atIndexPath:indexPath];
on that place.
EDIT3:
what if I do it like this:
- (void)viewDidLoad
{
[self updateData];
[self.tableView reloadData];
}
-(void)updateData
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Device"];
request.returnsDistinctResults = YES;
//request.resultType = NSDictionaryResultType;
request.propertiesToFetch = #[#"serialNumber"];
NSArray *fetchedObjects = [self.managedObjectContext
executeFetchRequest:request error:nil];
NSArray *result = [fetchedObjects valueForKeyPath:#"serialNumber"];
//there I get all serialNumbers of my devices and than I call method getStatus and get "new" status and than update it in Core Data.
}
Is that good way to solve this problem? I think better will be if I call getStatus method only one times and get array of statuses.
Maybe I can set all serialNubers in one variable ('xxx','yyyy','zzz') and on server do SELECT * FROM Devices WHERE serialNumber in (serialNuber).
Do you think this could work? I don't have experience how to take data from array to string like ('array_part1','array_part2'....)
Where in your code do you call [UITableView reloadData]?
You should call reloadData on your tableview once you have retrieved the new data from the server. As your server call is async the server call will run on a separate thread while the main thread continues, therefore I presume you have the following problem...
- (void) ...
{
[self getStatus:#"SERIAL_NUMBER"];
[self reloadData]; // This will be called before the async server call above has finished
}
Therefore you are reloading the original data and therefore the new data, which may have loaded a few seconds after, wont be shown.
To fix this, adjust the [getStatus:] method to call the [UITableView reloadData] method on server response.
[request setCompletionBlock:^
{
if([self isViewLoaded])
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
if([request_b responseStatusCode] != 200)
{
ShowErrorAlert(#"Comunication error", #"There was an error communicating with the server");
}
else
{
NSString *responseString = [request_b responseString];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *result = [parser objectWithString:responseString error:nil];
status = [result objectForKey:#"status"];
NSInteger statusInt = [status intValue]; //change to int value
// Store the server response in NSManagedObject *device,
// which will be used as the data source in the tableView:cellForRowAtIndexPath: method
// Once stored, check the tableview isn't NULL and therefore can be accessed
// As this call is async the tableview may have been removed and therefore
// a call to it will crash
if(tableView != NULL)
{
[tableView reloadData];
}
}
}
}];
ASIHTTPRequest is also no longer supported by the developers, I suggest you look into AFNetworking.
Update
In response to the problem you are now having with setting the statusInt within the device NSManagedObject
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[device setValue:statusInt forKey:#"status"]; //there is problem in save statusInt
This is caused as statusInt is an NSInteger which is a primary datatype and not an NSObject as expected by [NSManagedObject setValue:forKey:]. From the documentation for [NSManagedObject setValue:forKey:], the methods expected parameters are as follows.
- (void)setValue:(id)value forKey:(NSString *)key
Therefore you need to pass, in this case, an NSNumber. The problem with NSInteger is that it's simply a dynamic typedef for the largest int datatype based on the current system. From NSInteger's implementation you can see the abstraction.
#if __LP64__
typedef long NSInteger;
#else
typedef int NSInteger;
#endif
If your current system is 64-bit it will use the larger long datatype.
Now, technically the returned status value from the server can be stored as it is without any conversion as an NSString. When you need to retrieve and use the primary datatype of int you can use the [NSString intValue] method you have already used.
Although it's best practice to use a NSNumberFormatter which can be useful for locale based number adjustments and ensuring no invalid characters are present.
NSString *status = [result objectForKey:#"status"];
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
NSNumber * statusNumber = [f numberFromString:status];
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[device setValue:statusNumber forKey:#"status"];
To retrieve the primary datatype when you wish to use the int within your code, simply call the [NSNumber intValue].
NSNumber *statusNumber = [device objectForKey:#"status"];
int statusInt = [statusNumber intValue];
As for the problem you are having with the infinite loop, this is caused by called [... getStatus:atIndexPath:], which contains the method call reloadData, from within [UITableView tableView:cellForRowAtIndexPath:].
This is because reloadData actually calls [UITableView tableView:cellForRowAtIndexPath:].
Therefore your code continuously goes as the following...
Initial UITableView data load -> tableView:cellForRowAtIndexPath: -> getStatus:atIndexPath: -> Server Response -> reloadData -> tableView:cellForRowAtIndexPath: -> getStatus:atIndexPath: -> Server Response -> reloadData -> ...
Unfortunately you cant just force one cell to update, you have to request the UITableView to reload all data using reloadData. Therefore, if possible, you need to adjust your server to return an unique ID for devices so you can adjust only the updated device within your NSManagedObject.
A suggested alteration for the getStatus method could be just to use the serialNumber if this is stored within the NSManagedObject as a key.
- (void) getStatus:(NSString*)serialNumber