prepareForSegue is not called after performSegue:withIdentifier: with popover style - objective-c

I have a universal app, where I am sharing the same controller for a IPad and IPhone storyboard.
I have put a UILongPressGestureRecognizer on a UITableView, that when a cell is pressed on iPhone it calls an action that perform a segue:
-(IBAction)showDetail:(id)sender {
UILongPressGestureRecognizer *gesture = (UILongPressGestureRecognizer*)sender;
if (gesture.state == UIGestureRecognizerStateBegan) {
CGPoint p = [gesture locationInView:self.theTableView];
NSIndexPath *indexPath = [self.theTableView indexPathForRowAtPoint:p];
if (indexPath != nil) {
[self performSegueWithIdentifier:SEGUE_DETAIL sender:indexPath];
}
}
}
the segue is a detail view performed as a 'push'. The first thing you should notice is that the sender is an NSIndexPath, is the only way I found for passing the selected cell. Maybe there's a better solution.
Everything works fine, in a sense that the segue is performed, and before the prepareForSegue is called too.
However it happens that on iPad, I have changed the segue identifier to Popover.
Now things are working in part, the segue is performed, but prepareForSegue is not called and therefore the destination view controller is not set up as it should be.
What am I doing wrong ?

What I have discovered so far, is that with any segue identifier that is not popover these are the invocations made by iOS:
prepareForSegue (on source controller)
viewDidLoad (on destination controller)
while in popover segue the invocation order is:
viewDidLoad (on destination controller)
prepareForSegue (on source controller)
just because I put all my logic in viewDidLoad, the controller was not properly initialized, and a crash happened. So this is not exactly true that prepareForSegue is not called, the truth is that I was getting an exception, and I wrongly mistaken as prepareForSegue not getting called.
I couldn't put everything in viewWillAppear because a call to CoreData had to be made and I didn't want to check if entities were ok each time the view display.
How did I solve this ? I created another method in destination controller
-(void)prepareViewController {
// initialization logic...
}
and changing the prepareForSegue method in source controller itself:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
MyViewController *mvc = (MyViewController*)[segue destinationViewController];
// passing variable
// with segue style other than popover this called first than viewDidLoad
mvc.myProp1=#"prop1";
mvc.myProp2=#"prop2";
// viewWillAppear is not yet called
// so by sending message to controller
// the view is initialized
[mvc prepareViewController];
}
don't know if this is expected behavior with popover, anyway now things are working.

I've noticed that the boiler plate code for Xcode's Master-Detail template (iPhone) uses the following pattern for configuring the detail VC's view:
the detail VC's setters (for properties) are overwritten in order to invoke the configureView method (configureView would update all your controls in the view, e.g., labels, etc.)
the detail VC's viewDidLoad method also invokes the configureView method
I did not follow this pattern the other day when I was trying to re-use a detail VC in my movie app, and this gave me trouble.
I don't have much experience with popovers; however, if the pattern above is used with a detail VC that is displayed inside a popover, then wouldn't the detail VC's view get configured when you set the detail VC's properties from within the prepareForSegue method?

Related

Pass variable to view controller before viewDidLoad

I perform a segue which is defined in the Storyboard to open a new view controller. I need to configure the destination view controller of the segue in a special state where some of it's buttons does not needed to be displayed.
I know that I can do this by setting a variable on this controller in my source view controller's -prepareForSegue:sender:. The problem with this is, that firstly it instantiates the controller, so it's -viewDidLoad: will run, then only after can I set anything on it.
I can't create the controller entirely from code, because it's user interface is in Storyboard. -instantiateViewControllerWithIdentifier: also calls -viewDidLoad first obviously.
I could probably use a semaphore and add the initialization code into my destination controller's -viewWillAppear, but that's ugly, it has to be some more elegant way to do this than doing a check every time the view appears. (My settings need to be done only once.)
Is there some way to pass variables into the controller before it's -viewDidLoad runs?
EDIT: It looks like this happens only if I trigger the segue from code using -performSegueWithIdentifier:.
On my machine and on iOS 8.0 and iOS 9.0, viewDidLoad is called after prepareForSegue. So, something like the following worked for my test case of your answer.
In your source controller:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
TimViewController * controller = segue.destinationViewController;
if( [controller isKindOfClass:[TimViewController class]] )
controller.name = #"tim";
}
In your destination controller (TimViewController):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do view setup here.
NSLog( #"view did load %#", self.name );
}
Add a segue (a show segue) from your source control to the destination view controller.
Output:
2015-09-17 19:09:04.351 Test[51471:7984717] view did load tim
I think there is some confusion here. -prepareForSegue:sender: gets called before -viewDidLoad gets called. Please double check your implementation.
Edit:
May be this thread will help you understand this and one of the mentioned cases fall in your case.

prepareForSegue not being called from UITableView in a UIViewController

