iPhone: Waiting until selectrow animation finishes before pushing viewcontroller? - iphone

My code looks like this:
NSIndexPath *ip = [NSIndexPath indexPathForRow:15 inSection:0];
[[self tableView] selectRowAtIndexPath:ip animated:YES scrollPosition:UITableViewScrollPositionMiddle];
// Pause needed here until animation finishes
[[self navigationController] pushViewController:secondView animated:YES];
Right now it animates the scroll/selection of the row and pushes the view controller at the same time. What I would like it to do is wait until it finishes the scroll/selection and then push the view controller. Is there any possible way of doing this? Thanks

The tableview should call scrollViewDidEndScrollingAnimation when the scrolling ends. Try putting the push in that method:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
if (weShouldPushSecondView)
{
weShouldPushSecondView = NO;
[[self navigationController] pushViewController:secondView animated:YES];
}
}
You might need to use a bool ivar that you set to YES right after the selectRowAtIndexPath because the scrollViewDidEndScrollingAnimation may be called at other times for other scrolls.

Is there a reason you don't push the view controller in your -tableView:didSelectRowAtIndexPath: delegate method? That method should be called once the animation is complete.

Related

show modal view in parserDidEndDocument

I founded that the problem is the place where I'm calling the showNextView. I have another interface webService where i communicate with server and parse xml. When the parsing is finished with method parserDidEndDocument I'm calling the delegate method where is changed the view and show modal view. But when i call all that methods it will return to endDocument and xmlParseChunk and so on. It looks like the parserDidEndDocument is not realy the last method and somehow it mess with navigationcontroler. When i call the method for showig nextView with button it works.
The code which is working on button. In delegate method called from parserDidEndDocument is not working correct.
-(void)showNextView
{
UIViewController *nextView = [self.storyboard instantiateViewControllerWithIdentifier:#"vcTrabantInfo"];
[[nextView navigationController] setNavigationBarHidden:NO animated:NO];
[[self navigationController] pushViewController:nextView animated:YES];
UIViewController *picker = [[UIViewController alloc] init];
[picker setModalPresentationStyle:UIModalPresentationFormSheet];
[[self navigationController] presentModalViewController:picker animated:YES];
}
As usualy the problem was between keyboard and seat. The problem was that my modal views haven't been dismissed before i call another modal view :). So keep in mind that all is done in viewDidDisappear.

Trying To Call selectRowAtIndexPath Of My searchController.searchResultsTableView

What I'm trying to do is call selectRowAtIndexPath in my searchResultsTableView. I am doing this in my viewWillAppear so that the row gets selected after it comes back from another view controller. In this simple example, I'm just asking it to go to row 2. What happens is that it briefly selects row 2 and then the selection goes away. Any ideas?
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// just select row 2 as a test
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:2 inSection:0];
[self.searchController.searchResultsTableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[self.searchController.searchResultsTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:NO];
}
Thanks
Maybe you should call it in viewDidAppear:, so it's selected once the view is present, and not just before it's presented. The superclass of your controller might deselect all rows on viewDidAppear: (I'm not sure of that, though).
Also, I think you can do those two lines in just one:
[self.searchController.searchResultsTableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionTop];
Just go with viewDidLoad or viewDidAppear, and just use the single line told by EmilioPelaez.

problem with uitableview when return

i have viewcontroller with table view that when i click on a cell i go in the navigation to another view controller with another tableview.
i try to use the viewDidAppear and viewWillAppear in the first viewcontroller, that when i back to this viewcontroller from the second viewcontroller i will enter one of this method.
the problem is that he didn't enter this method when i return to this viewcontroller.
this is how i enter the second view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
ProfileViewController2 *tmp = [[ProfileViewController2 alloc]initWithType:indexPath.row string:[self.array2 objectAtIndex:indexPath.row]];
[[self navigationController] pushViewController:tmp animated:YES];
[tmp release];
[self.mytableview deselectRowAtIndexPath:indexPath animated:YES];
}
viewWillAppear and viewDidAppear are notoriously shaky on iOS.
If you are working with UITableViews we always put the code we need to run before the table gets loaded into the
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
function. It's a slight hack but works very well as this is the first function out of the UITableViewDataSource protocol called.
Alternatively, you can call
[tmp viewDidAppear];
After pushing the view controller and before releasing tmp to force the function being called.

