I have an array of NSNumbers which I would like to use to change the image of a UICollectionView cell if each of those numbers is equal to the indexPath row of the collectionView.
I'm aware that I can use:
if [indexPath row] == 5 {
self.myImage = [UIImage imageNamed:#"120-red.png"];
aCell.imageView.image = self.myImage;
}
for a single value, but how do I do this from an array?
btw. its a dynamic NSMutableArray which is created from purchased products and stored in NSUserDefaults. Any help is much appreciated!
You could save your indexes and image names in a dictionary with the keys being NSNumbers.
NSDictionary* dict = #{#(5):#"120-red.png", #(6):#"130-blue.png"};
Then in your dataSource method
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSString* str = [dict objectForKey:#(indexPath.row)];
if (str) { //str will be nil if the key isn't in the dictionary
image = [UIImage imageNamed: str];
}
else {
//the key wasn't in your dictionary. Do whatever you did before to keep this cell the same
}
}
When you change the data you need to reload it. Either use:
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
To reload only the ones you changed,
OR
- (void)reloadData
To reload the whole collection
Related
I want to create 2 arrays, and populate my table view with that arrays. Then, if user click on a cell, that contain object from first array, i want to perform transition to my detail controller "one". Therefore, if user tap on cell, that contain text from second array, i want to perform segue for detail controller "two".
How to achieve that? I can't put tag on array objects and check it.
Edit: or, if NSDictionary suitable for that case, i could use them instead.
instead of having an array of data elements, you could have an array of MyDataClass, which has attributes for your data, and to identify the source.
You can use a single array to populate the table, and as suggested have different methods for populating the table cell based on the source.
Another way to do this would be create a third array with NSDictionary objects with two keys 'tag' and 'data'. 'tag' key will hold the information about which array and 'data' key will hold data from the array.
NSMutableArray *tableArray = [NSMutableArray arrayWithCapacity:0];
for (id obj in array1) {
[tableArray addObject:#{#"tag":#1, #"data":obj}];
}
for (id obj in array2) {
[tableArray addObject:#{#"tag":#2, #"data":obj}];
}
Then use this new array to populate your table
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
....
id obj = [[tableArray objectAtIndex:indexPath.row] objectForKey:#"data"];
....
}
and on didSelectRowAtIndexpath you can check value for 'tag' key to check whether it is from array1 or array2
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
....
NSNumber *tag = [[tableArray objectAtIndex:indexPath.row] objectForKey:#"tag"];
if (tag.intValue == 1) {
//controller 1
}
else if (tag.intValue == 2) {
//controller 2
}
....
}
There are multiple ways of looking to this problem. Firstly, you have to understand that you can populate a table view with only a single array; however this array can be made from multiple arrays.
Without getting ourselves dirty into multiple data structures that might provide a lot of redundancy than efficiency, a simple way would be to check for the array number in tableView:didSelectRowAtIndexPath:.
Example:
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath
{
<Type> obj = tableViewArray[indexPath.row];
if (obj in array1)
{
// ...
}
else
{
// ...
}
}
The tableViewArray is probably array1+array2.
I was using this example for searching through a UICollectionView: https://github.com/ihomam/CollectionViewWithSearchBar/blob/master/collevtionViewWithSearchBar/CollectionViewController.m
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell
if (self.searchBarActive) {
cell.laName.text = self.dataSourceForSearchResult[indexPath.row];
} else {
cell.laName.text = self.dataSource[indexPath.row];
}
return cell;
}
...only my app is setup a little differently:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PlaceCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
Place *p = [_entries objectAtIndex:indexPath.item];
if (self.searchBarActive) {
cell.placeName.text = self.dataSourceForSearchResult[indexPath.item];
} else {
cell.placeName.text = p.PName;
cell.placeImg.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:p.PImage]]];
}
return cell;
}
So if I just run it the way it is then self.dataSourceForSearchResult[indexpath.item] will return EVERYTHING and I get an error because it's not returning a string...
'Can't use in/contains operator with collection <Place: 0x17d8ad70> (not a collection)'
but what I want it to do is search through the p.PName results. Something like:
self.dataSourceForSearchResult[p.PName indexpath.item]
Having looked through the github code I think that the core of your problem lies somewhere else.
The problem lies here :
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope{
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"self contains[c] %#", searchText];
self.dataSourceForSearchResult = [self.dataSource filteredArrayUsingPredicate:resultPredicate];
}
What this method does is it filters matching items from self.datasource (which I assume you renamed to self.entries, and I hope you also did it here) based on the predicate. The predicate is a little tricky to understand, especially if you have no previous experience with them. Let's break it down :
self - in this context it means the object that will be checked against the condition. This object will be an element of the array.
contains[c] - this is the condition. The self will be checked if it contains something. The [c] means that the check will be case insensitive.
%# - this is the 'something'. It will be replaced with searchText.
This worked in the example because the array contained NSString objects and NSPredicate knows how to check if they contain something (a substring). Since your array consists of Place objects the NSPredicate doesn't know how to check if they contain something (a subplace? :P).
To fix this replace the predicate with this one self.PName contains[c] %#. This one will acces the PName property of each object of array and check whether it contains the substring.
You can read more on NSPredicate on NSHipster and in the docs
As to the second problem, self.dataSourceForSearchResult[indexpath.item] will not in fact return an NSString but a Place object, though not EVERYTHING, but one of the filtered ones. It was actually never reached in your app, as it crashed earlier. No offence, but I think that you don't entirely understand the whole order of things in this code. To break it down :
The view controller is shown and the searchbar is empty so the cells are drawn, based on data in self.datasource (or self.entries as you renamed it). collectionView:cellForItemAtIndexPath: is being called.
User inputs something in the search bar. (this triggers searchBar:textDidChange:).
The matching objects are filtered into self.dataSourceForSearchResult array.
The collection view gets reloaded.
The cells are redrawn (collectionView:cellForItemAtIndexPath: is being called again), but since the searchbar is not empty, we now use only objects from self.dataSourceForSearchResult.
So to make it work your - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; should look like this :
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PlaceCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
Place *p;
// retrieve item from appropriate array
if (self.searchBarActive) {
p = self.dataSourceForSearchResult[indexPath.item];
} else {
p = self.entries[indexPath.item];
}
// populate the cell - we do this regardless of whether the searchbar is active or not
cell.placeName.text = p.PName;
cell.placeImg.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:p.PImage]]];
return cell;
}
I am going crazy trying to get my IOS Searchbar working for the Iphone. I access data from a remote server and populate a content file. I then do a filter which creates a filtered content file. I then do a [self.tableView reloadData()]. It works fine the first time around. Then I change my scope and do another fetch of data from my server and filter it and do another reload. However, the second time the table shows the first 9 items from the previous display rather than the new 9 items from the filtered file. I console display the file count in the filtered file which in this case is 9 in the tableView numberOfRowsInSection: I also display each item going through the cellForRowAtIndexPath. In the cellForRowAtIndexPath I am displaying the correct 9 unfiltered items but they do not show up on the table! The table shows the first 9 items from the old display instead.
My question is doesn't the new data display on the table instead of the old data even though the count is correct? Why am I displaying the correct items on the console but yet the display shows items from the old display. WHat do I need to do to make the new data appear? I know this is pretty hard to comprehend but I am listing some of my code below in hopes that someone can give me a clue on why the table view is not being updated with the latest data.
// This is where I get data back from the server.
self.listContent = [[NSArray alloc] init];
if(_scopeIndex == 0)
self.listContent = [jsonObject objectForKey:#"burials"];
else
if(_scopeIndex == 1)
self.listContent = [jsonObject objectForKey:#"obits"];
else
self.listContent = [jsonObject objectForKey:#"photos"];
if(self.listContent > 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
[self.tableView reloadData];
});
}
Below is where the data is filtered. In this case the unfiltered and filtered file are the same.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope{
/*
Update the filtered array based on the search text and scope.
*/
[self.filteredListContent removeAllObjects];// First clear the filtered array.
/*
Search the listContent for names that match and add to the filtered array.
*/
for (int i = 0; i < [self.listContent count]; i++)
{
NSComparisonResult result = [self.listContent[i][#"LastName"] compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[self.filteredListContent addObject:self.listContent[i]];
}
// }
}
}
This is where I get the table count of filtered items.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (tableView == self.searchDisplayController.searchResultsTableView)
{
NSLog(#"Filtered Name count = %i", [self.listContent count]);
return [self.filteredListContent count];
}
else
{
NSLog(#"Name count = %i", [self.listContent count]);
return [self.listContent count];
}
}
ANd this is where I update the cells in my table:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *kCellID = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellID];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
if(indexPath.row > [self.filteredListContent count] - 1)
return cell;
NSDictionary *burial = [self.filteredListContent objectAtIndex:indexPath.row];
NSString *lastname = burial[#"LastName"];
NSString *firstname = burial[#"FirstName"];
NSString *burialname = [NSString stringWithFormat: #"%#, %#", lastname, firstname];
cell.textLabel.text = burialname;
NSLog(#"Cell name= %# index path=%i", cell.textLabel.text, indexPath.row);
return cell;
}
I changed my logic to go to the server one time to get my content and this time included scope indicators in my content table. This enables me to process scope filters without having to go back to the server for data for a specific scope. Doing this resulted in proper view tables being displayed when changing scope. I would not recommend going to the server ascynchronously whenever the scope changes on the search as it really screws up the view table.
Some things to try:
Maybe you're reloading your tableView too soon, or
Your cellForRowAtIndexPath needs to distinguish between the table views (just as your numberOfRowsInSection does), or
Maybe you don't have all of your delegates set up correctly. The searchBar is used with a UISearchDisplayController, which has 2 delegates: searchResultsDataSource and searchResultsDelegate. Make sure those are set to self (or whatever class handles these).
How are you using searchbar? Logic should be that you have tableview with all data and the you use search bar to filter out matching results and add them to search data array and while searchbar is active you display search data array.
Is
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
used somewhere?
Added following
If you have this function set up you can update search array and reload it using something like
if (self.searchDisplayController.searchResultsTableView != nil)
{
//Updates searchData array
[self searchDisplayController:self.searchDisplayController shouldReloadTableForSearchString:searchBar1.text];
//Updates display
[self.searchDisplayController.searchResultsTableView reloadData];
}
else
{
[_channelsTableView reloadData];
}
It is calling searchDisplayController with current search bar text. When new tableview data comes from server you can activate searchDisplayController to refresh search results array and then it is possible to refresh the display.
I have 2 UITableViewControllers in my project.
The problem I am having is that I am getting blank cell entries in the tableView opposite to the tableView where the data is entered.
I can't seem to figure out why this is the case.
It's creating blank rows in this tableView even though the information is from the other UITableViewController.
Here's the main tableView part from the one of the 2 UITableViewControllers:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.diaryoptions count]);
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
return [self.diaryoptions count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
Data2 *diary = [self.diaryoptions objectAtIndex:indexPath.row];
cell.textLabel.text = diary.diaryname;
cell.detailTextLabel.text = diary.diaryWeight;
return cell;
}
And from other UITableViewController:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.addedSpaceObjects count]);
// Return the number of sections.
if ([self.addedSpaceObjects count]) {
return 2;
}
else {
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
if (section == 1) {
return [self.addedSpaceObjects count];
}
else {
return [self.recipes count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Josh";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
else {
// Configure the cell...
Data *recipe = [self.recipes objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
return cell;
}
Here is the full project on GitHub. https://github.com/josher32/Plant-Diet
Appreciate any help anyone can offer!
Ok, so I checked out the app and I'll try my best to explain the problem as precisely as I can to cover it adequately.
Firstly, the classes in question are:
RecipesTableTableViewController
AddRecipeViewController
Data
DiaryTableViewController
AddDiaryViewController
Data2
Secondly, we'll need to look into your
#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
AddRecipeViewController
So... AddRecipeViewController basically creates a Data object that is kept in an array and eventually stored in NSUserDefaults under the key name Added Space Objects Array.
Great!! So you now have got recipe related stuff in some Data object.
AddDiaryViewController
Same thing here.
AddDiaryViewController creates a Data2 object that is eventually stored in NSUserDefaults under the same key name Added Space Objects Array.
But before storing this, you're taking the old value of the key Added Space Objects Array, which is an array, and adding a new object to it before placing it back into NSUserDefaults.
But now... this array will now have a combination of Data as well as Data2 objects!
RecipesTableTableViewController
When we come here, things get real.
- (void)viewDidLoad
{
//...
NSArray *myRecipeAsPropertyLists = [[NSUserDefaults standardUserDefaults] arrayForKey:ADDED_SPACE_OBJECTS_KEY];
for (NSDictionary *dictionary in myRecipeAsPropertyLists) {
Data *spaceObject = [self spaceObjectForDictionary:dictionary];
[self.addedSpaceObjects addObject:spaceObject];
}
}
Since we already realized that self.addedSpaceObjects can contain Data as well as Data2 objects, in the case whendictionary is containing stuff specific to type Data2, spaceObjectForDictionary will not be able to translate it properly to the required Data object.
We're expecting name, title, ingredients, directions but we're getting diaryentry, diaryname,diaryWeight.
So (in this scenario):
The values of name, title, ingredients, directions will be nil
The section-row count will be incorrect because it will give count of both Data as well as Data2 objects (and we don't care about Data2 objects in the RecipesTableTableViewController class... right?... well anyways, I assumed)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
//...
}
We see recipe.name is nil, for some indexPaths, ergo blank rows and vice versa in DiaryTableViewController.
Solution:
Firstly, I wouldn't recommend NSUserDefaults for your purposes but anyways...
Basically, don't use a single #"Added Space Objects Array" key for your NSUserDefaults stuff.
I'd suggest you use 2 separate keys.
//replace
//#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
//with
#define ADDED_SPACE_OBJECTS2 #"RecipeEntries" //in RecipesTableTableViewController
//and
#define ADDED_SPACE_OBJECTS2 #"DiaryEntries" //in DiaryTableViewController
Basically, segregate the entries instead of mixing them up under a single key name.
This seems like the quickest way to solve your problem without changing your logic.
I am trying to get a URL from a cell. To do this, I am using NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; and then would like to do something like NSURL *url = self.finalURL[indexPath.row] but because indexPath.row is only for Arrays, this doesn't work. Is there a way to achieve the same thing as indexPath.row but for objects not in an array.
Here is how I am saving the url:
cell.finalURL = self.finalURL;
A cell doesn't have a URL, unless you create a subclass of the cell and add that property to is. Conventionally, you will have an array of objects, strings, dictionaries, etc., and that is your tableView's data source.
If I had an array with three NSURLs in it called myArray that contained google, amazon, and bing, and I wanted to display three cells with the respective labels matching the items in the array, I would implement the following code:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// we only want a single section for this example
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// this tells the tableView that you will have as many cells as you have items in the myArray array
return myArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// first we try to reuse a cell (if you don't understand this google it, there's a million resources)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
// if we were unable to reuse a cell
if (cell == nil) {
// we want to create one
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
// here is where we do ANY code that is generic to every cell, such as setting the font,
// text color, etc...
cell.textLabel.textColor = [UIColor redColor];
}
// here is where we do ANY code that is unique to each cell - traits that are based on your data source
// that you want to be different for each cell
// first get the URL object associated with this row
NSURL *URL = myArray[indexPath.row];
// then set the text label's text to the string value of the URL
cell.textLabel.text = [URL absoluteString];
// now return this freshly customized cell
return cell;
}
That, along with the rest of the default tableview code and setting up the array itself, results in the following:
When a user taps on a cell you can access the URL in the array and do something with it like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// first deselect the row so it doesn't stay highlighted
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// get the URL associated with this cell by using indexPath.row as an index on your
// data source array, if you tapped the first cell, it will give you back the first URL in your array...
NSURL *selectedURL = myArray[indexPath.row];
// do something with that URL here...
}
Think of your table view's data source as a bunch of little cubbies. You can create the data source in a million different ways, but once you have it you basically take the items and place them in numbered cubbies. Your table view create's itself based on what's in those cubbies, so to make the first cell it looks in the first cubbie, and so on, and later on when a user selects a cell from that tableview, all the table view does is tell you the cubbie number that was selected, and it's your job to use that information to retrieve the data from that specific cubbie and do what you need to with it. Hope that helps!