Switched from tableview to colelctionview now didselectitematindexpath not called - xcode

So I switched from a UITableView to a UICollectionView. The cells are all loading just fine and look beautiful, but now I can no longer select any of them. The taps are recognized in the logs, but it doesn't take me to the view that it's supposed to take me to anymore.
The only things that changed really are the ones the ones you see commented out in the "cellForItem" section. Everything else is identical to how it was when it was working in the TableView. It's just supposed to take me to the ViewController specified when you tap. Instead the tap is recorded, but it does nothing. Any help would be appreciated.
Also I tried getting rid of the [collectionView deselectItemAtIndexPath:indexPath animated:YES];
...but it didn't make any difference.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PlaceCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell
//UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
// NSArray *topLevelItems = [placeCell instantiateWithOwner:self options:nil];
// cell = [topLevelItems objectAtIndex:0];
Place *p = [_entries objectAtIndex:indexPath.row];
cell.placeName.text = p.PName;
NSLog(#"p:%#",p.PName);
cell.placeName.textColor = [UIColor colorWithRed:3/255.0f green:6/255.0f blue:4/255.0f alpha:1.0f];
NSString *pIcon;
pIcon = typeName;
//NSLog(pIcon);
cell.placeImg.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:p.PImage]]];
NSLog(#"i:%#",p.PImage);
return cell;
}
-(void) collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"Tap Recognized");
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
Place *p = [_entries objectAtIndex:indexPath.row];
DetailPlaceViewController * pvc = [[DetailPlaceViewController alloc] init];
[pvc setPlace:p];
[self.navigationController pushViewController:pvc animated:YES];
}

Well this one was fun...
For some odd reason my navigation controller isn't working... so of course using the navigation controller to load a view wasn't going to work. Simple enough solution... load the view without the navigation controller. I was just going to delete this question, but maybe somebody might be in a similar situation one day:
[self presentViewController:pvc animated:YES completion:nil];

Related

xcode Converting UITableView to UICollectionView (no valid cell)

