How do I use SWRevealViewController with unwind segues? - objective-c

I have a sequence of views that are pushed on top of each other on a Navigation Controller. I would like to do two things with these views:
To open the Rear menu view from all of them;
To be able to navigate
back the stack using Unwind segues.
I found out that if I push view controllers on top of each other using regular push segues then the unwind segue works as expected, but then the self.revealViewController on each view controller is not set and the Menu can't be called using the revealToggle: selector.
If I change the push segues to subclasses of SWRevealViewControllerSeguePushController then the views are pushed on top of each other and the Menu can be called from any of them using the revealToggle. Unfortunately, the unwind segues stop working (I think this might be because the view controllers are stacked using addChild instead of pushViewController on the SWRevealViewController class).
Is there a way of working together with SWRevealViewController and Unwind Segues?
Below there is an example storyboard:
The first view controller is the navigation controller; the second is the SWRevealViewController; the three view controllers below navigate one to each other, and the third has an unwind segue to the first. The first and third controllers have buttons that open the menu.
As I stated before, if the segues between the bottom view controllers are regular push segues then the unwind segue works as expected; the menu button from the first view controller works (as it is connected directly to the SWRevealViewController), but the menu button from the third view controller doesn't.
Switching the segue types to SWRevealViewControllerSeguePushController makes the menu buttons from the first and third view controllers work correctly, but the unwind segue stops working.
Oh, and I tested using "popToRootViewControllerAnimated:" and it also doesn't work if the segues are set to SWRevealViewControllerSeguePushController.

