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;
}
Related
I want to call didSelectItemAtIndexPath: for particular index path but I can't call it programmatically in cellForItemAtIndexPath because collection is not yet ready, I will get cell as nil. Do we have any delegate method or any other UIView method that is called after collection view is ready?
I have tried willDisplayCell: but it is not made for relevant work, couldn't find anything else.
I want to call didSelectItemAtIndexPath:
Don't. This is a delegate method. It is called by the runtime when the user selects an item. You must never call this yourself.
You have to do it programmatically utilising your manual logics. :)
The underlying concept is that get the indexes of selected cells and reload those specific cells only.
Declare a global var
NSMutableArray array_indexpath;
in your did select method add indexes of selected cells.
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
array_indexpath=[[NSMutableArray alloc]init];
[array_indexpath addObject:indexPath];
[self.myCollectionView reloadItemsAtIndexPaths:array_indexpath];
}
and in your cell for cellForItemAtIndexPath method check the indexes and reload it as required.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ShubhCalendarCollectionViewCell *cell =[collectionView dequeueReusableCellWithReuseIdentifier:#"ShubhCalendarCollectionViewCell" forIndexPath:indexPath];
cell.backgroundColor=[UIColor clearColor];
if (array_indexpath.count !=0)
{
for (int i=0; i<[array_indexpath count]; i++)
{
if ([array_indexpath objectAtIndex:i] == indexPath)
{
cell.backgroundColor=[UIColor greenColor];
}
}
}
return cell;
}
Hope it helps.. Happy Coding.. :)
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
I have a test application I have made for a part of my workflow. What I am trying to achieve is a fancy way of showing the user what they are typing for a Word styled game.
At the moment this is the approach but there could be an easier/better route. I have a UITextField which is not shown to the user and the keyboard is shown on viewDidLoad. What I am trying to have happen is each time a letter is pressed on the keyboard a new Tile showing the letter capitalised is added to the screen area above i.e. 'W', then another letter would mean another tile added i.e. 'I' next to the previous...
I have setup a UICollectionView and custom cell with a label in, that is all. The VC is the dataSource of the UICollectionView. The UITextField also has its delegate set to the self (the VC).
I cannot work out how to have the tiles (cells) created each letter.
#pragma mark -
#pragma mark - Key board delegate methods
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSLog(#"%s",__PRETTY_FUNCTION__);
NSString *lastLetterTyped = [textField.text substringFromIndex:[textField.text length] - 1];
[self.wordArray addObject:lastLetterTyped];
[self.tileCollectionView reloadData];
return YES;
}
#pragma mark -
#pragma mark - Collection View Data Source Methods
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 3;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// we're going to use a custom UICollectionViewCell, which will hold an image and its label
//
WordCVCell *cell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
// make the cell's title the actual NSIndexPath value
NSString *lastLetter = [self.typedWord substringFromIndex:[self.typedWord length] - 1];
cell.label.text = lastLetter;
return cell;
}
Your
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 3;
}
Should read as
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
[self.wordArray count];
}
This will trigger the following the same number of times as there are objects in your array.
-(UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// we're going to use a custom UICollectionViewCell, which will hold an image and its label
//
WordCVCell *cell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
// make the cell's title the actual NSIndexPath value
NSString *lastLetter = [self.wordArray objectAtIndex:indexPath.row];
cell.label.text = lastLetter;
return cell;
}
You need to have a NSMutableArray that you will add to each time the user types a character. For that you need to hook up your controller to UITextfieldDelegate. After that each time you add to this array you need to call [collectionView reloadData] and your number of items will be [myMutableCharacterArray count];
So basically each time a user types a letter, add it to a mutable array and call [collectionView reload data] to refresh the collectionview.
I am just a beginner. i want to display image which is stored in database in a collection view cell. I have already created database and collection view. my code is as follow,
MyDatabase *data;
data=[MyDatabase new];
imagearray=[data OpenMyDatabase:#"SELECT pic_name FROM exterior" :#"pic_name"];
so my question is how can i display images ? let me know the way/code
thanks in advance
You can use following code for display UIImage grid. Add UICollectionView for your xib file. Don't forget to set the delegate in collection.
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return noOfItem/ noOfSection;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return noOfSection;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:identifier
forIndexPath:indexPath];
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100];
recipeImageView.image = [imageArray objectAtIndex:
(indexPath.section * noOfSection + indexPath.row)];
return cell;
}
In your xib file add UIImageView to your CollectionViewCell and change it tag value to 100.
Please go through below links.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UICollectionView_class/Reference/Reference.html
Above link is from Apple developer site. It has all details of UIcollectionview and tutorials related it. Below URL is also having sample tutorial, which will help you a lot.
http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12
You have images array retrieved from database. Now it will act as data source for collection view. You need to form UICollectionViewCell which will have those images accordingly. Please study above links, you will get to know.
I've been trying to add simple Search functionality to a TableViewController in my app. I followed Ray Wenderlich's tutorial. I have a tableView with some data, I added the search bar + display controller in storyboard, and then I have this code:
#pragma mark - Table View
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"BreedCell" forIndexPath:indexPath];
//Create PetBreed Object and return corresponding breed from corresponding array
PetBreed *petBreed = nil;
if(tableView == self.searchDisplayController.searchResultsTableView)
petBreed = [_filteredBreedsArray objectAtIndex:indexPath.row];
else
petBreed = [_breedsArray objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = petBreed.name;
return cell;
}
#pragma mark - Search
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[_filteredBreedsArray removeAllObjects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains[c] %#",searchString];
_filteredBreedsArray = [[_breedsArray filteredArrayUsingPredicate:predicate] mutableCopy];
return YES;
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
// Tells the table data source to reload when scope bar selection changes
[_filteredBreedsArray removeAllObjects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains[c] %#",self.searchDisplayController.searchBar.text];
_filteredBreedsArray = [[_breedsArray filteredArrayUsingPredicate:predicate] mutableCopy];
return YES;
}
The standard stuff, but when I enter text in the search bar it crashes every time with this error:
2013-01-07 19:47:07.330 FindFeedo[3206:c07] *** Assertion failure in -[UISearchResultsTableView dequeueReusableCellWithIdentifier:forIndexPath:], /SourceCache/UIKit_Sim/UIKit-2372/UITableView.m:4460
2013-01-07 19:47:07.330 FindFeedo[3206:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier BreedCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
I understand that in iOS 6 the handling and dequeueing system for cells changed, and also that the search uses a different tableView, so I thought the problem was that the search tableView with the filtered results didn't know about the cell, so I put this in my viewDidLoad:
[self.searchDisplayController.searchResultsTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"BreedCell"];
And voila! It worked... Only the first time you search. If you go back to the original results and search again, the app crashes with the same error. I thought about maybe adding all the
if(!cell){//init cell here};
stuff to the cellForRow method, but doesn't that go against the whole purpose of having the dequeueReusableCellWithIdentifier:forIndexPath: method? Anyway, I'm lost. What am I missing? Help, please. Thank you in advance for all your time (:
Alex.
Try using self.tableView instead of tableView in dequeueReusableCellWithIdentifier:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"BreedCell"];
//Create PetBreed Object and return corresponding breed from corresponding array
PetBreed *petBreed = nil;
if(tableView == self.searchDisplayController.searchResultsTableView)
petBreed = [_filteredBreedsArray objectAtIndex:indexPath.row];
else
petBreed = [_breedsArray objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = petBreed.name;
return cell;
}
This code works pretty well
Note
If you have custom height cells, do not use
[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Use this instead
[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
The reason why it worked great on first run but then crashed if you exited the results table and went back in for another search is because the Search Display Controller is loading a new UITableView each time you enter search mode.
By search mode I mean, you've tapped the textfield and you've began to type, at which point a table view is generated to display results, exiting this mode it achieved by hitting the cancel button. When you tap the textfield the second time and begin typing again - this is entering "search mode" for the second time.
So in order to avoid the crash you should register the cell class for the table view to use in the searchDisplayController:didLoadSearchResultsTableView: delegate method (from UISearchDisplayDelegate) of instead of in your controllers viewDidLoad method.
As follows:
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
{
[tableView registerClass:[DPContentTableCell class] forCellReuseIdentifier:cellIdentifier];
[tableView registerClass:[DPEmptyContentTableCell class] forCellReuseIdentifier:emptyCellIdentifier];
}
This caught me by surprise because on iOS 7... the table view is being reused. So you can register the class in viewDidLoad if you prefer. For legacy sakes, I'll keep my registration in the delegate method I mentioned.
After searching, 'tableView' of cellForRowAtIndexPath method seems not an instance of the Table that you define. So, you can use an instance of a table that defines the cell. Instead of:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
Use:
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
(Do not use the tableView of cellForRowAtIndexPath method, use self.tableView.)
Dequeue the cell without using the 'indexPath' and in case of you obtain a nil element, you have to allocate it manually.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"YourCellId"];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"YourCellId"];
// fill your cell object with useful stuff :)
return cell;
}
Trying to use self.tableView for dequeue the cell may cause crashes when you have a sectioned main list and a plain search list.
This code instead work in any situation.
When I had this problem, the solution was replacing tableView dequeueReusableCellWithIdentifier:#yourcell with self.tableView
I am working on that tutorial also. The default TableViewController has "forIndexPath" and in his example it doesn't exist. Once I removed it the search works.
//Default code
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//Replace with
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
for swift 3 you just need to add self:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = self.tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath) as! YourCell
...
}