EDIT: I should specify that this only happens when I attempt to use the UICollectionViewFlowLayout, not when I try to use a custom view. But either way nothing ever shows up on the CollectionView though it was working just fine before I converted from a TableView.)
So I've been trying to convert a UITableView that I had into a UICollectionView. So far so good. But when I try to run the app it gives me the error:
'the collection view's data source did not return a valid cell from -collectionView:cellForItemAtIndexPath: for index path {length = 2, path = 0 - 0}'
I checked all the similar questions and answers here... so in my viewDidLoad I have (tableView is actually a UICollectionView):
UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
[self.tableView registerNib:placeCell
forCellWithReuseIdentifier:CellIdentifier];
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UICollectionView *)tableView numberOfItemsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [_entries count];
//return 5;
}
- (void)tableView:(UICollectionView *)tableView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == [_entries count]-1 && page > 1) {
NSLog(#"load more");
//add footer view loading
if (c_page == page) {
// _tableView.tableFooterView = nil;
}
else
{
c_page++;
[self loadPlace:c_page];
}
}
}
- (UICollectionViewCell *)tableView:(UICollectionView *)tableView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
PlaceCell *cell = (PlaceCell *)[tableView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
//cell = [cellLoader instantiateWithOwner:self options:nil];
NSArray *topLevelItems = [placeCell instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
Place *p = [_entries objectAtIndex:indexPath.row];
cell.placeName.text = p.PName;
NSLog (#"p:%#", p.PName")
cell.placeImg.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:p.PImage]]];
return cell;
}
I went into the xib of the UICollectionViewCell (PlaceCell) and made sure that "Cell" was the reuseidentifier. And I made sure that the datasource and delegate were connected to file's owner in the collectionView.
I also noticed that when I use a custom layout instead of the flow layout (like this one: https://github.com/ShadoFlameX/PhotoCollectionView/blob/master/CollectionViewTutorial/BHPhotoAlbumLayout.m ) it doesn't give me that error... but my collectionview still isn't populated.
So I'm wondering if there's some sort of log I can run or something I can do to figure out what's going wrong. Because I've tried all the solutions I've seen and it hasn't gotten me anywhere.
When you make a cell in a xib file you should register the xib, not the class. Also, when you register either the class or xib (or make the cell in the storyboard), you don't need an if (cell==nil) clause because your cell will never be nil when you dequeue it with dequeueReusableCellWithReuseIdentifier:forIndexPath:. You should delete that clause.
So the problem is: "Switched from UITableView to UICollectionView and no valid cell is being returned." It is really a two part answer. The crux of which is every instance of UITableView...
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 50, self.view.bounds.size.width, self.view.bounds.size.height-50)];
...you want to turn into "CollectionView"
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 50, self.view.bounds.size.width, self.view.bounds.size.height-50)];
Everything that's a "row":
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
...you'll want to turn into an "item."
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
Ultimately I had to delete the following section entirely:
UINib * placeCell = [UINib nibWithNibName:#"Shops" bundle:nil];
//cell = [cellLoader instantiateWithOwner:self options:nil];
NSArray *topLevelItems = [placeCell instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
My best guess is that the Nib was being loaded twice and that Xcode was complaining that the data wasn't being loaded by the original. So getting rid of that second entry got my cells loaded and populated with data. Hope this helps someone.

Memory Warning in UICollectionView (with image)

I am currently working on a UICollectionView with a lot of images. However, it sometimes crashes in this view with memory warning. I am using AFNetworking and UIImageView+AFNetworking category to set image through setImageWithURL: method. One issue can be caching. I am not sure if AFNetworking deals with image caching. Anyway, is there a way to optimize this code in terms of memory management? Or if I am to implement didReceiveMemoryWarning method in this view controller, what can be put in this method? I attach the code for cellForItemAtIndexPath for this collection view.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"RecipeCell" forIndexPath:indexPath];
// setting the image view for the cell using AFNetworking. Does this do caching automatically?
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:6];
if (PRODUCTION) {
[recipeImageView setImageWithURL:[[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_image"] placeholderImage:[UIImage imageNamed:#"default_recipe_picture.png"]];
} else {
[recipeImageView setImageWithURL:[NSString stringWithFormat:#"http://localhost:5000/%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_image"]] placeholderImage:[UIImage imageNamed:#"default_recipe_picture.png"]];
}
// configure the back of the cell. fill all the info.
UITextView *recipeNameView = (UITextView *)[cell viewWithTag:8];
recipeNameView.text = [NSString stringWithFormat:#"%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_name"]];
UILabel *recipeNameLabel = (UILabel *)[cell viewWithTag:2];
recipeNameLabel.text = [NSString stringWithFormat:#"%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_name"]];
NSDictionary *user = [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"user"];
UIButton *chefNameButton = (UIButton *)[cell viewWithTag:3];
[chefNameButton setTitle:[NSString stringWithFormat:#"%# %#", [user objectForKey:#"first_name"], [user objectForKey:#"last_name"]] forState:UIControlStateNormal];
NSMutableArray *missingIngredientsStringArray = [[NSMutableArray alloc] init];
NSArray *missingIngredients = [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"missing_ingredients"];
for (NSDictionary *missingIngredient in missingIngredients) {
[missingIngredientsStringArray addObject:[missingIngredient objectForKey:#"name"]];
}
NSString *missingIngredientsString = [missingIngredientsStringArray componentsJoinedByString:#","];
UITextView *missingIngredientsView = (UITextView *)[cell viewWithTag:4];
missingIngredientsView.text = [NSString stringWithFormat:#"%u Missing Ingredients: %#", missingIngredients.count, missingIngredientsString];
// configure the front of the cell. chef name button and missing ingredients and likes on front view
UIButton *frontNameButton = (UIButton *)[cell viewWithTag:11];
[frontNameButton setTitle:[NSString stringWithFormat:#"%# %#", [user objectForKey:#"first_name"], [user objectForKey:#"last_name"]] forState:UIControlStateNormal];
[frontNameButton sizeToFit];
frontNameButton.frame = CGRectMake(160 - [frontNameButton.titleLabel.text sizeWithFont:[UIFont boldSystemFontOfSize:13]].width - 7, frontNameButton.frame.origin.y, frontNameButton.frame.size.width, frontNameButton.frame.size.height);
UILabel *likesLabel = (UILabel *)[cell viewWithTag:9];
likesLabel.text = [NSString stringWithFormat:#"%# likes", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"likes"]];
UIButton *missingIngredientsButton = (UIButton *)[cell viewWithTag:12];
[missingIngredientsButton setBackgroundImage:[UIImage imageNamed:#"badge_green.png"] forState:UIControlStateSelected];
if (missingIngredients.count == 0) {
missingIngredientsButton.selected = YES;
[missingIngredientsButton setTitle:#"" forState:UIControlStateNormal];
} else {
missingIngredientsButton.selected = NO;
[missingIngredientsButton setTitle:[NSString stringWithFormat:#"%u", missingIngredients.count] forState:UIControlStateNormal];
}
// make back view invisible.
UIView *backView = [cell viewWithTag:1];
UIView *frontView = [cell viewWithTag:5];
frontView.alpha = 1.0;
backView.alpha = 0;
// adding flip gesture recognizers
UIView *flipView1 = [cell viewWithTag:12];
UIView *flipView2 = [cell viewWithTag:1];
UITapGestureRecognizer *flipGestureRecognizer1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flipCell:)];
UITapGestureRecognizer *flipGestureRecognizer2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flipCell:)];
[flipView1 addGestureRecognizer:flipGestureRecognizer1];
[flipView2 addGestureRecognizer:flipGestureRecognizer2];
return cell;
}
[Edit] I attach a screenshot of my Instruments run.
You can see that memory allocation increases as I just push segue and press back button repeatedly. Things that just keep increasing are CFData, CALayer, CABackingStore, UITableView. I doubt these are things that are created after segue, and they are not being released... Please help!
You're probably going to want some sort of image caching strategy to avoid re-downloading images. And UIImageView+AFNetworking category does cache images for you. But you may also have the responses being cached in the in-memory URL cache, which in this case is somewhat redundant.
So you might consider reducing or turning off the in-memory URL cache. I had the issue you're describing and the following reduced my memory issues quite a bit:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
AFNetworking automatically stores images in an NSCache collection, which automatically removes some or all of the images from memory on a low memory warning. AFNetworking is probably not your issue.
In fact, I don't think displaying images is your issue unless you're downloading lots of very large images and displaying them simultaneously. (If this is the case, you should try optimizing your images for display on the device so they don't need to be resized.)
One issue I see is that you are adding a gesture recognizer to the cell every time it comes into the view, but cells are reused, so when a cell comes in again you are adding unnecessary gesture recognizers to it. You could resolve this by subclassing UITableViewCell and assigning the gesture recognizers as properties. You could also resolve this by checking flipView1 and flipView2 to see if they have gesture recognizers attached before adding them. (I'm not sure if this is enough to cause a memory warning though.)
I'd recommend going to Build -> Profile and selecting the Allocations instrument. On the left, select Objective C only, and hide system calls. Then, scroll through your collection view and look at the instrument to see what's taking up all the memory.
UPDATE
Here's a screenshot of the Allocations tool:

Showing Activity indicator while loading sqlite local database

I have a local SQLite Database and need to return a large number of records. It takes a several seconds to load so I want to add an activity indicator. The activity indicator seems to be running as it should but the problem is the pool isn't allowing the arrays to return any value see below.
- (void)viewDidLoad
{
[super viewDidLoad];
activityIndicator = [[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease];
activityIndicator.frame = CGRectMake(0.0, 0.0, 48.0, 48.0);
activityIndicator.center = self.view.center;
[self.view addSubview:activityIndicator];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(loadData) withObject:nil];
}
//What I need to load from SQLITE
-(void)loadData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Path to the database
//CODED HERE FOR DATABASE TO OPEN AND QUERY HERE TO BUILD ARRAYS
NSString *firstField = [NSString stringWithUTF8String:cFirst];
NSString *secondField = [NSString stringWithUTF8String:cSecond];
NSString *thirdField = [NSString stringWithUTF8String:cThird];
[FirstArray addObject:firstField];
[SecondArray addObject:secondField];
[ThirdArray addObject:thirdField];
//Checking to see if records are being added to the arrays
NSString *recordAdded = [NSString stringWithFormat:#"%# - %# - %#", firstField, secondField, thirdField];
NSLog(#"Song: %#", recordAdded);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[activityIndicator stopAnimating];
[pool release];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
NSString *firstValue = [firstArray objectAtIndex:indexPath.row];
NSString *secondValue = [secondArray objectAtIndex:indexPath.row];
NSString *thirdValue = [thirdArray objectAtIndex:indexPath.row];
NSString *details = [NSString stringWithFormat:#"%# - %#", secondValue, thirdValue];
cell.textLabel.text = cellValue;
cell.detailTextLabel.text = details ;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
When I'm looking at the debugger I see that the arrays are being built as the activity indicator is going. The problem is that I think the pool release is releasing my arrays too.
The code returns the query in the table view fine when I don't add the Pool and activity indicator.
Can anyone help to point me in the right direction to not release the arrays if that's what is happening here? Any kind of help would be much appreciated. :-)
---Also---
After looking around more I found that I have to pass the arrays back to the main thread. This is what I gather from searching online.
[self performSelectorOnMainThread:#selector(done:) withObject:cellData waitUntilDone:NO];
How would I go about loading the table with these arrays?
Not sure about your pool releasing the array, your code seems pretty direct. You may want to check whether you are really getting any data from your database.
However, I notice that you stop animating the activity indicator in the background thread. UI activity should be done in the main thread. I just did this coding last night and solved my problem using this thread. Put your stop animating code in the selector specified by performSelectorOnMainThread.
The strings are fine because they are being added to the array.
It looks like your array is being released, but you don't show the code for it.
How are you creating the array itself? If it is autoreleased, it will be deallocated when you drain the pool. You should "retain" it instead of "auotrelease"ing it.
Try to add [tableview reloadData] at the end of the -(void)loadData method

Xcode table view reload continually adds data to the table

I have an rss parser as part of my app code, and it is working fine and loading the rss xml file and populating the tableview fine.
The problem is with a refresh/reload button, which does reload the rss data, but it APPENDS the new data to the table and the table just grows and grows in size.
What the behaviour should do is to clear the old table data and rebuild the table with the new data - so that the table always shows just ONE set of data and doesn't keep growing every time the reload/refresh is pressed.
The table build code is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
cell.textLabel.text = [[stories objectAtIndex: storyIndex] objectForKey: #"date"];
cell.detailTextLabel.text = [[stories objectAtIndex: storyIndex] objectForKey: #"title"];
[cell.textLabel setLineBreakMode:UILineBreakModeWordWrap];
[cell.textLabel setNumberOfLines:0];
[cell.textLabel sizeToFit];
[cell.detailTextLabel setLineBreakMode:UILineBreakModeWordWrap];
[cell.detailTextLabel setNumberOfLines:0];
[cell.detailTextLabel sizeToFit];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
And the reload/refresh button code is:
- (void)reloadRss {
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
UIBarButtonItem * barButton = [[UIBarButtonItem alloc] initWithCustomView:activityIndicator];
[[self navigationItem] setLeftBarButtonItem:barButton];
[barButton release];
[activityIndicator release];
[activityIndicator startAnimating];
[self performSelector:#selector(parseXMLFileAtURL) withObject:nil afterDelay:0];
[newsTable reloadData];
}
I have tried to solve this by adding the line:
if (stories) { [stories removeAllObjects]; }
to the reload section, which I think should work and does clear the table, but the app then crashes the app with an EXC_BAD_ACCESS.
Any ideas or suggestions greatly appreciated!
Actually, have now solved this!
Was due to "autoreleasing" elements of the array, so after clearing this out, they were invalid.
Removing autorelease and just releasing these objects in the final dealloc worked.
The Code EXC_BAD_ACCESS does mean, that you want connect at a variable that does not exists anymore.
In your code example, I can see, the problem to see at the following two lines of code:
[activityIndicator release];
[activityIndicator startAnimating];
You release the activityIdicator befor starting the Animating.
Try to release at the end of the function.
[activityIndicator startAnimating];
[self performSelector:#selector(parseXMLFileAtURL) withObject:nil afterDelay:0];
[newsTable reloadData];
[activityIndicator release];

How to change it back from activity indicator to disclosure in UITableViewCell after pushViewController

How to change it back from activity indicator to disclosure ??
the problem is:
When i didSelectRow, i made disclosure to activity indicator because it will load some picture for the next view controller and It's worked, but i dont have any luck to change it back to disclosure after pushViewController. i already tried to reload the table from the 2ndViewController but it's crashed.
So anybody have a solution for this problem..
Thanks in advance
This is my code in didSelectRow :
-(void)threadStartAnimating:(id)data
{
[activityView startAnimating];
[self.navigationController pushViewController:bdvController animated:YES];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[cell setAccessoryView:activityView];
[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
// Navigation logic -- create and push a new view controller
if(bdvController == nil)
bdvController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:[NSBundle mainBundle]];
AppRecord *appRecord = [self.entries objectAtIndex:indexPath.row];
bdvController.appsRecord = appRecord;
bdvController.HeaderText.text = appRecord.appName;
bdvController.title = appRecord.artist;
[bdvController.tableView reloadData];
}
The best way is to set pointer nil to the accessoryView, example:
cell.accessoryView = nil;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
In my code these operations work fine.

Resources