I have a UIViewController that contains a UITableView. The table view contains a custom UITableViewCell. The custom cell was built in interface builder and has a nib. In my main storyboard, I dragged a segue from the custom table view cell to the destination view controller. I set up the bare bones essentials in prepareForSegue, set a break point, but it never gets called.
I'm not that accustomed to using a UITableView in a view controller. I usually use a UITableViewController, but requirements dictate using the table view in a view controller. My initial assumptions is that most methods of doing things would be nearly identical, but I'm finding that not to be the case.
I tried setting the segue from the view controller itself and using didSelectRowAtIndexPath, and though it worked, the transition to the destination view controller was jerky.
Can anyone suggest what I might be missing in order to cause the prepareForSegue method to fire?
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
GaugeViewController *destination = [segue destinationViewController];
[destination setGaugeID:#"1"];
}
Thanks!
You need to refer to the identity of the segue in the Storyboard, something like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
GaugeViewController *destination = segue.destinationViewController;
if([segue.identifier isEqualToString:#"yourSegue"]) {
NSLog(#"prepareForSegue called");
[destination setGaugeID:#"1"];
}else {
// do something else
}
}
Also don't forget to set the Identifier in the Storyboard.
Remember that push segues are used with Navigation Controllers and a modal segue can be dragged from view controller to view controller.

How to make viewWillAppear to be called when we use a subclass of UITableViewController to handle table delegate?

I want to make my code more modular and flexible.
So rather than setting a the tableViewDelegate as the UIViewController, I have a subclass of UITableViewController as the tableView data source and delegate.
Basically, the original UIViewController provides the view for the subclass of UITableViewController.
That way similar tables can be used by several UIViewController's subclasses.
In some cases, UIViewController's provide the tableView and I just switch the tableView delegate at run time.
Works well.
Here is the code for BGTableViewDelegateMother that inherits from UITableViewController (that inherits from UIViewController.
#implementation BGTableViewDelegateMother
-(void) setDelegate:(id<BGTableViewDelegateMother>)delegate
{
_delegate=delegate;
self.view = self.delegate.tvDelegated; //So that viewWillAppear would work fine
[self view]; //load the view view didload is not called either
self.delegate.tvDelegated.delegate =self;
self.delegate.tvDelegated.dataSource=self;
}
Okay. The UITableViewController.view is used for one thing. Now that it points to the correct tableView, I expect viewWillAppear to be called. It's not
I think everytime the tableView will be shown, I should at least reloadData
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];//never called
[self.delegate.tvDelegated reloadData];
}
This code never called. Even though the view will indeed appear. Why?
viewDidLoad is also never called.
Make the table view controller a child view controller of your view controller while its view is linked (and it is the data source) and could be on screen. This tells the view controller hierarchy that the table view needs to know about the appearance callbacks.

Passing NSIndexPath to New View

I am having real troubles passing an NSIndexPath to my new view. This is how the app works:
I have a UIBarButtonItem in my nab br, tap that and you get a popover view, this shows a bunch of stuff. I need to get an NSIndexPath from my main view, to this popover view.
I have a property for the NSIndexPath in my popover view class and the popover transition is connected up in my storyboard.
Then I have this code to pass the index path across views:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"statsPopover"])
{
StatsViewController *statsVC = [segue destinationViewController];
statsVC.selectedIndex = stageSelectionTable.indexPathForSelectedRow;
}
}
However, while this gets called, the index path isn't actually sent between views. My index path on the popover is always the default, 0,0 row and section.
You say you know from debugging/logging that the method is running, your if statement is triggered, and stageSelectionTable.indexPathForSelectedRow has the value you expect.
Doing that kind of diagnosis puts you on the right path to solving your issue. Keep at it -- if you test some other things with NSLog or the debugger you should be able to find the problem.
Do you know that statsVC is non-nil? If it's nil, your message to set selectedIndex does nothing.
Is statsVC the class you expect? If, say, the (table?) view in your popover is embedded in a navigation controller, segue.destinationViewController will point to the nav controller, and you'll need to look into its child view controllers array to find the one you're looking for.
Is whatever accessor method your StatsViewController class is using for its selectedIndex property working right? (This shouldn't be a problem if it's a synthesized accessor for a strong/retain property.)
In your StatsViewController class, are you trying to work with selectedIndex before it actually gets set? Setting a property in prepareForSegue:sender: and then using it in viewDidLoad should work fine, but using it in awakeFromNib might not, and using it in an init... method definitely won't.

presentModalViewController on Parent from UITableView inside UIViewController

This one is probably something simple, still learning the ins-and-outs on this but I've run out of searches for this one with no available answer.
I've got a UIViewController with several elements displayed on it, one such element is a UITableView. The UITableView has it's own class and is allocated in the UIViewControllers viewWillAppear
- (void)viewWillAppear:(BOOL)animated
{
UITableView *insideTableView = [[UITableView alloc] init];
tableView.delegate = insideTableView;
tableView.dataSource = insideTableView;
}
Everything is working fine in regards to the tableview. Today I am experimenting with a few additions, one of which is a new view popup on cell selection within that tableview.
Inside my TableView Class, I have the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Cell Pressed, Present View");
PopupView *popupView = [[PopupView alloc] initWithNibName:#"PopupView" bundle:nil];
popupView.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:popupView animated:YES];
}
Now it gets called fine, verified by the NSLog, however the view doesn't appear. I know the problem is related to the fact that I want PopUp to appear over the TableViews Parent rather than itself.
I'm just not sure how to properly call it in this instance.
The delegate is a UIViewController which doesn't have its view property set, which is why presentModalViewController:: doesn't work.
You need the view controller containing the table view to present the modal view controllers, but note that that view controller is not the parent of the table view delegate. This is because you have no view controller hierarchy in place.
The easiest way to fix this is to put those methods inside the view controller whose view contains the table view. Alternatively the table view delegate needs to hold a reference to the view controller so it can call presentModalViewController:: on it.
The latter approach can lead to retain cycle, so you have to use a non-retaining reference. The nicest implementation is the delegate pattern.
Also, you don't want to do the instantiation in viewWillAppear: because that can be called multiple times during the lifecycle of a view controller. Put the code in viewDidLoad and balance it in dealloc. Right now you are leaking memory every time your view appears, which when your modal view controller is working will be every time the modal view controller is presented and dismissed.