Call of scrollviewdidscroll method in uitableview? - ios

I’m building an article reading app.I’m using AFNetworking third party library to fetch JSON data into the UITableView.
Let say Json link is www.example.com&page=1 gives 1-10 articles and www.example.com&page=2 gives11-20 articles and so on.
I have implemented pagination and scrollViewDidScroll method means when user scroll it gives next ten article.
I’m facing an issue when app launch and UITableView load scrollViewDidScroll method called three times but expected call once.
I’m using increment variable for pagination in scrollViewDidScroll method as i say it call three time and x value goes to 3 and give 30 articles.
When user scroll again it gives next 30 articles.i’m unable to figure out why scrollViewDidScroll method called three times when app is launched.
this is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
tempJson = [[NSMutableArray alloc] init];
[self loadNinjas];
}
- (void)loadNinjas {
NSString *jsonLink=[NSString stringWithFormat:#"www.example.com&page=%d",x];
NSURL *url = [[NSURL alloc] initWithString:jsonLink];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)responseObject;
for (NSDictionary *dic in jsonArray) {
Json *json = [[Json alloc] initWithDictionary:dic];
[tempJson addObject:json];
}
self.jsons = [[NSArray alloc] initWithArray:tempJson];
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
[operation start];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.jsons.count ;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Cellidentifier1 = #"ysTableViewCell";
ysTableViewCell *cell1 = [tableView
dequeueReusableCellWithIdentifier:Cellidentifier1 forIndexPath:indexPath];
cell1.TitleLabel1.text = [self.jsons[indexPath.row] title];
cell1.AuthorLabel1.text = [self.jsons[indexPath.row] author];
[cell1.ThumbImage1 setImageWithURL:[NSURL URLWithString:
[self.jsons[indexPath.row] a_image]]];
return cell1;}
- (void)scrollViewDidScroll: (UIScrollView*)scroll {
CGFloat currentOffset = scroll.contentOffset.y;
CGFloat maximumOffset = scroll.contentSize.height - scroll.frame.size.height;
self.tableView.contentInset = UIEdgeInsetsMake(65, 0, 0, 0);
if (maximumOffset - currentOffset <= -60.0) {
x++;
[self loadNinjas];
[self.tableView addInfiniteScrollingWithActionHandler:^{
}];
[self.tableView reloadData];
}
}

- (void)scrollViewDidScroll: (UIScrollView*)scroll
gets called a cuple of times while scrolling
You should better use:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
OR
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate

Related

AFNetworking tutorial of raywenderlich not displaying data on table cell