Deselect row in UITableView which is a subview

I have a UITableView which is added to a view as a subview. When selecting a row a new view will be present with pushViewController: and the user has the option to push a Back-button to go back to the UITableView but then the cell is still selected.
Is there a way to deselect this when the view appears?
I have read that I should use the following code but I can't get it working. No errors, no warnings, no nothing.
[tableProgram deselectRowAtIndexPath:[tableProgram indexPathForSelectedRow] animated:NO];
You should deselect the row in the didSelectRowAtIndextPath method of the delegate of your UITableView. It should look something like this.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
/* initialize your view controller here v and then push it */
SomeViewController *v = [[[SomeViewController alloc] init] autorelease];
[self.navigationController pushViewController:v animated:YES];
}
Dot confuse it with didDeselectRowAtIndextPath method - it happens as people do not pay much attention when selecting methods from intelisense.
Another place you can use this is inside viewDidAppear
- (void)viewDidAppear:(BOOL)animated
and insert the following
NSIndexPath *indexPath = [tableView indexPathForSelectedRow];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
The cell stays selected after clicked and pushed to a different view controller, but deselects when it pops back to the original view controller, so it gives the user a visual cue for the last selected row.
quote SimonBS:
tried that and hoped it would work (to
get the animation) but it seems that
both viewDidAppear:(BOOL)animated and
viewWillAppear:(BOOL)animated aren't
called when in a subview.
the code by honcheng does work
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSIndexPath *indexPath = [self.tableViewOutlet indexPathForSelectedRow];
[self.tableViewOutlet deselectRowAtIndexPath:indexPath animated:YES];
}
you just need to use it on the TableView's superview. (For instance: if you have a xib file with a TableView subview inside the view you just need to go to the corresponding view controller code and override viewWillAppear, i just did it and it works! hello visual cue!)

Sending data from my detail view back to my UITableView

