I'm trying to use a UISearchDisplayController to search with CLGeocode as the user types, for use with an MKMapView. My view controller has only two things in it - the UISearchBar associated with the UISearchDisplayController and the map view. I'm using a Storyboard.
Normally one would do this with an underlying table view and would declare a prototype cell there, but there seems to be no way of doing that here since I don't have the table view. So I created a subclass of UITableViewCell called SearchTableViewCell, with associated nib.
The nib contains one label, with this outlet connected:
#interface SearchTableViewCell : UITableViewCell
#property (unsafe_unretained, nonatomic) IBOutlet UILabel *textLabel;
#end
I load the nib in viewDidLoad:
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"SearchTableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"CellIdentifier"];
I get the data from CLGeocoder like this:
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller -(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self.geocoder geocodeAddressString:searchString completionHandler:^(NSArray *placemarks, NSError *error) {
self.placemarks = placemarks;
[self.searchDisplayController.searchResultsTableView reloadData];
}];
return NO;
}
And my tableview code looks like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.placemarks count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellID = #"CellIdentifier";
SearchTableViewCell *cell = (SearchTableViewCell *)[tableView dequeueReusableCellWithIdentifier:kCellID forIndexPath:indexPath];
CLPlacemark *placemark = self.placemarks[indexPath.row];
NSString *addressString = CFBridgingRelease(CFBridgingRetain(ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO)));
cell.textLabel.text = addressString;
return cell;
}
The search part is working - when I get to cellForRowAtIndexPath there is data in self.placemarks. However, the line that dequeues a new cell is failing with an exception the first time it's called:
'NSUnknownKeyException', reason: '[<NSObject 0x1a840d40> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key textLabel.'
My cell object is compliant for textLabel, but I'm wondering about that NSObject there in the error message - is the object being returned not a SearchTableViewCell? It is configured in IB to use the correct class. And why is the dequeue method checking for textLabel anyway? I'm pretty sure I've had custom table cells before that had no textLabel field.
Any suggestions on where I've gone wrong?
It turned out that somehow, I had two connections to textLabel - one proper one to the IBOutlet and another to File's Owner. That must have been how the NSObject was getting returned.
After fixing that, I was getting an assertion failure in -[NSLayoutConstraint constant]. Just on a whim I turned off AutoLayout in the nib, which didn't seem to need it and presto. it works now.
Related
UITableViewCells get emptied on scroll (Objective-C)
I am having a problem with UITableViewCells getting emptied as soon I a start to scroll within the table view.
I already had a look at Cells become empty after scrolling. (Xcode) - however the problem still persists.
1) I have a popover view controller, which presents a way to log into some administration. Upon successful login (which hasn’t been implemented yet, the LOGIN button simply takes one straight to a test tableView - which should be fed from some external database later on).
2) Upon successful login, the login view inside the popover gets removed and a custom UITableViewController comes into play with its own XIB.
3) This UITableViewController uses a custom UITableViewCell - since prototype cells are not possible within this configuration.
It all works to the point where I scroll the table - and all the cells get emptied for some reason.
Here is the code run down (I leave out the obvious, eg properties and table section, etc setups):
1) customPopUpViewController(XIB ,.h, .m):
- (IBAction)loginButtonPressed:(UIButton *)sender {
UITableViewController *libraryTableViewController = [[LibraryAdminTableViewController alloc]initWithNibName:#"LibraryAdminTableViewController" bundle:nil];
libraryTableViewController.view.frame = CGRectMake(0, 179, libraryTableViewController.view.frame.size.width, libraryTableViewController.view.frame.size.height);
[self.view addSubview:libraryTableViewController.view];
}
2) LibraryAdminTableViewController (XIB ,.h, .m):
- (void)viewDidLoad {
[super viewDidLoad];
self.LibraryAdminTable.delegate = self;
self.LibraryAdminTable.dataSource = self;
self.tblContentList = [[NSMutableArray alloc]init];
self.tblContentList = [NSMutableArray arrayWithObjects:#"Sync Pack 1",#"Sync Pack 2",#"Sync Pack 3", nil];
[self.LibraryAdminTable registerNib:[UINib nibWithNibName:#"LibraryAdminTableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"LibraryAdminCell"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LibraryAdminTableViewCell *cell = [self.LibraryAdminTable dequeueReusableCellWithIdentifier:#"LibraryAdminCell" forIndexPath:indexPath];
NSString* trackList = [self.tblContentList objectAtIndex:indexPath.row];
cell.cellLabel.text = trackList;
return cell;
}
3) LibraryAdminTableViewCell (XIB ,.h, .m) - I gave the Identifier in the Attributes Inspector “LibraryAdminCell”:
#property (strong, nonatomic) IBOutlet UILabel *cellLabel;
What am I missing?
It is solved. According to this thread I had to get a strong reference to the custom UITableViewController via a property in the popup controller since the ViewController (being the DataSource for the tableView) would not be retained in memory.
This is happening because you are not creating a new cell, when tableview will try to dequeue a cell, and it does not get the cell, then it should create the cell to use but you are not creating any cell, so it is returning nil.Try the code below
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LibraryAdminTableViewCell *cell = (LibraryAdminTableViewCell*)[self.LibraryAdminTable dequeueReusableCellWithIdentifier:#"LibraryAdminCell"
forIndexPath:indexPath];
if (cell == nil)
{
cell = [[LibraryAdminTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"LibraryAdminCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSString* trackList = [self.tblContentList objectAtIndex:indexPath.row];
cell.cellLabel.text = trackList;
return cell;
}
I have a UITableView which has another UITableView nested inside one its cells (I know this is bad practise, don't worry!).
The problem is that when I call dequeueReusableCellWithIdentifier: I am getting nil back. HOWEVER this works just fine when the UITableView is not nested inside another one.
Is there a way to NOT reuse a UITableViewCell, but instead directly instatiate it every time?
I've tried using this:
ContactFieldCell *cell = [[ContactFieldCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:thisCellIdentifier];
which doesn't return nil, but then nothing appears in my UITableView!
Here's the code for the "parent" UITableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ContactCardCell";
ContactCardCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray *objects = [[sections objectAtIndex:indexPath.section] objectForKey:#"objects"];
CDCard *card = [objects objectAtIndex:indexPath.row];
cell.delegate = self;
cell.fieldsTableView = [[CardTableViewController alloc] initWithCard:card];
[cell.fieldsTableView.view setFrame:CGRectMake(17, 12, 256, 163)];
[cell.contentView addSubview:cell.fieldsTableView.view];
return cell;
}
and here's the code for the "child" UITableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *thisCellIdentifier = #"ContactFieldCell";
ContactFieldCell *cell = [self.tableView dequeueReusableCellWithIdentifier:thisCellIdentifier];
cell.delegate = self;
cell.field = [self.card.sortedFields objectAtIndex:indexPath.row];
return cell;
}
ContactFieldCell is a prototype cell within the storyboard. It has the following code:
#interface ContactFieldCell : UITableViewCell
#property (nonatomic, weak) id<ContactFieldCellDelegate> delegate;
#property (nonatomic, strong) CDField *field;
#property (nonatomic, strong) IBOutlet UILabel *displayNameLabel;
#end
dequeueReusableCellWithIdentifier: does not create a cell if none was found for dequeueing.
Create a cell manually, or use dequeueReusableCellWithIdentifier:forIndexPath:
Yes - #vikingosegundo is correct, but to expand his answer, you need to also register your cell first. dequeueReusableCellWithIdentifier: may return nil. And if it is you need to create your cell,s but dequeueReusableCellWithIdentifier: forIndexPath: will always return a valid cell, the catch is you need to tell it what kind of cell, that is what registerClass does.
Do this for both UITableViews.
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[ContactFieldCell class] forCellReuseIdentifier:#"ContactFieldCell"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *thisCellIdentifier = #"ContactFieldCell";
ContactFieldCell *cell = [self.tableView dequeueReusableCellWithIdentifier:thisCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.field = [self.card.sortedFields objectAtIndex:indexPath.row];
return cell;
}
UITableViews are a very powerful element and can be used to build great apps.
The only thing to keep in mind is, the basics must be clear. Now from your code, I cannot make out whether you have assigned the delegates and dataSources properly, but I'll still mention it in case someone else needs it.
You have a subclassed UITableViewCell which in turn contains a UITableView. The UIViewController must be the delegate and dataSource for the outer UITableView. Make sure you have set it in both the .h and .m file.
Next, your custom cell must also be the delegate and dataSource, but for the inner UITablewView. I suppose here, you have created the inner UITableView in the init method of the UITableViewCell. Set the delegate and dataSource there itself. Then you set other runtime properties in the drawRect method (if needed) and call it's reloadData.
The UIViewController must override the delegate and dataSource methods for the outer table and the cell must override the methods for the inner table.
Also, make sure, the time the cells are plotted, your data is not nil or null.
And a very important fact, that people miss is the following code:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Just dequeueing the cell is not enough. The first time a cell is dequeued, it is nil because it has not been created yet. Hence the if condition. Once it is allocated and initialized and added to the table, the dequeue code works thereafter.
NOTE : After looking more closely to your code (sorry for not looking the first time), I noticed you have allocated a UITableViewController to your cell. How do you think the cell is going to display a controller? Use a UITableView instead. Try to follow the pattern I have mentioned in paragraph 3. Use a table in the custom cell as a private member (or property, your wish), allocate it in init. Assign the data to the cell from your view controller. Then use this data to set the inner table view cell's properties in it's drawRect. It should work fine.
I have created .h and .m files for UITableView called mainTableViewgm.h and mainTableViewgm.m resp. and I am calling -initWithFrame: method from my main view controller to this mainTableViewgm.m implementation file
[[mainTableViewgm alloc]initWithFrame:tableViewOne.frame]
Note that this tableview is in my main view controller. But I have created separate files for the tableView and have also set the custom class to mainTableViewgm in storyboard.
the -initWithFrame: methods appears as follows
- (id)initWithFrame:(CGRect)frame
{
//NSLog(#"kource data");
self = [super initWithFrame:frame];
if (self)
{
[self setDelegate:self];
[self setDataSource:self];
[self tableView:self cellForRowAtIndexPath:0];
[self tableView:self numberOfRowsInSection:1];
// Initialization code
}
return self;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"kource data");
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"kource data2");
UITableViewCell*cellOne =[[UITableViewCell alloc]init];
cellOne.detailTextLabel.text=#"text did appear";
return cellOne;
}
the -initWithFrame: is being called fine along with the 'if (self)' block in this method. But the problem is numberOfRowsInSection: and cellForRowAtIndexPath: are not being automatically called here . kource data/kource data2 never appear in log. What do I do to load the table? Are the delegate/datasource being set incorrectly?
I must mention that I have also set the UITableViewDelegate and UITableviewDataSource protocols:
#interface mainTableViewgm : UITableView <UITableViewDelegate,UITableViewDataSource>
#end
Help will be much appreciated. Thank you.
Your tableview is not loaded when the controller is initializing, so you cannot do that in the init methods. You have to move your code to the viewDidLoad method.
Also you are not setting the delegate and datasource on the tableview object (probably a type, you are setting them on the view controller). It should look like this:
- (void)viewDidLoad:(BOOL)animated {
[super viewDidLoad:animated];
[self.tableView setDelegate:self];
[self.tableView setDataSource:self]; // <- This will trigger the tableview to (re)load it's data
}
Next thing is to implement the UITableViewDataSource methods correctly. UITableViewCell *cellOne =[[UITableViewCell alloc] init]; is not returning a valid cell object. You should use at least initWithStyle:. And take a look how to use dequeueReusableCellWithIdentifier:forIndexPath:. A typical implementation would look like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
// Reuse/create cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Update cell contents
cell.textLabel.text = #"Your text here";
cell.detailTextLabel.text=#"text did appear";
return cell;
}
I can't believe I've been doing XCode programming for two years, and still hit this issue.
I had the same problem with XCode 6.1 - I was setting my UITableView's delegate & dataSource in the viewWillAppear function, but none of the delegate functions were kicking in.
However, if I right-clicked on the UITableView on the Storyboard, the circles for delegate and dataSource were empty.
The solution, then, is to hold down the CTRL key, and drag from each of these circles up to the name of your UIView which contains your UITableView:
After doing this, my UITableView happily populated itself.
(So, we're upto v6.1 of XCode now are we ? Do you think Apple ever going to make this thing, you know, friendly...? I would quite like to add a Bookmark in my code... that'd be a nice feature.)
I created a universal master-detail application. I have a table view within a custom class (MasterViewController class). This is a UITableViewController class.
Using Storyboard, I created a custom cell in my iPhone table view. The custom cell contains 3 labels and 1 image.
In StoryBoard, this table view datasource and delegate is set to the MasterViewController class.
My custom table view cell has 'User Interaction Enabled' in the View section of the attribute inspector.
In my MasterViewController class, the UITableViewDataSource methods such as 'numberOfSectionsInTableView' are working fine.
I have a seque defined between the table view and a detailed view.
When I run the application and select a cell in the table view, the didSelectRowAtIndexPath is not called for the iPhone. My prepareForSegue method executes and the segue occurs.
I have read quite a number of posts on didSelectRowAtIndexPath not being executed, but I have not been able to solve my problem.
Any hints would be very much appreciated.
My custom cell identifier is ConversationCell. The segue identifier is ShowConversationDetails.
I debug with a breakpoint on the first line of numberOfRowsInSection and this method is never entered.
The segue works and the detail view displays my label with 'Hello' text.
Some Code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
//NSLog(#"Rows %lu",(unsigned long)[sectionInfo numberOfObjects]);
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ConversationCell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
[[self.fetchedResultsController sections] count]);
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
self.detailViewController.detailItem = object;
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ShowConversationDetails"]) {
NSIndexPath *selectedIndex = [self.tableView indexPathForSelectedRow];
Event *e = nil;
e = [self.fetchedResultsController objectAtIndexPath:selectedIndex];
UIViewController *destinationViewController = [segue destinationViewController];
[segue.destinationViewController setLabel:#"Hello"];
}
}
MASTERVIEWCONTROLLER:
Here is my interface for above;
#interface MasterViewController : UITableViewController <NSFetchedResultsControllerDelegate,ASIHTTPRequestDelegate,UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *eventsArray;
}
This may be a clue to my problem. When I add the following code to prepareForSeque
NSIndexPath *selectedIndex = [self.tableView indexPathForSelectedRow];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:selectedIndex];
NSString *messageTableID = [[object valueForKey:#"messagetableid"] description];
selectedIndex is empty as if a row is no longer selected when prepareForSeque is executing.
By the way, just to be safe, I removed the app from the simulator and then did a Clean on the project and nothing changed.
Tim
In addition to the checks you have already mentioned, here are a few other ones you might want to consider:
Check that the method is exactly this (no difference in letter cases) :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Check that the controller implements the UITableViewDelegate protocol
Check that interaction does work (change the selection style option so that the cell changes color on touch events)
Check that you don't have any view inside that is first responder and takes away the interaction (remove any view in the cell to test)
Try to programmatically allow selection on the table [tableView setAllowsSelection:YES];
Are you sure that your UITableViewDelegate object is correctly being set as the table view's delegate? (This is separate from setting it as the data source.)
I ended up deleting my MasterViewController and DetailViewController. I recreated them and things are now working. Not sure what I did wrong the first time. Thanks for everyone that offered advice.
Tim
I have an nslog that is successfully telling me that the information I will load into dynamic cells is there. However, I've discovered that my tableView is not loading the array "dogs" with object "dogs" at all because the nslog for name11Label is returning null despite the fact that there is a value for dogs in viewDidLoad. What would I need to do to initiate the tableView? (.h does have an iboutlet and a property(just to make sure) for my "tableView" and also has as well as and import for cell11.h)
.m file
-(void)viewDidLoad{
...
self.tableview.delegate = self;
...
}
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
if ( dogs != NULL ) {
return [dogs count];
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Cell11 *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell11"];
if (cell == nil)
{
cell = [[[Cell11 alloc] initWithFrame:CGRectZero reuseIdentifier:#"Cell11"] autorelease]; //I know that this method is depreciated, but it is not the source of this problem
}
NSDictionary *itemAtIndex =(NSDictionary *)[dogs objectAtIndex:indexPath.row];
cell.name11Label.text = [itemAtIndex objectForKey:#"dogs"];
NSLog(#"dogs array = %#", dogs); //returns correct information of the object dogs
NSLog(#"%#", cell.name11Label.text); //returning null even though "dogs" in viewDidLoad is showing a result;
return cell;
}
Cell11.h
#import <UIKit/UIKit.h>
#interface Cell11 : UITableViewCell
#property (nonatomic, strong) IBOutlet UILabel *name11Label;
#end
Cell11.m
#import "Cell11.h"
#implementation Cell11
#synthesize name11Label;
#end
Change:
Cell11 *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell11"];
to
Cell11 *cell = [tv dequeueReusableCellWithIdentifier:#"Cell11"];
tableView is probably not hooked up right.
The nslog statement will never get called. Move it above:
NSLog(#"%#", cell.name11Label);
return cell;
Also you are initializing the cell with cgrect zero which won't display anything. Make sure it has a height and width that are expecting (e.g. CGRectMake(0.0f, 0.0f, 320.0f, 50.0f))
If the cell still doesn't display, are you calling reloadData on the tableview after the array dogs is set up?
Have you assigned your UITableView's "dataSource" property to the object with the UITableViewDataSource protocol (the above methods)?
First of all: The NSLog after the return will never be called.
NSLog(#"%#", cell.name11Label);
return cell;
}
Second: Control drag from you tableview of the interface builder to your class. And make sure the connection dataSource is properly connected.