Custom Segue Pushing/Popping UIViewControllers - ios

I'm trying to implement an iBooks-like flip transition as a storyboard. The segue should push resp. pop the destinationViewController onto/from the UINavigationControllers stack.
I can push viewcontrollers in my segues perform method but I am not able to pop. When I pop the controller right after creating my flip animation the animation does not run and its callback - that should perform [[UIApplication sharedApplication] endIgnoringInteractionEvents] gets never called and my App results dead.
So I tried to push/pop in the animationDidStop:anim:flag delegate method but it never gets called with the flag set to true.
I assume that the segue is deallocated before the delegate method gets called. What else could I do?

Forgive me if I am completely misunderstanding this question, but it seems like you just want to do a basic horizontal flip back and forth between two view controllers. And even if you've already figured this out, maybe it will help anyone else who has the same question.
(1) In your storyboard (that has ViewController A & B) create a Modal Segue from A to B. Give it an identifier (showViewControllerB) and choose Transition:Flip Horizontal.
We set up the protocol and delegates:
(2a) In ViewControllerB.h add above #interface:
#class ViewControllerB;
#protocol ViewControllerBDelegate
- (void)viewControllerBDidFinish:(ViewControllerB *)controller;
#end
(2b) Add the delegate as a property:
#property (weak, nonatomic) id <ViewControllerBDelegate> delegate;
(3a) In ViewControllerB.m synthesize:
#synthesize delegate;
(3b) And delegate in the method to flip back:
- (IBAction)flipBack:(id)sender
{
[self.delegate viewControllerBDidFinish:self];
}
(4) In ViewControllerA.h add at the very top #import "ViewControllerB.h" and on the end of #interface <ViewControllerBDelegate>
(5a) In ViewControllerA.m add the method to conform to the protocol:
- (void)viewControllerBDidFinish:(ViewControllerB *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
(5b) Then set it as the delegate in prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showViewControllerB"]) {
[[segue destinationViewController] setDelegate:self];
}
}
I hope this answers your question. If I misunderstood, just let me know.

Your question is a bit confusing as so mingle pop, push, flip and backflip. I´m not sure if ai can answer your question, but i can tell what i did.
If i push a viewController into the navigation controller stack and set the Storyboard Segue Style to Push, it will be pushed into the view from right to left. A back button appears and shows the title of the presentingViewController in it.
If i set the Storyboard Segue Style to Modal i can set the Transition to Flip Horizontal (what seems to be what you want). But then no Back Button will appear. In the presentedViewController i dismiss the view with:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
It will flip the second view back with a right flip.
But this is the dirty solution and it is not recommended by apple.
http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ManagingDataFlowBetweenViewControllers/ManagingDataFlowBetweenViewControllers.html#//apple_ref/doc/uid/TP40007457-CH8-SW9
Luke Dubert gave you an example how to implement the delegate.

Related

IOS/Objective-C/Storyboard: Detect when show segue underway

