I'm new in CoreData and using MagicalRecord to rule with it. My problem is that I have the UITableView with an NSArray as dataSource populated with objects which fetched from CoreData db, and everything seems fine until I scroll the table for some times.
Here is my code:
Method for fetching data (MyDatabase.m):
+(NSArray *)getEntities{
...
return [MyEntity MR_findAllSortedBy:#"name" ascending:YES withPredicate:predicate];
}
Here is how I fetch and set data to UITableView in my ViewController:
- (void)viewDidLoad {
[super viewDidLoad];
myEntitiesArray = [MyDatabase getEntities];
if(myEntitiesArray.count != 0)
[myTableView setTableData:myEntitiesArray];
}
Here is setTableData method implementation in MyTableView.m:
- (void)setTableData:(NSArray *)array {
if (array && [array count] > 0) {
_tableData = array;
[self reloadData];
}
}
And here is how I set up my cells in MyTableView.m:
- (void)tableView:(UITableView *)tableView willDisplayCell:(SSCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
}
I tried to put an NSLog(#"name is %#",[(MyEntity *)_tableData[indexPath.row] name]) into willDisplayCell and found that when cells become empty, NSLog prints out the messages "name is (null)". I know this question is possibly solved by many people and many times before I faced this problem. Hope someone will help me to solve it too :)
UPDATE: cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"ssCell";
SSCell *cell = (SSCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if( !cell ) {
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell setSelectedBackgroundView:selectedBackgroundView];
}
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
return cell;
}
I also call this method inside MyTableView.m init method:
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:#"ssCell"];
You have to use cellForRowAtIndexPath. In this method the cells are allocated.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/*
* This is an important bit, it asks the table view if it has any available cells
* already created which it is not using (if they are offscreen), so that it can
* reuse them (saving the time of alloc/init/load from xib a new cell ).
* The identifier is there to differentiate between different types of cells
* (you can display different types of cells in the same table view)
*/
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
/*
* If the cell is nil it means no cell was available for reuse and that we should
* create a new one.
*/
if (cell == nil) {
/*
* Actually create a new cell (with an identifier so that it can be dequeued).
*/
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MyIdentifier"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
/*
* Now that we have a cell we can configure it to display the data corresponding to
* this row/section
*/
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
return cell;
}
You should be initializing the cell by calling init. Instead you are doing the following:
if( !cell ) {
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell setSelectedBackgroundView:selectedBackgroundView];
The second call attempts to again reuse an existing cell when there isn't one available. That would probably return nil again.
be very careful of the "feature" of objective C, where calling a method of a nil object does nothing. Instead of crashing with null.pointer.exception like Java, it probably floats over [cell setSelectedBackgroundView:selectedBackgroundView] and a whole bunch of other lines without a problem.
Related
I have the following code to create my cells in a UITableView.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"futureAppointments";
FutureAppointmentsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (nil == cell) {
[tableView registerNib:[UINib nibWithNibName:#"FutureAppointmentsViewCell" bundle:nil] forCellReuseIdentifier:CellIdentifier];
cell = [[FutureAppointmentsViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
NSUInteger position = indexPath.row;
cell.appointmentDescription.text = [NSString stringWithFormat:#"%lu. %#. %#", (unsigned long)position, #"Steve", #"10/03/"];
return cell;
}
The problems is, the first cell of the tableView is missing. It should start with 0. Steve but instead starts with 1. Steve. Also there are only 4 elements in the list instead of 5.
When I place a break point in the code, the first cell is nil.
Does anyone know what might be happening?
Put this line of code:
[tableView registerNib:[UINib nibWithNibName:#"FutureAppointmentsViewCell"
bundle:nil]
forCellReuseIdentifier:CellIdentifier];
In viewDidLoad. It doesn't belong in cellForRowAtIndexPath. Once you do this then you no longer need to check whether the cell is nil.
Change cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"futureAppointments";
FutureAppointmentsViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSUInteger position = indexPath.row;
cell.appointmentDescription.text =
[NSString stringWithFormat:#"%lu. %#. %#",
(unsigned long)position, #"Steve", #"10/03/"];
return cell;
}
I think I had similar problem when I was using UITableView inside UIView and initialized it in init method.
I was not able to find good explanation for that behavior, but I've found tricky solution for that - I was reloading UITableView instance from UIViewController in viewDidAppear method.
I would also like to know, why UITableView is not drawing all UITableViewCell.
I'm seeking how create several cells to go to different ViewControllers.
For my TableView, I'm using a subclass of UITableViewController.
And when I choose 2 in the following method, I just see 2 identical cells which are doing exactly the same thing. I'm not interested by this. I don't even know their IndexPath in order to change their title.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 2;
}
And When I try to put another UITableViewCell in my TableView, it doesn't appear on iOS simulator, even with the same option (same subclass) than my first UITableViewCell which I can see.
Thanks for your help.
Edit : Here is my new code to create 2 cells but doesn't work :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell2";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[customCell alloc] init];
}
static NSString *CellIdentifier1 = #"Cell1";
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if (cell1 == nil) {
cell1 = [[customCell alloc] init];
}
// Configure the cell...
return cell;
}
You define your cells in tableView:cellForRowAtIndexPath:, so you should provide an implementation for that method.
tableView:numberOfRowsInSection: only returns the number of cells in the table.
If you need more help, please provide your implementation for tableView:cellForRowAtIndexPath:. This is how a typical implementation looks like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
... customize your cell ...
}
EDIT:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell2";
static NSString *CellIdentifier1 = #"Cell1";
if(indexPath.row == 0 ) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[customCell alloc] init];
}
} else {
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
if (cell1 == nil) {
cell1 = [[customCell alloc] init];
}
}
return cell;
}
This method gets called when a cell has been selected. You can decide what you wanna do according to the selected row
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0)
[self goToFirstViewController];
else
if(indexPath.row == 1)
[self goToSecondViewController];
}
Use the following:
- (NSInteger) tableView: (UITableView*) tableView numberOfRowsInSection: (NSInteger) section
This delegate method returns the number of rows you want in that particular section. So if you want more than 2 rows, or you want the number of rows to be dynamic, you can create a NSArray in the AppDelegate or in the init method of the viewController class, and return the number in the numberOfRowsInSection method like
return [delegate numberOfNames];
In my example above, I created an array in my AppDelegate and also a method to return the number of objects I have in that array so that I can create the number of rows for my table.
- (UITableViewCell*) tableView: (UITableView*) tableView cellForRowAtIndexPath: (NSIndexPath*) indexPath
This delegate method will show what you want to display in each cell. Therefore, following on from my array created in my AppDelegate, I first create the cell, then I will set the text I want to display on the cell with a method I created in my AppDelegate that will return a NSString while taking in a NSInteger so that I can loop through my array and display the text accordingly.
static NSString* MyIdentifier = #"Default";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if( cell == nil )
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
cell.textLabel.text = [delegate nameAtIndex:indexPath.row];
}
nameAtIndex is the name of the method I created in my AppDelegate that will return the NSString object at the specific index (ie. the row number) from the NSArray I created to store all the items of my table.
When the user clicks on any of the rows in the table created, this delegate method will be called
- (void) tableView: (UITableView*) tableView didSelectRowAtIndexPath: (NSIndexPath*) indexPath
And in here, I will check if the text displayed matches any of the items in my array from the AppDelegate that stores the items in the table, and create the view that is necessary.
UIViewController* viewController = nil ;
NSString* nameInArray = [delegate nameAtIndex:indexPath.row] ;
if( [nameInArray isEqualToString:#"firstName"] )
{
viewController = [[FirstViewController alloc] init];
}
else if( [nameInArray isEqualToString:#"secondName"] )
{
viewController = [[SecondViewController alloc] init];
}
else if( [nameInArray isEqualToString:#"thirdName"] )
{
viewController = [[ThirdViewController alloc] init];
}
So with these 3 delegate methods, you will be able to create the table using a NSArray created, and be able to redirect the user to a viewController according to which option in the table he chooses. You will not have to keep editing the delegate methods if you choose to add more rows to the table as well since you are returning the count of the array when setting up the table.
The array and methods to get the data of the array can be created in the viewController as well, not necessarily in the AppDelegate, in case you were wondering.
The methods are as follows:
-(NSInteger) numberOfNames
{
return [myArray count];
}
-(NSString*) nameAtIndex: (NSInteger) index
{
return [myArray objectAtIndex:index] ;
}
Hope this helps! :)
I have one UIViewController with UITableView inside,above table I have UISegmentControl, when I press on segment control I want to load a UItableCustomeCell, would you please help me in this implementation, I don't know how should I add them in cellForRowAtIndexPath, Since I have 3 different Custom cell
Here is the code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath
{
if (indexPath.row == self.segmentedControl.selectedSegmentIndex == Test1) {
MytestsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MytestsCell"];
if (!cell) {
cell = [[MyBooksCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"MytestsCell"];
}
return cell;
}
else if (indexPath.row == self.segmentedControl.selectedSegmentIndex == tests) {
testCell *cell = [tableView dequeueReusableCellWithIdentifier:#"testCell"];
if (!cell) {
cell = [[TestsCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"testsCell"];
}
return cell;
}
break;
case 1:
if (indexPath.row == self.segmentedControl.selectedSegmentIndex == PTest) {
PTestsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PTestsCell"];
if (!cell) {
cell = [[PTestsCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"PTestsCell"];
}
return cell;
}
break;
}
I don't want to have 3 of them in one table, each custom cell is for one segment control
Thanks in advance!
One alternative I can think of is to switch the table views data source. But I would not recommend that. You could define a delegate of your data source and ask it for the table view cell for a selected segmented control. But this just moves the problem. I would stick to your approach.
So...here is what I would do. Starting with iOS6, you no longer need to check if your cell is nil after dequeuing from the tableview if you use
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath
You are guaranteed to get a cell back as long as the identifier exists. Also, it doesn't look like you need to do any additional configuration so something like this should work:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [NSString stringWithFormat:#"%d", self.segmentedControl.selectedSegmentIndex];
return [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
}
Edit: I forgot to add, that in order to use this, use numbers that correspond to the segments as the identifier for each cell.
I have a TableView with data from parse.com where I Placed order to descend according to Functional queryfortable with the "Date" selected by a pickerdate. Now the earliest date turns out to be the last cell and only in the last cell I would like to add a image ... In this specific case, how could I do? This is more 'complicated for me: (
I need to insert an image only in the last cell
Thanks Rory
If you're using PFQueryTableViewController then you should be using a custom subclass of it.
/// MyPFQueryTableViewController.h
#interface MyPFQueryTableViewController : PFQueryTableViewController
self.objects is the Datasource array the PFQueryTableViewController class uses for each table row.
You have to check whether the indexPath.row is the last object inside the self.objects array.
Within MyPFQueryTableViewController.m you override the cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
static NSString *CellIdentifier = #"Cell";
PFTableViewCell *cell = (PFTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell
// Is this the last displaying cell?
if( indexPath.row == ([self.objects count]-1) )
{
cell.imageView.image = [UIImage imageNamed:#"End_Of_List.jpg"];
cell.textLabel.text = #"";
}
else
{
cell.textLabel.text = [object objectForKey:self.textKey];
}
return cell;
}
I am having issues with my tableView not firing the didSelectRowAtIndexPath method. I have implemented the delegates as such:
#interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate>
And in my storyboard the tableView's data source and delegate are both pointed at the base View Controller. I have User Interactions enabled as well as Selection set to Single Selection, and it is not the TapGesture problem since my tap gestures are not bound to the view and I have checked and they do not fire.
This is the code for setting up the table:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return menuArray.count;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
NSDictionary *menuItem = [menuArray objectAtIndex:indexPath.row];
cell.textLabel.text = menuItem[#"Title"];
cell.detailTextLabel.text = menuItem[#"Subtitle"];
return cell;
}
-(void)showMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(menuTable.frame.size.width, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(void)hideMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(0, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(IBAction)showMenuDown:(id)sender {
if(content.frame.origin.x == 0)
[self showMenu];
else
[self hideMenu];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//whatever
}
The table is initially out of view on the storyboard (origin.x is set to -150), then when the user clicks on a button in the navigationBar, the view slides over to reveal it, which is what might be causing the problem I think.
Is there anything wrong with my code or implementation that would be causing this to not work?
If you already see your table populated with values from your dictionary then you can rule out data source and delegate as being the problem. i.e. your storyboard connections are working.
Your code looks fine to me. the only difference I see is I usually define my table like this. Try this and see if it helps.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSLog(#"Inside cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Your code here
// ....
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
This will return nil in case there was never a cell created.
so checking if cell is nil is mandatory and if so, you need to create a cell.
static NSString *CellIdentifier = #"menuCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
as you are using storyboard you can alternatively use
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
for prototype cells. Make sure you use the same identifier in the storyboard and that you registered your the cell's class
- (void) viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"menuCell"];
}