UIStroyboard segue issue - objective-c

I created story board App. Actually I have issuw with segue. I have one "A" view controller, and segue is suppose to push "B" viewcontroller. In "A" viewController, we are expecting some userInfo and we are performing some validations for that, and based on validation only it should push new viewController through segue. My issue is, I used triggered segue and it always push "B" view controller. Can you please inform me based on validations, how can I disable segue.

Connect the button from Scene A. Not from a UI Element on Scene A, but from the base view of Scene A.
Now, in Scene A's view controller, if you've got a button that's supposed to validate, just use an if or whatever. If all the info is good, call the segue, if it's not, don't call the segue.
Call the segue like this:
[self performSegueWithIdentifier: #"MySegue" sender: self];
Where #"MySegue" is replaced with the name you gave your segue on the storyboard.
For example:
- (IBAction) myButtonPress:(id)sender {
//validate the info
if(valid) {
[self performSegueWithIdentifier: #"MySegue" sender: self];
} else {
//probably prompt the user that their info is invalid
//maybe clear textboxes, etc.
}
}
They key here though is attaching the segue from the viewController, and not from the button itself. You also have to be sure to name your segue, and be sure the name on storyboard matches the name in code. XCode won't help you autocomplete it.

Related

Storyboard - Popping to a View Controller then Pushing Causes multiple pushes

In Objective C / iOS;
We have a process similar to this (set up in xcode storyboard);
Menu View Controller -> Enter in a code -> Process/Validate -> Present a Failed Code page
The (->) arrows signify a push segue setup in storyboard
When in failed state I want to pop to the Enter in a code view controller.
UIViewController *vc = nil;
NSUInteger index=0;
for (UIViewController *viewController in self.navigationController.viewControllers) {
if ([viewController isKindOfClass:[SomeViewController class]]) {
vc = viewController;
break;
}
index++;
}
if (vc) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.navigationController popToViewController:vc animated:YES];
});
return;
}
This pops me back to the VC I want to go to.
Except now when I press a submit on the Enter code page it does 3 or 4 more "pushes", when it should only be 1.
Do I need to unwind the segue? I tried emptying the navigational view controller stack, and I even tried ridding it of its last active view controller -- both of these return a blank or black window view frame.
Why would popping a view controller in the navigation stack affect the segues in my view controller to the point where whenever I try to do a push segue action it will try to push multiple view controllers onto the stack?
Turns out I had the following issue
Button press causes segue action in storyboard
I did the same segue action in code on the button, hence pushing multiple times
I have now solved this issue

How to interrupt a modal segue?

