Insert rows in UITableView from code - objective-c

This seems ridiculous but I can't seem to sort it out. I need to insert a row in a table view in response to a push notification. The implementation should be as simple as
- (void)didSaveMessage:(Message*)message atIndex:(int)index
{
//check to make sure I have an array to populate the table from...
if (self.messageManager.messages != nil)
{
self.indexRow = index;
NSLog(#"refresh table view from code");
//remove a label that says "You have no messages"...
[[messageTable tableFooterView] removeFromSuperview];
[messageTable beginUpdates];
[messageTable insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:index inSection:0]]
withRowAnimation:UITableViewRowAnimationFade];
[messageTable endUpdates];
[self scrollToNewestMessage];
}
}
This method is called when I receive a remote notification and it does fire. The log statement prints. However, the UITableView does not update. What is particularly strange to me is that if I put the same code in an IBAction and link it to a refresh button in storyboard, it works. Is there something special that I have to do to update a UITableView programmatically?

Try to send the table the reloadData message. This should force the table view to update.

It seems pretty trivial, but is it possible that the Message* that you are passing into the didSaveMessage is nil? It would explain why the NSLog statement works, and I would assume that you had to create the message manually when testing from an IBAction.

This is resolved. I was calling the method to update the UI from didReceiveRemoteNotification: in the AppDelegate but to a different instance of my view controller. So I added a viewController property onto the app delegate, set the value for that in my view controller's view did load, and now it's all working.

Related

EXC_BAD_ACCESS error at viewwillappear using scrollToRowAtIndexPath

