How to specify the segue programmatically (NOT perform segue)? - cocoa-touch

AView.m
UIButton *btn = [[UIButton alloc]initWithFrame()];
[btn addTarget:self action:#selectior(perpareForSegue:sender:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
//I want to get destinationViewController of segue with identifier"toNextView"
// and then perform the segue
// Difficulties that I encounter is that I have a NSInvalidArgumentException
// Is that mean the view controller doesn't know the existence of the segue?
// I have connected two controller with a segue
}
I want to get destinationViewController of segue with identifier"toNextView"
and then perform the segue
Difficulties that I encounter is that I have a NSInvalidArgumentException
Is that mean the view controller doesn't know the existence of the segue?
I have connected two controller with a segue.

Take a look at the class reference of UIStoryboardSegue.
The method prepareForSegue:sender: is called by the storyboard runtime when a segue is triggered. It is not meant to be called to perform the segue.
Here is the explanation from the class reference:
When a segue is triggered, but before the visual transition occurs,
the storyboard runtime calls the current view controller’s
prepareForSegue:sender: method so that it can pass any needed data to
the view controller that is about to be displayed.
From the class reference:
You can still initiate a segue programmatically using the
performSegueWithIdentifier:sender: method of UIViewController if you
want. You might do so to initiate a segue from a source that was added
programmatically and therefore not available in Interface Builder.
So, just specify the selector for the button and then inside the selector just call performSegueWithIdentifier:sender: method.
[btn addTarget:self action:#selector(whateverAction) forControlEvents:UIControlEventTouchUpInside];
- (void)whateverAction {
[self performSegueWithIdentifier:#"toNextView" sender:nil];
}
And if you want to pass an object to the destination view controller of the segue, that's where prepareForSegue:sender: method comes into play as explained in the class reference, for example:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toNextView"]) {
DestinationViewController *vc = (DestinationViewController *)[segue destinationViewController];
//do whatever you want to do with vc, including passing of data, setting property, etc.
}
}

Related

iOS 8 UISplitViewController delegate not responding

I'm transitioning my app to iOS 8, and I've decided to use a SplitViewController because its new functionality finally allows me to do what I want. I present the SVC modally on iPad, and the SVC is the root view controller of the full-screen cover vertical transition. From the presenting view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
plotSplitViewController = segue.destinationViewController;
plotViewController = (PlotViewController *)[[[segue.destinationViewController viewControllers] objectAtIndex:1] topViewController];
plotViewController.inventory = _inventory;
if ([plotViewController view]) [plotViewController setPlot:selectedPlot];
}
Then I manually make connections in the PlotSplitViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
// set up controllers
layoutNavigationController = [self.viewControllers objectAtIndex:1];
plotViewController = (PlotViewController *)[layoutNavigationController topViewController];
plotViewController.delegate = self;
// configure split view
[self showInfoPane:NO withTable:infoTableViewController];
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
}
So both the master and the detail get view controllers inside UINavigationControllers (to take advantage of the free toolbar resizing, plus the master pushes a table view hierarchy).
Everything seems fine; the views load as they're supposed to, the delegate method from PlotViewController functions correctly, etc. But as you can see, I assign the Split View Controller to be its own delegate...but it won't respond to any of its own methods, so I can't customize its behavior. I checked to make sure it's set correctly:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"split view %# did appear, delegate: %#", self, self.delegate);
}
And it returns the same object (itself) for both values. Is this just a no-no? I read that you can assign a SplitViewController as its own delegate, and I think an object can be a delegate for more than one other object, right? It can certainly implement protocols for more than one. So why is my SplitViewController not able to receive delegate methods for itself? I have NSLogs in all of them, and none are ever called.
Turns out it works if you make your custom view controller a subclass of UIViewController, and add a UISplitViewController programmatically, as a child view controller. Make your VC its delegate, and you can make it behave however you want.

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 override the UIBarButtonItem triggered segue defined in a storyboard

I have a UIViewController in a storyboard iPhone app that I need to reuse in several places. In the storyboard, there is a UIBarButtonItem that has a triggered segue to another controller. In some cases, however, I would like to override the behavior of the button to segue to a different view controller. I'm thinking that there must be a way to either initialize the view controller with a message that specifies the target view controller, or to set some property after the controller is initialized but before it's pushed?
One challenge seems to be that segues can't be defined programmatically (based on what I've read so far), and I don't think I can have multiple segues on the same view controller in storyboard. So I may need to do something like this:
[self presentModalViewController:myNewVC animated:YES];
... rather than use a segue, but I'm not sure how to override the behavior of the button defined in storyboard to do it that way.
Any help is appreciated.
Just create an IBAction and a BOOL for some condition to pick which view controller should be instantiated.
UIViewController *viewController;
if (someCondition) {
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"someViewID"];
}else{
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"someOtherID"];
}
[self presentViewController:viewController animated:YES completion:nil];

prepareForSegue is not called after performSegue:withIdentifier: with popover style

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?

Calling a method in a UIViewController from a UIButton in a subview

Still learning about Objective C and getting the structure right.
I have an iOS App with a UIViewController that has a defined method named "doSomething". In my view controller I have a view and in that view a number of UIButton that I create programmatically (see example below with one button).
Now when I press the button I want to call my method "doSomething". The way I currently do it is like this:
[myButton addTarget:nil
action:#selector(doSomething:)
forControlEvents:UIControlEventTouchUpInside];
Since my target is nil it goes up the responder chain until it finds a method called "doSomething". It works, but it does not really feel right.
I have started to look into using #protocol but not really got my head around it. I have been looking at some tutorials but for me it is not clear enough. I have used protocols like for the table view controllers, but defining one is new for me.
Would it be possible to get an example for this specific case?
Thanks!
As your target pass in the view controller and the method will be called on that object.
Edit:
[myButton addTarget:controller
action:#selector(doSomething:)
forControlEvents:UIControlEventTouchUpInside];
Assuming that you have a variable called controller that is your UIViewController. If you don't have a reference to your controller then simply pass one to your view.
Edit2:
View interface:
#property (assign) UIViewController* controller;
View implementation:
#synthesize controller;
Controller:
- (void) viewDidLoad {
[super viewDidLoad];
someView.controller = self;
}
The way I'd do it is set the value of addTarget to self.
[myButton addTarget:self
action:#selector(doSomething:)
forControlEvents:UIControlEventTouchUpInside]
This will look for the method doSomething on the object that the target is added in. In this case, this would be the view controller.