I created modal segues with xcode's interface builder using storyboards. So I don't have any code about these segues.I wanna stop one of these modal segues if "BOOL permission = NO". If "BOOL permission = YES" modal segue will work normally. How can I do that ?
You can't interrupt a segue but what you can do is choose whether to call it or not.
Instead of having the segue start from a button (for example) you should have it start from the view controller. Then give the segue an identifier "myModalSegue".
Then connect a method to the button...
- (IBAction)segueButtonPressed
{
if (user.hasPermission) {
[self performSegueWithIdentifier:#"meModalSegue" sender:nil];
}
}
Now that button will only perform trigger the segue if the user has permission.
In storyboard you can give your segues an identifier by clicking on the segue line, going to the attributes inspector and entering an identifier, for example mySegue.
In your code you can then check if the user has permission by connecting your button to an IBAction, and checking the permission bool.
-(IBAction)mySegueButtonPressed {
if (permission) {
[self performSegueWithIdentifier:#"mySegue" sender:nil];
}
}

Object becomes Nil when pushing controller

I have a the first application controller, MAViewControllerMenu, and when that controller loads, I already allocate the next controller, imageControllerView.
- (void)viewDidAppear{
[super viewDidAppear:(YES)];
if (!imageControllerView)
imageControllerView = [[self storyboard] instantiateViewControllerWithIdentifier:#"chosenImageController"];
}
Then, I select an image from the image picker, and want to move to the next controller,imageControllerView, where the image would be displayed. I set the next window's image property as follows:
imageControllerView.image = [[self.pageViews objectAtIndex:(centered_image_ind)] image];
This line works, I checked that there's a value in imageControllerView.image.
However, when I move to the next controller,imageControllerView , I notice that the memory address of imageControllerView changes, or in other words, imageControllerView's properties that I change before moving to that controller, specifically image, reset when I move there.
Instead of throwing code here, I was hoping you could let me know what I should provide.
I think it's a common problem people know of:
Controller's objects re-init'ing when moving from one controller to another.
Here's a screen shot that might give a hint of what Im trying to do
Left most one is where I select pictures which in turn go into the slide show scrollview. Then I click next, and the image is supposed to appear in the centered ImageView
Thanks
OK...
You cannot "already allocate the next view controller" this won't work. There is no point in creating it like this at all. You can delete the imageViewController property (or iVar) completely.
The arrows that you have between the view controllers in your storyboard are segues. In Interface Builder you can select a segue and give it an identifier. For instance you would use something like #"ImageViewSegue".
I guess the segue is attached to the Next button. This is fine.
Now, in your MAViewControllerMenu you need to put this method...
- (void)prepareForSegue:(UIStoryBoardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ImageViewSegue"]) {
// the controller is CREATED by the segue.
// the one you create in view did load is never used
ImageViewController *controller = segue.destinationController;
controller.image = [[self.pageViews objectAtIndex:(centered_image_ind)] image];
}
}
Now for the segues in the other direction...
You appear to be using segues to dismiss the modal views. You can't do this. What it will do is create a new view controller and present that instead of dismissing the presented view.
i.e. you'll go...
A -> B -> C -> B -> A -> B
// you'll now have 6 view controllers in memory
// each segue will create a fresh view controller with no values set.
What you want is...
A -> B -> C
A -> B
A
// now you only have one view controller because the others were dismissed.
// when you dismiss a view controller it will go back to the original one.
// the original one will have all the values you set previously.
To do this you need to create a method something like...
- (IBAction)dismissView
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Then whatever the button is for your dismiss action attach it to this method.
Now delete all the backwards curly segues.
Passing info back
To pass info back to the original view controller you need a delegation pattern or something similar.
You can read more about creating a delegate at This random Google Search
Create a delegate method something like...
- (void)imageViewSelectedImage:(UIImage *)image;
or something like this.
Now when you do prepareForSegue you can do...
controller.delegate = self;
and have a method...
- (void)imageViewSelectedImage:(UIImage *)image
{
// save the method that has been sent back into an array or something
}
I might be wrong, but seems you go to your second view controller using a segue, it is normal your controller instance isn't the same than the one retrieved by [[self storyboard] instantiateViewControllerWithIdentifier:#"chosenImageController"]
you should take a look at - (void) prepareForSegue:(UIStoryboardSegue *)segue
(UIViewController method)
inside this method set your image property to the segue destination controller (check the identifier of the segue)

How do I cancel a segue that is connected to an UIButton

I'm working on this tutorial, which shows how to create an app with storyboards. My viewController has two UIButtons and I have connected a segue to those UIButtons. These segues push a new viewController.
Now I am looking for a way to cancel this segue if a certain condition becomes true. When I used the old xib files to create my interface I was able to do something like this:
-(IBAction)sendButton:(id)sender {
if(TRUE) {
// STOP !!
}
}
This does not work anymore. How can I cancel the push of the new viewController?
Nope. You can't cancel segues that are directly linked to interface elements like your UIButton.
But there is a workaround.
Remove the segue that is linked to the button
Add a segue from the viewController (the one with the button) to the viewController that should be pushed. This must be a segue that is not connected to a interface element. To do this you can start the segue from the status bar. Or create the segue in the left sidebar.
Select this segue, open the attributes inspector and change the Identifier of the segue (e.g. PushRedViewController)
Select Xcodes assistant view, so you can see the .h file of the viewController with the button.
Connect the button to an action. To do this select the button and control-drag to the .h file. Select action in the menu and name your action (e.g. redButtonPressed:)
Open the implementation of your viewController
change the action from step 5 to something like this:
- (IBAction)redButtonPressed:(id)sender {
[self performSegueWithIdentifier:#"PushRedViewController" sender:sender];
}
You can actually do this by adding following method into your code
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if([identifier isEqualToString:#"btnSegue"])
{
return YES;
}
else{
return NO;
}
}
Lets assume your segue is btnSegue.And if you need the segue to be performed based on some condition you can have following code
if(check)
{
[self performSegueWithIdentifier:#"btnSegue" sender:self];
}
Where check is BOOL which you can set true or false based on your condition.

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?