I am learning AFNetworking using,
https://www.raywenderlich.com/59255/afnetworking-2-0-tutorial
But, For json part I am not getting data displayed on cell that displyed in this tutorial.
I am getting response in json format but after that nothing is displayed in cells.
static NSString * const BaseURLString = #"http://www.raywenderlich.com/demos/weather_sample/";
#interface WTTableViewController ()
#property(strong) NSDictionary *weather;
#end
#implementation WTTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.toolbarHidden = NO;
[self.tableView setDelegate:self];
self.tableView.dataSource=self;
// 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.
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"WeatherDetailSegue"]){
UITableViewCell *cell = (UITableViewCell *)sender;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
WeatherAnimationViewController *wac = (WeatherAnimationViewController *)segue.destinationViewController;
NSDictionary *w;
switch (indexPath.section) {
case 0: {
w = self.weather.currentCondition;
break;
}
case 1: {
w = [self.weather upcomingWeather][indexPath.row];
break;
}
default: {
break;
}
}
wac.weatherDictionary = w;
}
}
#pragma mark - Actions
- (IBAction)clear:(id)sender
{
self.title = #"";
self.weather = nil;
[self.tableView reloadData];
}
- (IBAction)jsonTapped:(id)sender
{
NSString *string = [NSString stringWithFormat:#"%#weather.php?format=json", BaseURLString];
NSURL *url = [NSURL URLWithString:string];
//NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:string parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Weather"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(!self.weather)
return 1;
switch (section) {
case 0: {
NSLog(#"return 1");
return 1;
}
case 1: {
NSArray *upcomingWeather = [self.weather upcomingWeather];
NSLog(#"return upcomingWeather");
return [upcomingWeather count];
}
default:
return 0;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{NSLog(#"tableView");
static NSString *CellIdentifier = #"WeatherCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *daysWeather = nil;
switch (indexPath.section) {
case 0: {
daysWeather = [self.weather currentCondition];
NSLog(#"currentCondition");
break;
}
case 1: {
NSArray *upcomingWeather = [self.weather upcomingWeather];
daysWeather = upcomingWeather[indexPath.row];
NSLog(#"upcomingWeather");
break;
}
default:
break;
}
cell.textLabel.text = [daysWeather weatherDescription];
NSLog(#"textLabel = %#", cell.textLabel.text);
// Configure the cell...
return cell;
}
I have this code in my table view controller.
This methods are used in delegate method in above code :
- (NSDictionary *)currentCondition
{
NSDictionary *dict = self[#"data"];
NSArray *ar = dict[#"current_condition"];
return ar[0];
}
- (NSArray *)upcomingWeather
{
NSDictionary *dict = self[#"data"];
return dict[#"weather"];
}
I am getting response in json formate but cells are empty.
Please refer tutorial if anything is missing here.
Thank you.
In success of data received you just printing data.
you have to store that data in dictionary and reload tableview.
Use below code:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:string parameters:nil progress:nil
success:^(NSURLSessionTask *task, id responseObject) {
self.weather = (NSDictionary *)responseObject; //add this line
[self.tableView reloadData]; //add this line
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Weather"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
You are missing following code from success block
self.weather = (NSDictionary *)responseObject;
self.title = #"JSON Retrieved";
[self.tableView reloadData];
Just check the data existence inside weather dict once before you loaded into tableview and after completion of getting data from server just call reloadData method of uitableView so it will load data received by your code later.

Download Images from a server to display on the CollectionView

I am working on a product application where user could sell/buy. This application is based on collection view. Collection view has collection cell where it displays product image thumbnail.
The following code gets products images from the server and it waits to download all images and then display them in the cells. The following code works but user is waiting 10-20 seconds to see all products. Is there a better way to handle ?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
[self loadFromURL];
dispatch_async(dispatch_get_main_queue(), ^{
});
});
}
-(void)loadFromURL {
NSURL *url = [NSURL URLWithString:#"http://myURL/productAll.php"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
pElements= (NSMutableArray *)responseObject;
[collectionView reloadData];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Product" message:[error localizedDescription]delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alertView show];
}];
[operation start];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return pElements.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ProductCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
cell.backgroundColor=[UIColor whiteColor];
NSString *arrayResult = [[pElements objectAtIndex:indexPath.row] objectForKey:#"image"];
NSData *data = [[NSData alloc] initWithBase64EncodedString:arrayResult options:NSDataBase64DecodingIgnoreUnknownCharacters];
cell.productImage.image = [[UIImage alloc] initWithData:data];
cell.productImage.layer.cornerRadius = 10;
cell.productImage.clipsToBounds = YES;
return cell;
}
You have a response from the server in which the image data for all of the images is base-64 encoded in the response. This means that the response is likely very large and won't be shown to the user until everything is downloaded.
Instead, you might consider refactoring your server code to not include the image data in base-64 format, but rather to just include a URL (or some identifier) that can be used to retrieve the image later. Your response should be much smaller and should be able to be processed much more quickly.
Then, when cellForItemAtIndexPath is called, rather than extracting the image data out of the original response, you lazily (and asynchronously) request the image for the cell. AFNetworking provides a nice UIImageView category in UIImageView+AFNetworking that asynchronously retrieves images from network source. (And using this category gets you out of the weeds of lots of subtle issues when doing asynchronous image retrieval.)
By the way, if your images are of varying sizes, you might want to include the dimensions of the image in the original request so that the cells and their image views can be appropriately sized up front, rather than resizing them as the images are retrieved.
--
A couple of observations:
You don't need to dispatch [self loadFromURL] to a background queue, as that's already asynchronous. And I'd probably use GET of the request
You can't just cast responseObject to NSMutableArray, because it probably is not mutable. You really should use NSArray or use mutableCopy if you really need it to be mutable.
You're doing some cell configuration in cellForItemAtIndexPath. Most of that (clipping, background color, etc.) can be done right in IB, so I would do it there rather than doing it programmatically. The only thing you might need to do programmatically is the rounding of the corners (and, even that, I'd probably do with a IBDesignable subclass, though that's beyond the scope of this question).
Thus, assuming (a) your array has a new property called imageURL which is the URL of the image; and (b) the cell has a fixed sized image view, you could do something like:
#interface ViewController ()
#property (nonatomic, strong) AFHTTPRequestOperationManager *manager;
#property (nonatomic, strong) NSArray *pElements;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.manager = [AFHTTPRequestOperationManager manager];
[self loadFromURL];
}
-(void)loadFromURL {
NSString *urlString = #"http://myURL/productAll.php";
[self.manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
self.pElements = responseObject;
[self.collectionView reloadData];
} failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Product" message:[error localizedDescription]delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alertView show];
}];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.pElements.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ProductCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
NSString *imageURL = self.pElements[indexPath.row][#"imageURL"];
[cell.productImage setImageWithURL:[NSURL URLWithString:imageURL]];
cell.productImage.layer.cornerRadius = 10;
return cell;
}
#end

Implementing pagination in UITableView with scrollViewDidScroll method?

I’m building an article reading app.I’m using AFNetworking third party library to fetch JSON data into the UITableView.
Let say Json link is www.example.com&page=1 gives 1-10 articles and www.example.com&page=2 gives11-20 articles and so on.
I have implemented pagination and scrollViewDidScroll method means when user scroll it gives next ten article.
I’m facing an issue when app launch and UITableView load scrollViewDidScroll method called three times but expected call once.
I’m using increment variable for pagination in scrollViewDidScroll method as i say it call three time and x value goes to 3 and give 30 articles.
When user scroll again it gives next 30 articles.i’m unable to figure out why scrollViewDidScroll method called three times when app is launched.
this is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
tempJson = [[NSMutableArray alloc] init];
[self loadNinjas];
}
- (void)loadNinjas {
NSString *jsonLink=[NSString stringWithFormat:#"www.example.com&page=%d",x];
NSURL *url = [[NSURL alloc] initWithString:jsonLink];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)responseObject;
for (NSDictionary *dic in jsonArray) {
Json *json = [[Json alloc] initWithDictionary:dic];
[tempJson addObject:json];
}
self.jsons = [[NSArray alloc] initWithArray:tempJson];
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
[operation start];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.jsons.count ;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Cellidentifier1 = #"ysTableViewCell";
ysTableViewCell *cell1 = [tableView
dequeueReusableCellWithIdentifier:Cellidentifier1 forIndexPath:indexPath];
cell1.TitleLabel1.text = [self.jsons[indexPath.row] title];
cell1.AuthorLabel1.text = [self.jsons[indexPath.row] author];
[cell1.ThumbImage1 setImageWithURL:[NSURL URLWithString:
[self.jsons[indexPath.row] a_image]]];
return cell1;}
- (void)scrollViewDidScroll: (UIScrollView*)scroll {
CGFloat currentOffset = scroll.contentOffset.y;
CGFloat maximumOffset = scroll.contentSize.height - scroll.frame.size.height;
self.tableView.contentInset = UIEdgeInsetsMake(65, 0, 0, 0);
if (maximumOffset - currentOffset <= -60.0) {
x++;
[self loadNinjas];
[self.tableView addInfiniteScrollingWithActionHandler:^{
}];
[self.tableView reloadData];
}
}
This is a simple code that initializes the tableView with 50 cells and as the user scrolls down the page, adds 20 new cells to the tableView every time it reaches the cell which is 10 cells above the end of the table.
int i;
int lastSeen;
- (void)viewDidLoad {
[super viewDidLoad];
i = 50;
lastSeen = 0;
}
#pragma mark - Table view data source
- (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 i;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell" forIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:#"%ld", (long)indexPath.row];
lastSeen = (lastSeen < indexPath.row) ? indexPath.row : lastSeen;
return cell;
}
- (void)scrollViewDidScroll: (UIScrollView*)scroll {
if (lastSeen >= (i - 10)) {
i += 20;
//load new data here.
[self.tableView reloadData];
}
}

Loading imageviews with AFNetworking via JSON hierarchy and objectForKey

Very new to AFNetworking, but after many SO questions... I've concluded that this is the solution to my issue.
I've got a hierarchy of JSON data loading my TableViewController cells dynamically. All of my text strings are loading appropriately but the image from my URL is not making it to my imageview.
Before utilizing the AFNetworking library, I had the images loaded, but I was experiencing some issues with the photos not staying loaded when I scrolled away and returned (they had to load again.)
What am I missing to get my images in there?
TableViewController.m
-(void)viewDidLoad {
[super viewDidLoad];
// Set the side bar button action. When it's tapped, it'll show up the sidebar.
siderbarButton.target = self.revealViewController;
siderbarButton.action = #selector(revealToggle:);
// Set the gesture
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
self.tableView.backgroundColor = [UIColor whiteColor];
self.parentViewController.view.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1];
self.navigationItem.titleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"logo_app_white.png"]];
NSURL *myURL = [[NSURL alloc]initWithString:#"http://domain.com/json2.php"];
NSData *myData = [[NSData alloc]initWithContentsOfURL:myURL];
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:&error];
[tableView reloadData]; // if tableView is unidentified make the tableView IBOutlet
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return jsonArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NeedCardTableViewCell *cell = (NeedCardTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"needCard"];
if (cell == nil)
{
cell = [[NeedCardTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"needCard"];
}
NSDictionary *needs = jsonArray[indexPath.row]; // get the data dict for the row
cell.textNeedTitle.text = [needs objectForKey: #"needTitle"];
cell.textNeedPoster.text = [needs objectForKey: #"needPoster"];
cell.textNeedDescrip.text = [needs objectForKey: #"needDescrip"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"userImage" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
_imageProfPic.image = responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return cell;
}
Have you tried using the AFNetworking+UIImageView category? You can then call:
[_imageProfPic setImage:[NSURL URLWithString:#"http://myimageurl.com/imagename.jpg"]];
This will make a request and then set the returned image to your UIImageView's UIImage without you having to do anything else. You should also consider initializing NSURLCache in your AppDelegate:
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:10 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:cache];
Take a look at NSHipster's run down on NSURLCache. This will help reload images, and all your requests, much faster the second time around. This is increasingly important when dealing with images and tables.
Manage to figure this one out with the use of this tutorial:
Networking Made Easy with AFNetworking
I used the final snippet of code to get my desired result:
NSURL *url = [[NSURL alloc] initWithString:[movie objectForKey:#"artworkUrl100"]];
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholder"]];
I cut the second line of his code because I already have an if statement in my PHP/JSON
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NeedCardTableViewCell *cell = (NeedCardTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"needCard"];
if (cell == nil)
{
cell = [[NeedCardTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"needCard"];
}
NSDictionary *needs = jsonArray[indexPath.row]; // get the data dict for the row
cell.textNeedTitle.text = [needs objectForKey: #"needTitle"];
cell.textNeedPoster.text = [needs objectForKey: #"needPoster"];
cell.textNeedDescrip.text = [needs objectForKey: #"needDescrip"];
NSURL *url = [[NSURL alloc] initWithString:[needs objectForKey:#"userImage"]];
[cell.imageProfPic setImageWithURL:url];
return cell;
}
It worked like a charm, and the tutorial was pretty helpful since I'm a rookie with AFNetworking.

AFNetworking EXC_BAD_ACCESS (code=2, address=0x0) in downloadTask

I have an App that compiles a list of customer specific reports for a customer after login. These reports are showing up and I have put a "view" button which is supposed to download the PDF file, and bring it up to view within the app.
At this stage, it looks as though I have a memory issue when the view button is pressed, and I am not sure how to find where the issue is. HEre is my code:
#import "reportsTestViewController.h"
#import "ReportsDataObject.h"
#import "Session.h"
#import "AFNetworking.h"
#interface reportsTestViewController ()
#end
#implementation reportsTestViewController
#synthesize response;
#synthesize myDataIvar;
#synthesize viewReportPressed;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
reportsTable.delegate = self;
reportsTable.dataSource = self;
self.sections = [[NSMutableDictionary alloc] init];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
#pragma mark NSURLConnection Delegate Methods
//
//Create your request pointing to the test page
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.tesg.com.au/allCustBuild.php"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//initialize it when you create your connection
if (connection){
self.myDataIvar = [[NSMutableData alloc] init];
}
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[self.myDataIvar setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.myDataIvar appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Connection Failed: %#", error.userInfo);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
//this is where you would parse the data received back from the server
NSString *responseString = [[NSString alloc] initWithData:self.myDataIvar encoding:NSUTF8StringEncoding];
NSLog(#"Received Data: %#",responseString);
[self setupReportsFromJSONArray:self.myDataIvar];
}
-(void)connectionWasASuccess:(NSData *)data{
[self setupReportsFromJSONArray:data];
}
-(void)setupReportsFromJSONArray:(NSData*)dataFromReportsArray{
BOOL found;
NSError *error;
// NSMutableArray *reportsArray = [[NSMutableArray alloc] init];
NSArray *arrayFromServer = [NSJSONSerialization JSONObjectWithData:dataFromReportsArray options:0 error:&error];
if(error){
NSLog(#"error parsing the json data from server with error description - %#", [error localizedDescription]);
}
else {
reportsArray = [[NSMutableArray alloc] init];
for(NSDictionary *eachReport in arrayFromServer)
{
ReportsDataObject *report = [[ReportsDataObject alloc] initWithJSONData:eachReport];
[reportsArray addObject:report];
NSString *c = [[eachReport objectForKey:#"title"] substringToIndex:3];
found = NO;
for (NSString *str in [self.sections allKeys])
{
if ([str isEqualToString:c])
{
found = YES;
}
}
if (!found)
{
[self.sections setValue:[[NSMutableArray alloc] init] forKey:c];
}
}
}
NSLog(#"Array Populated");
NSLog(#"%u reports found",reportsArray.count);
//Now you have your reportsArray filled up with all your data objects
for (NSDictionary *eachReport in arrayFromServer)
{
[[self.sections objectForKey:[[eachReport objectForKey:#"title"] substringToIndex:3]] addObject:eachReport];
}
// Sort each section array
for (NSString *key in [self.sections allKeys])
{
[[self.sections objectForKey:key] sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES]]];
}
[reportsTable reloadData];
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)viewReportPressed:(UIButton*)button {
NSLog(#"Button successfully Pressed");
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://tesg.com.au/portal/media/reports/1367365180_367 Collins Passive April 2013.pdf"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *theResponse) {
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[theResponse suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSUInteger count = [[self.sections allKeys] count];
NSLog(#"Number of sections: %d", count);
return count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
//We check against table to make sure we are displaying the right number of cells
// for the appropriate table. This is so that things will work even if one day you
//decide that you want to have two tables instead of one.
{
NSUInteger count = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section]] count];
NSLog(#"Number of rows in section: %d", count);
return count;
}
}
//- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
//return [[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
// }
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//The beauty of this is that you have all your data in one object and grab WHATEVER you like
//This way in the future you can add another field without doing much.
NSUInteger count = [[self.sections allKeys] count];
if(count == 0){
cell.textLabel.text = #"no reports to show";
}
else{
NSDictionary *Reports = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];
cell.textLabel.text = [Reports objectForKey:#"title"];
cell.detailTextLabel.text = [Reports objectForKey:#"building"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(viewReportPressed:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"View" forState:UIControlStateNormal];
button.frame = CGRectMake(180.0f, 5.0f, 150.0f, 30.0f);
[cell addSubview:button];
// in the future you can grab whatever data you need like this
//[currentReport buildingName], or [currentReport reportName];
}
return(cell);
}
#end
When the view button is pressed in any of the cells, I get the NSLog saying 'button successfully pressed' but it crashes immediately after with error
[downloadTask resume]; Thread 1:EXC_BAD_ACCESS (code=2, address=0x0)
Any ideas how to fix this?
If you get a EXC_BAD_ACCESS with an address of 0x0, that generally means that a nil value was used in a context where nil is not permitted.
In this case, it is a result of the fact that your URL string is not valid. Spaces are not allowed. Thus your resulting URL variable is nil, which will result in the behavior you describe. You probably want to percent escape the URL string, e.g.:
NSString *URLString = #"http://tesg.com.au/portal/media/reports/1367365180_367 Collins Passive April 2013.pdf";
NSURL *URL = [NSURL URLWithString:[URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
To identify this sort of exception in the future, you can use exception breakpoints, which will often help identify the offending line of code. Or just carefully review your code code for situations where you may have unintentionally passed a nil value to a method that does not accept it.
If, however, you ever get a EXC_BAD_ACCESS with a value other than 0x0, that is often a result of an attempt to use a previously deallocated object. (Not always, but frequently. Fortunately, ARC has made this sort of problem less common.) Clearly, this is not the case here, but you asked how one would identify a situation where you attempt to use a previously deallocated object.
To diagnose that situation, you would generally turn on "zombies" (see Running Your Application with Diagnostics). By the way, once you finish your diagnostics with zombies, make sure you turn them back off, as you don't want to keep zombies turned on (especially in a production app).
Anyway, in this case, because the address was 0x0, it wasn't a result of a deallocated object, but rather the problem was the incorrect usage of a nil value, caused by the invalid URL.

Resources