I'm getting an EXC_BAD_ACCESS error when using scrollToRowAtIndexPath in the viewWillAppear method. I searched for solutions and saw some old posts recommending to set delegate and table to nil (see code below), however when I set that I simply dont get anything loaded in my tableview.
I should say that this is part of a chat application where I want to show the last message entered first. Many thanks for any assistance with this.
Here's my viewWillAppear:
-(void)viewWillAppear:(BOOL)animated {
[self.table reloadData];
int lastRowNumber = [self.table numberOfRowsInSection:0] - 1;
NSIndexPath* ip = [NSIndexPath indexPathForRow:lastRowNumber inSection:0];
//self.table.delegate = nil;
//self.table = nil;
[self.table scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
I should add that this code seems to work fine in other parts of my program, the only time I get the error is in the viewWillAppear method.
viewWillAppear: is too early to do any animation on view. Per Apple Documentation:
This method is called before the view controller's view is about to
be added to a view hierarchy and before any animations are configured
for showing the view. You can override this method to perform custom
tasks associated with displaying the view. For example, you might use
this method to change the orientation or style of the status bar to
coordinate with the orientation or style of the view being presented.
If you override this method, you must call super at some point in your
implementation.
So, you cannot add animations when even the view hierarchy is not set completely.
It may help you
NSIndexPath * lastIndex =[NSIndexPath indexPathForRow:yourContenetArray.count-1 inSection:0];
[self.yourTableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionNone animated:YES];
Write your code in viewDidAppear method.As your table view is loaded
then after you should call the method scrollToRowAtIndexPath . Then
your animation will be performed.And if you want to use in
viewWillAppear , then You can try to reload your tableView and then
write code for scrollToRowAtIndexPath
You should probably use the viewDidLoad method for scrolling, and if your array count is 0 (empty array) it will throw an error. It is not possible to scroll to cell at index -1.

Cocoa Touch: How can I update the table view everytime I tap on a tab of tabbar?

I'm using iOS 8 and Objective c.
If I do a change in the values in one view, I can't see the reflection on my other tabviews. But if I re-run the app, I can see the reflection on the other tabs. Sometimes it works, sometimes it doesn't. How can I make sure that when I tap on a tabview, it will be reload according to the database?
I'm not sure what you are trying in your code, but you can try to implement the viewDidAppear method with a reloadData in your tableView:
- (void)viewDidAppear:(BOOL)animated{
[self.tableView reloadData];
}
[tableView reloadData] is the one you are looking for , this UITableView method refreshes the table view by calling its delegate and data source methods again to get fresh data.
I solved it!
Here is my code:
"TheMaster" is the main tabview. Here selectedIndex can be 0,1,2.... according to the placement of the tab. The leftmost tab has the index 0, and after that 1,2, and so on.
UITabBarController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"TheMaster"];
controller.selectedIndex=0;
[self presentViewController:controller animated:YES completion:nil ];

Why does an empty tableView check the number of sections but a non-empty one does not?

I have set up a demo application with a simple UITableViewController with no contents, but an 'Add' button in the toolbar. This launches a modal view controller which is again empty other than a 'cancel' button. The cancel button just tells its delegate (the UITableViewController) to dismiss the modal.
I then added an NSLog statement in the UITableViewController's numberOfSectionsInTableView method.
Ordinarily, when the table view controller loads I see two calls to numberOfSectionsInTableView. When I open and dismiss the modal (which returns to the UITableViewController) I see no further calls to numberOfSectionsInTableView.
However, if I return 0 from numberOfSectionsInTableView, in addition to the two calls on display, I also see an additional numberOfSections call when the modal is dismissed.
This only happens when numberOfSectionsInTableView returns 0, and I have added no additional code to my project besides that mentioned. This is easily verifiable by setting up a couple of controllers as I've described and modifying the result from numberOfSectionsInTableView.
My questions:
Why is the UITableView calling numberOfSectionsInTableView on return from a modal view?
Why is it only doing this if numberOfSectionsInTableView returns 0?
In addition to numberOfSectionsInTableView, the UITableViewController is also calling cellForRowAtIndex: when the modal is dismissed. In fact, it is attempting to display the new contents of its dataSource. How am I meant to manually animate a row insertion if the first row added is going to already be updated automatically? Shouldn't it be left to me to make sure that my UITableView is consistent with its dataSource?
What property is the UITableViewController checking to know that there is one or more sections (and therefore ask my delegate how many sections)? It can't be numberOfSectionsInTableView itself, since I would see it called whenever I return from the modal, not only when numberOfSections = 0.
From UITableViewController docs:
When the table view is about to appear the first time it’s loaded, the
table-view controller reloads the table view’s data... The
UITableViewController class implements this in the superclass method
viewWillAppear:
If you watch in the debugger, the second call upon app launch is from UITableViewController's viewWillAppear: implementation - specifically the part referred to above, where tableView is sent the reloadData message.
Now, the first call to numberOfSectionsInTableView: on launch is also from UITableViewController's implementation of viewWillAppear: but not directly from that implementation's call to -[UITableView reloadData]. I'm not sure what the first call is all about.
But, to your question, the call to numberOfSectionsInTableView: that happens when dismissing the modal has exactly the same call stack as the second call from applicationDidFinishLaunching:withOptions:. My hypothesis then is that UITableView interprets having zero sections as being in a state where it has not loaded at all. That does make some sense actually. I'd consider an "empty" table view to be one without any rows, but one without any sections seems almost "uninitialized" to me. Furthermore the UITableViewDataSource documentation implies UITableView has by default one section. Returning zero from this method would be inconsistent with that assumption of the docs as well.
Now, to your concern about animation - if you give the table an empty section to work with, you will be able to have full control over inserting the first row with whatever animation you'd like, and not be locked in to when you need to reload.
I think the moral of the story is, don't return zero sections unless you really, really need to for some reason. The title of your post refers to this table view being "empty" as well but I think it's clear the framework finds zero sections to not be empty but unloaded.
Hope this helps! And thanks for posting the sample project for me to play around with.
Perhaps the delegate just couldn't believe its eyes. But seriously, since a table view has to have at least one section, passing 0 doesn't make any sense. Why do it? You pass it an invalid argument and it gives you back a weird response. As to why it doesn't ask for number of sections when you pass 1, I think it's because it doesn't need to know at that point (coming back from the modal view controller) -- the table view has already been populated (if there were some data) and you haven't changed anything in the model, so it doesn't need to update.
I added a few lines to your example project to slide in a row each time you return from the modal view controller, which is what I think you're trying to do. I added an int property num for the return value of numberOfRowsInSection, added an array to populate the table, and a call to insertRowsAtIndexPaths from the modal view controller dismissal method.
- (void)viewDidLoad
{
_num = 0;
self.theData = #[#"one",#"two",#"three"];
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(addRecipe)];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"# sections requested");
//when 0, this fires on return from the modal. When 1, it does not.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"in numberOfRows in section");
return _num;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"in cellForRowAtIndexPath");
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
cell.textLabel.text = [self.theData objectAtIndex:indexPath.row];
return cell;
}
- (void)addRecipe
{
//create the modal and suscribe for delegate notifications
AddRecipeViewController *addRecipeController = [[AddRecipeViewController alloc]initWithStyle:UITableViewStyleGrouped];
addRecipeController.delegate = self;
//display the modal in a navigation controller
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addRecipeController];
[self.navigationController presentModalViewController:navController animated:YES];
}
- (void)addRecipeVC:(AddRecipeViewController *)addRecipeVC didAddRecipe:(NSString *)recipe
{
[self dismissModalViewControllerAnimated:YES];
_num += 1;
[self performSelector:#selector(addRow) withObject:nil afterDelay:.5];
}
-(void)addRow {
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:_num-1 inSection:0]] withRowAnimation:UITableViewRowAnimationRight];
}
The tableview checks number of sections when it's populating the table view with data!
Since the table can be divided into sections, it has to know specifically how many sections to divide it into.
When you reload the data, the number of sections is also checked.
Because every time the table view has to take action in accessing either the data of the table, like what row you tapped, and in what section, or populating the data table, the number of sections has to be known!
Hope this helped!