I am using a standard "show" segue in storyboard for a transition between two view controllers.
In the second VC, there is a method in viewWillAppear that fires a progress bar that is appearing during the segue and marring its appearance.
Is there any way to detect that the segue is in progress so that I can delay the progress bar until after the segue is complete?
I know I could move the progress bar to a later point in the lifecycle of the second view controller such as viewDidAppear but in most cases, the VC is reached without this particular segue and I would like the progress bar to fire immediately. If in the midst of the segue, however, I'd like to delay it.
Note: these are garden variety show Segues with animation in Storyboard, not custom segues.
You can define a public method for fire the progress bar and call it from first VC.
Here is the implementation of this approach:
In FirstVC.m:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// set segu identifier to SecondVC in StoryBoard
if ([segue.identifier isEqualToString:#"SecondVC"]) {
SecondVC *secondVC = (SecondVC *)segue.destinationViewController;
[secondVC fireProgressBar];
}
}
In SecondVC.h:
#import <UIKit/UIKit.h>
#interface SecondVC : UIViewController
-(void)fireProgressBar;
#end
In SecondVC.m:
#import "SecondVC.h"
#implementation SecondVC
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)fireProgressBar {
NSLog(#"Progress Bar Fired!!!");
}
#end

Navigate to RootViewController from ModalViewController

I have an app with 3 view controllers. A is the root, B is next and C is a modal view that appears / transitions when a button on B is clicked. What I want to do is sometimes move from C to A directly when a button on C is clicked (other times, the C->B transition should happen, which is ok). I've tried various options and incorporated info from different SO posts but in the end, stuck at the scenario where the direct transition from C to A happens but does show B momentarily.
Is there a way I can remove this momentary B appearance altogether. Also, I do need to have animations when the user goes from B to C or from C to B. It is the C to A transition that need not have an animation if there is a limitation there.
Hopefully you can chip in... have copied relevant code and further details below.
ViewControllerC.h:
#protocol ViewControllerCDelegate <NSObject>
- (void)didNavigateBackFromViewC:(ViewControllerC *)viewControllerC;
#end
#interface ViewControllerC : UIViewController
#property (nonatomic, weak) id < ViewControllerCDelegate > viewCDelegate;
#end
ViewControllerC.m:
- (IBAction)navigateToViewA:(id)sender
{
[self.viewCDelegate didNavigateBackFromViewC:self];
}
For the B->C and C->B transitions, I've used a separate transitioning controller that is a delegate of ViewControllerB. Also, ViewControllerB is a delegate of UIViewControllerTransitioningDelegate... I've not included those details for the sake of simplicity here but hopefully am able to convey the problem here about the C->A transition.
ViewControllerB.h:
#interface ViewControllerB () <ViewControllerCDelegate>
#end
ViewControllerB.m:
- (void)didNavigateBackFromViewC:(ViewControllerC *)viewControllerC
{
__block ViewControllerB *me = self;
[viewControllerC dismissViewControllerAnimated:YES completion:^{
[me.navigationController popToRootViewControllerAnimated:YES];
}];
}
really interesting question #vikram17000. First of all, you might want to try unwind segues, it seems that they can do what you want but I haven't tested that. It appered you were going the programmatic route, and here's what i found. I split (what I called) the 'back' and 'home' actions into two separate actions. Here's my ViewControllerC code:
-(IBAction)goBack:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
-(IBAction)goHome:(id)sender
{
[self.delegate ViewControllerCWillGoHome:self];
[self dismissViewControllerAnimated:YES completion:nil];
}
ViewControllerB is the delegate that gets the -...WillGoHome, and it popsToRootVC before VC C dismisses. This gets the behavior we want, C animates out and A is left - yay! Unfortunately there's a catch -
Unbalanced calls to begin/end appearance transitions for ViewControllerB: 0x7fc3c857ca80.
So is it possible? Yes. Does the os like it? No it does not. Hopefully this helps you get going though.
I've created sample project for your question.
There is protocol GoBackProtocol which contains method
-(void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button;
A and B controllers adopt GoBackProtocol. B controller implements -controller:didPressBackButton: method and decides based on button parameter what to do: hide presented controller with animation or hide it without animation and forward call to his delegate, which is controller A.
- (void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button
{
ViewControllerC *modalController = (ViewControllerC *)controller;
if (button == modalController.buttonB)
{
[controller.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
else if (button == modalController.buttonA)
{
[controller.presentingViewController dismissViewControllerAnimated:NO completion:nil];
[self.delegate controller:self didPressBackButton:nil];
}
}
Controller A implements -controller:didPressBackButton: as well and pops viewController B.
- (void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button
{
[self.navigationController popViewControllerAnimated:YES];
}
All Together it makes desired effect.
Please forget all custom protocol solutions here. Apple says:
The presenting view controller is responsible for dismissing the view controller it presented.
IMO you have at least two options here:
1) You could add the close button on C to public interface like
#property (nonatomic, strong, readonly) UIButton *closeButton;
Set the target differently and use a combination of dismissViewControllerAnimated:completion: and popToRootViewControllerAnimated: as in your method didNavigateBackFromViewC
2) You could use self.navigationController.presentingViewController in C to get access to the non-modal viewcontrollers.
As non-official option you could set the viewcontrollers array on a UINavigationController instance directly. Then you will get a Push Animation from C to A.
I haven't fully testing this out yet but results so far seem encouraging. Given that I wanted to be able to transition to B or C from A, and that B and C could navigate between each other, I've started playing around with making ViewControllerA as the container view controller for ViewControllerB and ViewControllerC. That allows me easy access to B / C from A, as well as the ability to transition between B and C seamlessly using custom transitions.

Receive gestures in a different view controller

I have a main view controller that is able to process gestures in an iPad app.
I launch a second view controller via:
wVC = [self.storyboard instantiateViewControllerWithIdentifier:#"vc_webView"];
[self presentViewController:wVC animated:YES completion:nil];
If I now gesture while that VC is showing, the gestures are not processed. How can I "pass" the gestures to the first storyboard for subsequent processing so that I don't need to rewrite the whole gesture functionality in the new VC?
OK so the answer is to use delegates.
For completion's sake, here is the way to do it:
Set up the first VC to be a delegate of the second, then in the second VC call the delegate function.
In original controller right before presenting the wVC:
wVC.delegate = self;
In the wVC.h file:
#protocol senddataProtocol <NSObject>
-(void)ProcessPasswordGesture:(NSInteger)iGest;
#end
#property(nonatomic,assign)id delegate;
In the wVC.m file:
#synthesize delegate;
[delegate ProcessPasswordGesture:<data>];
Hope this helps someone else!!

How can I pass data between two View Controllers in a Navigation Controller, with two Push Segue?

I have two view controllers inside a Navigation Controller.
In the first view controller I have two buttons. Both of them call the second view controller using a Push segue, but:
I need to know which button sent me in the second view controller. How?
In the second view controller I have a UIDatePicker and a Button "Ok": how can I send the chosen date to the first view controller when Ok is pressed? (And how do I receive them?)
EDIT:
I don't know if my problem is clear: now I know how to pass data from the first view controller to the second view controller with prepareForSegue, but what I really need is to pass data (the picked date) from the second view controller to the first, and how can I do it without a prepareForSegue (when Ok is pressed)?
EDIT2:
I made it. It was so simple, guys...
I decided to use modal segue:
Firstviewcontroller.h:
+(FirstViewController *)getInstance;
Firstviewcontroller.m:
static FirstViewController *instance =nil;
+(FirstViewController *)getInstance
{
return instance;
}
and in its ViewDidLoad:
instance = self;
Secondviewcontroller.m, in the OkButton IBAction:
SecondViewController *secondViewController = [SecondViewController getInstance];
//...
//modify what I need to modify in secondviewcontroller
//...
[self dismissModalViewControllerAnimated:YES];
That's it.
Thank you all anyway.
Assign Identifier to each segue in storyboard and implement
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"YOUR_SEGUE_NAME_HERE"])
{
// Get reference to the destination view controller
YourViewController *vc = [segue destinationViewController];
[vc setDelegate:self];
// Pass any objects to the view controller here, like...
[vc setMyObjectHere:object];
}
}
For more info about How to use storyboard and pass value check this article or this discussion on stackoverflow
for the second question you can use delegate pattern
IN SecondViewController.h
#protocol SomethingDelegate <NSObject>
- (void)dateChanged:(NSString *)dateStr; //you can use NSDate as well
#end
#interface ViewController2 : UIViewController
#property(weak) id<SomethingDelegate> delegate;
#end
in .m file
-(void) OkClicked{
[_delegate dateChanged:#"YOUR_DATE_VALUE"];
}
In FirstViewController.h
#import "SecondViewController.h"
#interface FirstViewController : UIViewController<SomethingDelegate>
in .m
-(void)dateChanged:(NSString *)dateStr{
// do whatever you need with dateStr
//also i made some change in prepareForSegue method
}
Note:- take care your naming convenes for VC
just pass the button id to the second viewcontrol.
use delegates to sent the data from second viewcontroller back to first view controller
regards
Johan

didSelectViewController does not get called on certain occasions

I have the problem that many already have reported, didSelectViewController doesn't get called, but in my case it sometimes gets called. I have three tabs and three view controllers. Every time user presses second or third tab I need to execute some code. In my SecondViewController and ThirdViewController I have:
UITabBarController *tabBarController = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
[tabBarController setDelegate:self];
Now everything works fine with the SecondViewController, the didSelectViewController gets called every time the second tab is pressed. Also in ThirdViewController didSelectViewControllergets called every time the third tab is pressed but only when second bar is meanwhile not pressed. So when I switch back and forth between FirstViewController and ThirdViewController everything is OK. But when I go in a pattern like first->second->third, then didSelectViewController doesn't get called in ThirdViewController. Also when I go like first->third->second->third didSelectViewController gets called in ThirdViewController the first time but not the second time. Any ideas?
It's hard to follow what exactly you are doing, but from what I understand you are responding to tab switches by changing the UITabBarController's delegate back and forth between SecondViewController and ThirdViewController.
If that is true, I would advise against doing this. Instead I would suggest you try the following:
Assign a delegate that never changes. For a start you could use your app delegate, but it would probably be better if you had a dedicated small class for this. I am sure that now you have a non-changing delegate, it will get 100% of all the calls to tabBarController: didSelectViewController:.
The object that is the delegate must have a reference to both the SecondViewController and ThirdViewController instances. If you are designing your UI with Interface Builder, you might do this by adding two IBOutlets to the delegate class and connecting the appropriate instances to the outlets.
Now when the delegate receives tabBarController: didSelectViewController: it can simply forward the notification to either SecondViewController or ThirdViewController, depending on which of the tabs was selected.
A basic code example:
// TabBarControllerDelegate.h file
#interface TabBarControllerDelegate : NSObject <UITabBarControllerDelegate>
{
}
#property(nonatomic, retain) IBOutlet SecondViewController* secondViewController;
#property(nonatomic, retain) IBOutlet ThirdViewController* thirdViewController;
// TabBarControllerDelegate.m file
- (void) tabBarController:(UITabBarController*)tabBarController didSelectViewController:(UIViewController*)viewController
{
if (viewController == self.secondViewController)
[self.secondViewController doSomething];
else if (viewController == self.thirdViewController)
[self.thirdViewController doSomethingElse];
}
EDIT
Some hints on how to integrate the example code from above into your project:
Add an instance of TabBarControllerDelegate to the .xib file that also contains the TabBarController
Connect the delegate outlet of TabBarController' to the TabBarControllerDelegate instance
Connect the secondViewController outlet of TabBarControllerDelegate to the SecondViewController instance
Connect the thirdViewController outlet of TabBarControllerDelegate to the ThirdViewController instance
Add a method - (void) doSomething to SecondViewController
Add a method - (void) doSomethingElse to ThirdViewController
Make sure that you don't have any code left in SecondViewController and ThirdViewController changes the TabBarController delegate!
Once you are all set and everything is working fine, you will probably want to cleanup a bit:
Change the names of the notification methods doSomething and doSomethingElse to something more sensible
If you followed the discussion in the comments, maybe you also want to get rid of the secondViewController and thirdViewController outlets
I too had this problem and got fed up with it. I decided to subclass UITabBarController and override the following methods. The reason I did both was for some reason on application launch setSelectedViewController: wasn't being called.
- (void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
// my code
}
- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
// my code
}
I just dug through this tutorial on storyboards, and I thought of an alternative to using UITabBarControllerDelegate. If you want to stick to UITabBarControllerDelegate then feel free to ignore this answer.
First, create a subclass of UITabBarController, let's call it MyTabBarController. In the storyboard editor you need to change the "Class" property of the tab bar controller so that the storyboard picks up your new class.
Add this code to MyTabBarController.m
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SecondVC"])
{
SecondViewController* secondViewController = (SecondViewController*)segue.destinationViewController;
[secondViewController doSomething];
}
else if ([segue.identifier isEqualToString:#"ThirdVC"])
{
ThirdViewController* thirdViewController = (ThirdViewController*)segue.destinationViewController;
[thirdViewController doSomethingElse];
}
}
In the storyboard editor, you can now select the two segues that connect to SecondViewController and ThirdViewController and change the segue identifier to "SecondVC" and "ThirdVC", respectively.
If I am not mistaken, that's all you need to do.

Resources