First of all, thank God for Stack Overflow. I am new at Objective-C/Cocoa/iPhone development, and this site is an amazing aid.
I have a window, that has a tab bar, that has a navigation controller, that loads a UITableViewController. Clicking an "Add" button on the navigation bar (created programatically), pushes a UITableViewController like so:
InvoiceAddViewController *addController = [[InvoiceAddViewController alloc] initWithNibName:#"InvoiceAddViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:addController animated:YES];
This table view controller pushes it's own detail view:
UITableViewCell *targetCell = [self.tableView cellForRowAtIndexPath:indexPath];
GenericTextFieldDetailViewController *dvController = [[GenericTextFieldDetailViewController alloc] initWithNibName:#"GenericTextFieldDetailViewController" bundle:[NSBundle mainBundle]];
dvController.fieldName = targetCell.textLabel.text;
dvController.fieldValue = targetCell.detailTextLabel.text;
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
The concept being, you click on a cell in the table view controller such as "Notes". This pushes "GenericTextFieldDetailViewController" with the name of the "field" you clicked, and the value (if one already exists). This allows me to reuse my detail view rather than creating one ad nauseum for every field.
In order to push data back, I created a method on the "Add" UITableViewController:
- (void) updateField:(NSString*) fieldName value:(NSString*) fieldValue
{
UITableViewCell *targetCell;
if([fieldName isEqualToString:#"Invoice ID"])
{
NSUInteger indexArr[] = {1,1};
targetCell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
targetCell.detailTextLabel.text = fieldValue;
}
else if([fieldName isEqualToString:#"P.O. Number"])
{
NSUInteger indexArr[] = {1,2};
targetCell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
targetCell.detailTextLabel.text = fieldValue;
}
else if([fieldName isEqualToString:#"Add Note"])
{
NSUInteger indexArr[] = {3,0};
targetCell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
targetCell.detailTextLabel.text = fieldValue;
}
}
This method is designed to receive the data I push with this method in "Generic":
- (IBAction)saveField:(id)sender
{
self.fieldValue = theTextField.text;
InvoiceAddViewController *parentController = (InvoiceAddViewController*)self.view.superview;
[parentController updateField:self.fieldName value:self.fieldValue];
}
Which brings us to the problem:
When the save method fires off, it throws an invalid selector error because self.view.superview is not the UITableView that pushed the "Generic" detail view.
I have tried the following combinations (from GDB):
(gdb) po [[self view] superview]
<UIViewControllerWrapperView: 0x4b6d4d0; frame = (0 64; 320 367); autoresize = W+H; layer = <CALayer: 0x4b6c090>>
(gdb) po [[self navigationController] parentViewController]
<UITabBarController: 0x4d2fa90>
(gdb) po [self parentViewController]
<UINavigationController: 0x4d2fdc0>
I feel like I'm landing all around the UITableView I want to invoke, but can't find it.
What am I doing wrong?
refrains from pulling more hair out
The problem is that you are confusing the view hierarchy for the navigation stack. Your detail view controller wants to send a message to the controller that pushed it on the stack, which is the second to last object in the navigation controller's viewControllers array.
Try changing your saveField: method to:
- (IBAction)saveField:(id)sender
{
self.fieldValue = theTextField.text;
NSArray *navigationStack = self.navigationController.viewControllers;
InvoiceAddViewController *parentController = (InvoiceAddViewController*)[navigationStack objectAtIndex:navigationSTack.count - 2];
[parentController updateField:self.fieldName value:self.fieldValue];
}
Edit: I should note this design is very brittle. A better way is to apply Model-View-Controller and create an object that represents a field and title value. Then your InvoiceAddViewController can pass instances of these objects to your detail controller, and as your detail controller changes them, these changes can be easily reflected in your other controllers.
Edit 2: Here is a hint of how it will work.
UITableViewCell *targetCell = [self.tableView cellForRowAtIndexPath:indexPath];
GenericTextFieldDetailViewController *dvController = [[GenericTextFieldDetailViewController alloc] initWithNibName:#"GenericTextFieldDetailViewController" bundle:[NSBundle mainBundle]];
dvController.dataObject = [self dataObjectForIndexPath:indexPath];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
I'm assuming you've implemented a dataObjectForIndexPath: method. Presumably tableView:cellForRowAtIndexPath: would also use this method to configure its cells.
Now, you can eliminate both the saveField: and updateField: methods. In your InvoiceAddViewController, viewWillAppear: could be used to refresh your view like this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData]
}
There is a whole world of possibilities here. reloadData is a very heavy handed one. Experiment with stuff like KVO to make this more automatic.
In your detail controller, of course, don't forget to update the object, say in view will disappear to do something like
self.dataObject.fieldValue = theTextField.text;
This is just to get you started. There are a lot of possibilities and details to consider. You should really look at a lot of sample code, this pattern gets used a lot. The CoreDataBooks example on the developer portal uses a similar pattern, there are almost certainly others.
You should be using notification, KVO or delegation. Use the design patterns that fit passing data around on this platform. Even if you can make the call directly using one of these patterns will most certainly prove to be a better way of achieving this in an encapsulated manner.
This approach is just screaming for delegation. Create a protocol on your detail view controller, declare a method like
- (void)genericTextFieldDetailViewController:(GenericTextFieldDetailViewController *)controller didUpdateValue:(NSString *)value forField:(NSString *)field
Now when you're ready to send the data back you would just send this message to your delegate (the pushing view controller).
[self.delegate genericTextFieldDetailViewController:self didUpdateValue:newValue forField:passedInField]
Make sure that when you create and push the detail view controller, you assign yourself as it's delegate and implement that method to handle the incoming values. I think you'll find this approach way more flexible.
I had the same problem, but not with a table view. I wanted to change a value from the last view controller. I'm using Xcode 5:
{
NSArray *navigationStack = self.navigationController.viewControllers;
ViewController *control = [navigationStack objectAtIndex:([navigationStack count] -2)];
control.pagecont.currentPage = self.currIndex;
CGRect frame;
frame.origin.x = control.scroll.frame.size.width * control.pagecont.currentPage;
frame.origin.y = 50;
frame.size = control.scroll.frame.size;
[control.scroll scrollRectToVisible:frame animated:YES];
[self.navigationController popViewControllerAnimated:YES];
}