UITableView Refresh Query

I'm using a UITableView control to show some data that can be edited by the user. To edit the details the user taps on an edit button which pushes a new view onto the stack. The user edits the data, taps a save button and the data is saved to a plist and the view popped off the stack. Even though the plist has been updated the UITableView still shows the old data. This can be corrected by adding a call to reloadData in the viewWillAppear method. However when the view is first loaded the data is displayed correctly, by adding the reload statement does this mean a double bind? If so, how can this be avoided?
I found the following code (here) which forces a refresh without explicitly calling reloadData:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
int orientation = [[UIDevice currentDevice] orientation];
if(orientation != UIDeviceOrientationUnknown)
[self willRotateToInterfaceOrientation:orientation duration:0];
}
Can anyone explain how/why this works?
The trick from your link is a dirty hack. Not only does it reload the data, but also forces the table to redraw. It tells your app that the device is getting a new orientation, so your table gets redrawn, along with other UI elements.
The standard way of refreshing a single row or a specific set of rows in your UITableView is calling its reloadRowsAtIndexPaths:withRowAnimation: method: doing so calls through to your data source to get data for only the row(s) that have been updated, preventing the full reload.
Do this:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//remove all objects from yourTableViewDataSourceArray
[yourTableViewDataSourceArray removeAllObjects];
//add new records from plist
yourTableViewDataSourceArray = plist request of data here
//reload table now
[yourtableView reloadData];
}

indexPath null in didSelectRowAtIndexPath in an ipad splitview navigation app

I'm porting a fairly simple iPhone Navigation based app to a Split View iPad app.
I have two nested levels of navigation on the Master view. The user picks a value from the first table and it loads the 2nd table. Selecting a value on the second table loads the Detail item for the detail view. Or it's supposed to. The didSelectRowAtIndexPath on my 2nd controller is firing but indexPath is null.
I'm following the SplitView template fairly closely. I'm only really getting off the beaten track by adding that 2nd TableViewController. My RootViewController loads the 2nd TableViewController on didSelectRowAtIndexPath, and that part's working. In my 2nd TableViewController.m I'm trying to set the detail item for the DetailView. But it's that didSelectRowAtIndexPath method that's not giving me the row. I'm fairly new to this, but it seems odd to me that the method for clicking a row would fire but not have the index for that row.
Here's the code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TrackerSplitViewAppDelegate *appDelegate = (TrackerSplitViewAppDelegate *)[[UIApplication sharedApplication] delegate];
detailViewController.thisRequest = [appDelegate.requests objectAtIndex:indexPath.row];
NSLog(#"Request Loaded: %#", detailViewController.thisRequest.Title);
NSLog(#"Index Row: %#", indexPath.row);
[appDelegate release];
}
The array of requests is loading properly (appDelegate.requests) but my objectAtIndex is failing because indexPath is null. Or at least indexPath.row is null.
EDIT: Ok, the comments below are correct, I'm not using NSLog properly. The indexPath.row is fine (I thought it was also showing up as null using the mouseover in the debugger, but I just don't know how to use the debugger properly).
The detailViewController property isn't getting set right for some reason. If I substitute with this:
//detailViewController.thisRequest = [appDelegate.requests objectAtIndex:indexPath.row];
Request *aRequest = [appDelegate.requests objectAtIndex:indexPath.row];
The aRequest object loads just fine. So the next question is why setting my property on my detailViewController object isn't working. I'm still quite fuzzy on how things persist in this environment. Any additional input would be great.
The value of indexPath.row is not a string. You can't log it with %#. Try using %i or %li, which are used for integers. Also, you haven't reaLly properly checked for the request to be loaded, afaik. Is the title property only available after a successful load?
NSLog(#"Index Row: %#", indexPath.row);
indexPath is an NSIndexPath object, you could use %# to print it.
But indexPath.row and indexPath.section are NSUInteger. So you have to use %u to print them.
So maybe your indexpath.row is not nil (as in no object) but 0 (as in zero, the number before one)
Select a row that is not the first one and you should get a crash.