I posted this question on SWRevealViewControllers github site and received an answer from Patrick Bodet who was EXTREMELY helpful. I will post the answer below so it may help somebody in the same situation as I.
I had to update the storyboard and added an additional navigation controller as shown below.
As shown in the figure, I wanted to be able to push view controllers on top of each other and also to unwind the segues both to the Login screen (from the Menu) and from stacked view controllers.
On my previous attempts, it seemed as if SWRevealViewController wasn't able to cope with proper navigation segues. Patrick's first suggestion was to move the original navigation controller from before the RevealViewController to before the First view controller. That actually worked, by I still needed to be able to unwind segue from the Menu to the Login screen, so I needed an additional navigation controller.
As suggested by Patrick, I added an additional Navigation Controller. And embarrassingly at the end I realised the button that pointed from the third to the first view controller had both an ibaction and a segue to the first, so that was why it was acting all weird! :-(
So, for the storyboard shown above, in order to work you just use regular Push segues for the view controllers. No need to use SWRevealViewControllerSeguePushController segues.
The code for First and Third view controllers is like this:
#import "ThirdViewController.h"
#import "SWRevealViewController.h"
#import "FirstViewController.h"
#interface ThirdViewController ()
#property (weak, nonatomic) IBOutlet UIBarButtonItem *menuButton;
#end
#implementation ThirdViewController
- (void)viewDidLoad {
[super viewDidLoad];
SWRevealViewController *revealViewController = self.revealViewController;
if (revealViewController) {
[self.menuButton setTarget: revealViewController];
[self.menuButton setAction: #selector( revealToggle: )];
}
}
- (IBAction)returnToFirst:(id)sender {
[self performSegueWithIdentifier:#"First" sender:self];
//[self.navigationController popToRootViewControllerAnimated:YES];
}
#end

Related

How do I present a UIViewController modally when a button on it's parent view controller is tapped?

I'm having a little issue with an app I've completed. The problem is with a search view controller presented modally.
I have a search button that when tapped presents the SearchViewController modally.
I have 3 different controllers it can be presented from.
MainViewController > CollectionViewController > DetailViewController
This is actually how the controllers are in the hierarchy.
The results are always displayed on the collection view controller. Basically the collection view is refreshed and the remaining cells show are the result of the search.
Searching from collection view controller:
In the collection view controller, when the search is tapped. The search view controller is presented modally. Search text is entered and a list of matches is shown. When a row is tapped then the search view controller is dismissed and notifies the collection view controller using delegation the collection view is them refreshed with the results.
Searching from detail view controller:
In the detail view controller, when the search is tapped. The detail view controller is popped off the stack revealing the collection view controller. I use delegation to notify the collection view controller that the detail view controller was popped of the stack after the tap of a search button. Immediately the search view controller is opened making it possible to search as if we originally presented the search view controller from the collection view controller.
My issue arises when trying to search from my main view controller. Right now things are working but it doesn't have a very professional feel to it. Let me explain.
To get search working from my main view controller I use delegation. So in the method connected to the search button I perform this segue:
- (void)searchButtonTapped
{
[self performSegueWithIdentifier:#"garmentsCollectionSegue" sender:nil];
}
The protocol is defined in my interface file:
#class VAGMainTableViewViewController;
#protocol VAGMainTableViewControllerDelegate <NSObject>
- (void)mainTableViewControllerDisappearedwithTitleForObject:(NSString *)titleString;
- (void)searchButtonOnMainTableViewControllerTapped;
#end
#interface VAGMainTableViewController : UITableViewController
#property (nonatomic, weak) id<VAGMainTableViewControllerDelegate> aDelegate;
In my preparation before segue I have this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
[self setADelegate: [segue destinationViewController]];
[[self aDelegate] searchButtonOnMainTableViewControllerTapped];
}
Here I set the delegate and also make a call to the delegate method, making the collection view controller aware of the button tap in my main view controller.
Finally in my collection view controller when the tap is detected a call to the method that presents the search view controller modally is made. Search view controller is presented.
In collection view controller
- (void)searchButtonOnMainTableViewControllerTapped
{
[self searchButtonTapped];
}
- (void)searchButtonTapped
{
VAGSearchViewController *svc = [[self storyboard] instantiateViewControllerWithIdentifier:#"searchPageSB"];
svc.delegate = self;
[self presentViewController:svc animated:NO completion:nil];
}
The search view controller notifies the collection view controller it was dismissed using delegation. This is when the querying and refresh of the collection view controller is done and the result of the search is shown.
- (void)searchViewControllerDismissed:(VAGSearchViewController*)searchViewController withTitleForObject:(NSString *)titleString
{
_searchTitleString = titleString;
[self setObjects:nil];
[self performQuery];
}
Ok so this works but it doesn't look professional because for a split second after the push to the collection view controller from the main view controller the collection view is shown before the search view controller is presented.
The feel I'm aiming for is a snappy one. So I'd prefer if the search view controller would be presented instantly.
Hopefully my detailed post give you an idea of what I'm doing.
There is a much more efficient way to do this. The problem is I can't figure it out.
Would appreciate some help
Thanks for your patience
You can do one thing did not push Collection view controller when search button is tapped.
After pushing modalviewControler if search done then you Call the delegate in MainviewController and Then push collection Viewcontroller with no animation in DelegateMethod. its look what you want

Dismissing the split view popover controller

I have a UISplitViewController with the master view set up like this:
UITabBarController
Tab1:
UINavigationController -> UIViewController -> UIViewController
Tab2:
UINavigationController -> UIViewController
Each of the UIViewControllers is a table view, and when the user chooses a row in the last one, an image is shown in the detail view, which contains a UIScrollView.
The tab bar Controller is the UISplitViewControllerDelegate and handles putting up the button on a toolbar at the top of the scroll view.
The problem is, I want to add code to dismiss the popover when the user makes their choice. The pointer to the popover has to be saved in the tab bar controller when the button goes up, and then used to dismiss the popover several view controllers down the line when the user makes their final selection. There doesn't seem to be any way for the view controller that needs that pointer to get at it, without doing something gross like storing it in the App Delegate.
I don't see other people asking this question, which leads me to believe that I've once again overlooked something simple. Please enlighten me!
It sounds like your tab bar controller is already a subclass of UITabBarController, which means that you've already got some custom code in there. I would suggest that the tab bar controller is the primary owner of the popover, and it is the table view controller's responsibility to simply notify the tab bar controller that a selection has been made. The tab bar controller can respond to that message by dismissing the popover. You can take advantage of the fact that UIViewController already has a method for accessing the tab bar controller that contains a given controller.
So it would look something like this:
#interface MyTabBarController : UITabBarController
- (void)itemWasSelected;
#end
#implementation MyTabBarController {
UIPopoverController *popover;
}
- (void)itemWasSelected {
[popover dismissPopoverControllerAnimated:YES];
}
#end
//////////////
#implementation TableController
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)path {
// Do whatever else you want to do
MyTabBarController *tabController = (MyTabBarController *)self.tabBarController;
[tabController itemWasSelected];
}
With this solution, the table controller doesn't have to know anything about the popover; it just has to know that it's going to be presented inside a MyTabBarController, which seems a reasonable thing for it to know.
You could create a singleton class to track your popover status and then make it available to all classes equally and easily. That way it could easily be updated and accessed from any code without having to go straight to overburdening the app delegate even though thats basically the same idea but a bit cleaner in its own singleton.

Pushing a navigation controller is not supported- performing segues

I created a new navigation controller in my storyboard (not programmatically!) and set it to be "Root View Controller" to a regular UIViewController and added a button in it which says- forward to the next view controller (this second view controller is a view controller which I want that will have a back button to link to the initial view controller).
Now, whenever I try to link the button to the next view controller- "Pushing a navigation controller is not supported".
Help me please, and thanks
EDIT:
I accidentally subclassed UINavigationController, and not UIViewController in my class.
Thank you anyway.
I've tried this and have no problems, its all done in IB with no additional code required ...
Start a new project, "Single View Application" using story boards
Select storyboard and delete the views its produced.
Drag on a new Navigation Controller (it will bring a table view with it)
Delete the table and the table view controller, so you are just left with the Navigation Controller
Drag on a normal view controller
Right Click and drag from the Navigation controller to the new View and choose "Relationship - Root View Controller"
Drag a "Bar Button Item" on to the Navbar which should be visible on the top of your ViewController, you can rename this Forward if you wish.
Now drag on another view controller which is the one your "Forward" button will push in to view.
Right Click and drag from the bar button to the 2nd View Controller, and choose "Push"
Run the project and you will get a Single view with a Navbar and your button, clicking your button will Push the other view and give you a Back Button to return to the first View Controller. I'll try and post a picture of my storyboard if it helps.
Plasma
I had the same trouble. I wanted to have a navigation controller on each storyboard, so that each could run independently, be individually debugged, and so that the look would be right with the navigation bar.
Using other approaches, I found the UINavigationController would be retained from the original storyboard -- which I didn't want -- or I'd get errors.
Using the AppDelegate in your view controller to set the rootViewController worked for me (borrowing segue naming conventions from Segue to another storyboard?):
- (void)showStartupNavigationController {
NSLog(#"-- Loading storyboard --");
//Get the storyboard from the main bundle.
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"Startup" bundle:nil];
//The navigation controller, not the view controller, is marked as the initial scene.
UINavigationController *theInitialViewController = [storyBoard instantiateInitialViewController];
NSLog(#"-- Loading storyboard -- Nav controller: %#", theInitialViewController);
//Remove the current navigation controller.
[self.navigationController.view removeFromSuperview];
UIWindow *window = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window];
window.rootViewController = theInitialViewController;
To swap views Programatically you would need to select the segue and give it an Identifier like "PushView" then call it like this ....
[self performSegueWithIdentifier:#"PushView" sender:self];
That will programatically do the same as clicking the forward button. I've created you an example project with the code discussed above. Has an -(IBAction) with code in you can use for programatially changing the view.
PushView.zip
I also wanted to do this, present a screen (that had an embedded navigation controller) when the user pushes a button.
At my first attempt, I connected the segue from the button in the fist screen to the Navigation Controller, and the app was crashing with this error "Pushing a navigation controller is not supported".
This is the solution I found:
Select the segue from the button in the first screen to the navigation controller.
If it had an identifier, copy its name. Then delete that segue.
Then create a new segue by CTRL-clicking the button in the first view controller and dragging to the VIEW CONTROLLER YOU WANT TO PRESENT (not to the Navigation Controller that is pointing at it), and select Push in the small pop up window.
Then click the icon in the middle of the segue and paste the name you copied in the first step as an identifier for it.
IB is going to give you a warning "Scene is unreachable due to lack of entry points and does not have an identifier for runtime access via -instantiateViewControllerWithIdentifier:." Don't worry, it works perfectly.
If you want to customize the string that is shown as the Back button to return, you can add this line in the viewDidLoad method OF THE VIEW CONTROLLER THAT IS BEING SHOWED AFTER THE BUTTON IS PRESSED, that is the Child view controller.
(replace "Settings" with the name you need)
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.topItem.title = #"Settings";
...
}

Reference the main view controller... need the code

I have a view based app (not navigation or tab based...)
My main view controller is called from the app delegate and initiated from a xib.
Then I use presentModalViewController to bring another view on the screen with it's own xib and view controller.
I have no problems passing data to that view controller.
However, when I dismiss the second view controller, I want to send data back to the main view controller for my app, but I just can't figure out how to reference it. Actually, I'd like to call a method in the main view controller if possible.
I've been struggling with this a bit and have found suggestions online but I just can't seem to get it to work. I'm hoping someone can provide the sample code to do this.
P.s. is this "main view controller" still referred to as a "root view controller" or is that term only used when dealing with a view controller stack (i.e. navigation or tab view controller)
EDIT:
I'm sure Bryan's solution would work so I have accepted as answer. However I ended up using NSNotificationCenter to get this to work and I find it a bit simpler to understand as a beginner
You can use the delegation pattern. In your modal view controller's header file, create an interface for a new delegate protocol...
#protocol ModalViewControllerDelegate <NSObject>
- (void)sendData:(Data *)someData;
#end
...and give your ModalViewController a new instance variable that implements this protocol:
#property (nonatomic, assign) id<ModalViewControllerDelegate> delegate;
Your main view controller should implement this protocol...
#interface MainViewController : UIViewController <ModalViewControllerDelegate> {
...and set itself as the delegate before it presents the modal view controller:
ModalViewController *modalViewController = [[[ModalViewController alloc] init] autorelease];
[modalViewController setDelegate:self];
// Present modal view controller
The main view controller should implement the delegate protocol's method:
- (void)sendData:(Data *)someData {
NSLog("I have just received some data: %#", someData);
}
Then inside your modal view controller, you can simply call the following method whenever you want to send data back to the main view controller:
[delegate sendData:someData];

Adding Navigation from home page

I'm adding navigation to subviews in my app from the home view.
I understand the concepts of pushing and popping view in the navigation stack, however, I don't want the navigation bar in the home screen.
Basically, when the user leaves the home view to any sub view, I'd like them to see the "Home" button on the left of the button nav bar, but no nav bar in the home view.
Any help would be appreciated.
It sounds like you want to start out with a bare UIViewController, containing your home screen with your own custom buttons.
The UINavigationController should come into play only when a user performs some action. Do this by
[navVC setModalTransitionStyle:UIModalTransitionStyleCrossDissolve]; // pick an effect
[self.viewController presentModalViewController:VC animated:YES];
Where navVC is the navigation controller, and self.viewController refers to your (new) main view controller. (add a suitable line IBOutlet UIViewController *viewController; + #property line + #synthesize line)
You need to fiddle a bit in the way the app starts up, for now it will probably show the navigation controller directly. If you are using a xib, you can do this by adding a UIViewController while leaving the navigation controller there as it stands. In application: didFinishLaunchingWithOptions: you'll find a line saying
[window addSubview:...];
which actually determines which viewcontroller's view is first visible. Change this to:
[window addSubview:self.viewController.view];
If you've done all this correctly, you've inserted the extra UIViewController between startup and navigation.