I was trying to update tableview in masterviewcontroller from detailviewcontroller using delegate. I called reloadData from delegate methods but it didn't wwork. I still couldn't solve this.
this is my delegate method in MasterViewController
- (void)updateScore:(DetailViewController *)controller withScore:(NSUInteger)score {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:_selectedIndexPath];
NSLog(#"%#", cell.detailTextLabel.text);
cell.detailTextLabel.text = [NSString stringWithFormat:#"Best score: %lu", (unsigned long)score];
[self.tableView reloadData];
NSLog(#"%#", cell.detailTextLabel.text);
}
from NSLog the cell.detailTextLabel.text was updated, but the tableview doesn't reload
thanks
You need to make sure that your view controller is the tableview delegate and the datasource
If you are using storyboards, under connections inspector, your tableview needs your view controller set as dataSource and delegate
if you want to just do it in your viewcontroller.m file in the viewDidLoad method you can use these lines
self.tableView.delegate = self;
self.tableView.dataSource = self;
- (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];
//other init
}
if(_selectedIndexPath.row == indexPath.row && _selectedIndexPath.section == indexPath.section){
cell.detailTextLabel.text = [NSString stringWithFormat:#"Best score: %lu", (unsigned long)score];
}
}
Your can try move code in cellForRowAtIndexPath above then tableView reloadData
[self.tableView reloadData] updates all visible cells in the table. It calls numberOfSectionsInTableView, numberOfRowsInSection, cellForRowAtIndexPath and so on. In other words: It completely renews the table.
The only correct way to set cell content is to set it in cellForRowAtIndexPath. The code:
if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; //other init}
never gets called since iOS 6 if you use storyboards.
So with your code you:
use an incorrect way to setup cell content.
clear all your settings with [self.tableView reloadData].
Solution:
Save score in __strong ivar or property.
Call [self.tableView reloadData]. It calls cellForRowAtIndexPath at appropriate times.
Setup a new score in cellForRowAtIndexPath method.
Suggestion: Use:
dispatch_async(dispatch_get_main_queue(), ^(){ [self.tableView reloadData]; });
to return fast from the delegate and not wait until the table was updated.
Related
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.
I have a custom UITableViewCell, and when it's selected, it expands and adds a UILabel to the selected cells UIView that I added in the storyBoard.
When I run the app and select a cell, the label gets added to myView as expected. The problem is, when I scroll down, the label is also shown at another cell.
Apparently the reason its behaving like so, is because I'm reusing the cell and I don't clean them as Emilie stated. I'm trying to call the method of prepareForReuse and 'cleaning' the cell, but I'm having trouble doing that. Here is my code:
- (void)prepareForReuse {
NSArray *viewsToRemove = [self.view subviews];
for (UILablel *v in viewsToRemove) {
[v removeFromSuperview];
}
Doing that, cleans even the selected cells label.
- (void)viewDidLoad {
self.sortedDictionary = [[NSArray alloc] initWithObjects:#"Californa", #"Alabama", #"Chicago", #"Texas", #"Colorado", #"New York", #"Philly", #"Utah", #"Nevadah", #"Oregon", #"Pensilvainia", #"South Dekoda", #"North Dekoda", #"Iowa", #"Misouri", #"New Mexico", #"Arizona", #"etc", nil];
self.rowSelection = -1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
return customCell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CategorieCell *customCell = (CategorieCell *)[tableView cellForRowAtIndexPath:indexPath];
if (self.info) {
[self.info removeFromSuperview];
}
self.info = [[UILabel alloc] init];
[self.info setText:#"Hello"];
[self.info setBackgroundColor:[UIColor brownColor]];
CGRect labelFrame = CGRectMake(0, 0, 50, 100);
[self.info setFrame:labelFrame];
[customCell.infoView addSubview:self.info];
NSLog(#"%ld", (long)indexPath.row);
self.rowSelection = [indexPath row];
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath row] == self.rowSelection) {
return 159;
}
return 59;
}
The answer is quite simple : you reuse your cell like you should, but never clean them
Reusing your UITableViewCell means that the cell you clicked on previously will be reused when it will go off-screen.
When clicked, you add a view to your UITableViewCell. When reused, the view is still there because you never remove it.
You have two choices : One, you could set a tag of the self.info view (or check with the indexpath you're keeping in memory), then check when you dequeue the cell if the info view is there, and remove it. The cleaner solution would be to implement the view removal by overriding the prepareForReuse method of your custom UITableViewCell
Precision
The first thing you need to do is set a tag for your self.info view after initializing it:
[self.info setTag:2222];
If you want to keep it as simple as possible, you could check and remove the self.info view directly in your cellForRowAtIndexPath method :
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
if [customCell.infoView viewWithTag: 2222] != nil {
[self.info removeFromSuperview]
}
return customCell;
I am not a percent sure this code compiles, I cannot test it on my side for now. Hope it works !
I am trying to call selectRowAtIndexPath on a UITableView that is a subview to the UIViewController I am calling it from.
I have set it up so that when you select a cell it goes grey and thats fine, however I am loading different data sets in and out of the UITableView and when ever a selection is made I am sending that selected NSIndexPath back to the UIViewController. Then when the view is next loaded with the correct data set for the NSIndexPath I call this method from my UIViewController.
if (codeIndexPath != nil) {
[filterViewController.tableView selectRowAtIndexPath:codeIndexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle];
}
Then in the UITableView class my cellForRowAtIndexPath and didSelectRowAtIndexPath look like this.
- (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];
}
// Configure the cell...
NSString *projectDescriptionString = [currentFilterMutableArray objectAtIndex:indexPath.row];
cell.textLabel.text = projectDescriptionString;
if (indexPath == currentlySelectedIndex) {
cell.highlighted = YES;
} else if (indexPath == currentlySelectedIndex) {
cell.highlighted = NO;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleGray;
// send this data back in the delegate so you can use it to show where the tick is again if you need too.
currentlySelectedIndex = indexPath;
[[self delegate] updateInstallTableWithFilter:[currentFilterMutableArray objectAtIndex:indexPath.row] FilterType:filterType InstallIndex:indexPath];
}
When It loads on the screen the correct cell will highlight for a second then go back to white.
Update
New if statment inside cellForRowAtIndexPath
if ([indexPath isEqual:currentlySelectedIndex]) {
cell.highlighted = YES;
} else if (![indexPath isEqual:currentlySelectedIndex]) {
cell.highlighted = NO;
}
I am still receiving the same error.
UITableViewController has a property called clearsSelectionOnViewWillAppear. From the doc:
When the table view is about to appear the first time it’s loaded, the
table-view controller reloads the table view’s data. It also clears
its selection (with or without animation, depending on the request)
every time the table view is displayed. The UITableViewController
class implements this in the superclass method viewWillAppear:. You
can disable this behavior by changing the value in the
clearsSelectionOnViewWillAppear property.
So in that table view controller subclass, in viewDidLoad...
- (void)viewDidLoad {
[super viewDidLoad];
self.clearsSelectionOnViewWillAppear = NO;
}
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"];
}
I'm a new programmer in Objective-C and I have a terrible problem in my first application.
I have one class called Places (NSObject) where I found places in a Google places and return an NSArray.
I have another classe called DisplayPlaces (UiViewController). This class imports my "Place.h".
In my viewDidLoad I have this code:
Places *places = [[Places alloc]init];
[places.locationManager startUpdatingLocation];
[places LoadPlaces:places.locationManager.location];
[places.locationManager stopUpdatingLocation];
[places release];
In my method LoadPlaces I load JSON URL and put a result in NSDictionary after I get only places and put in NSArray and return.
Into my Places I alloc my DisplayPlaces and call a method ReturnPlaces: NSArray to return places that I found.
- (void)ReturnPlaces:(NSArray *)locais{
placesArray = locais;
[self.UITableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [placesArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIndentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIndentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIndentifier] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [placesArray objectAtIndex:indexPath.row];
return cell;
}
It all works.
My problem is:
In my ReturnPlaces: NSArray I call [MyUiTableView reloadData] but I can't refresh my UiTableView.
What can I do?
Set your tableview yourTableView as your property and use
self.yourTableView = tableView; // for assigning the tableView contents to your property
if you are reloading inside any method for tableView, just use
[tableView reloadData];
if you are reloading outside your tableView methods, use
[self.yourTableView reloadData];
Are you calling reloadData on the instance of your tableView. In other words if you have
MyUITableView *mytableView;
then reloading it would require you call
[mytableView reloadData];
not
[MyUITableView reloadData];