Dismissing the split view popover controller - objective-c

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.

Related

How do I use SWRevealViewController with unwind segues?

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

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

View Controller Doesn't respond to Action Methods

I have a simple test app (in OSX) that has a view controller with a button in its view. The button's action method is in the view controller's class, and that IBAction is connected in IB (through File's Owner). When the button is clicked, I get an EXC_BAD_Access error (except occasionally I get -[NSRunLoop buttonClick:] instead). I've read a bunch of posts here on SO having to do with NSViewControllers not being in the responder chain, but also that specifically hooking the action method up in IB should work. The only code I have is this:
In the app delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
TestController *controller = [[TestController alloc] initWithNibName:#"TestController" bundle:nil];
[self.window.contentView addSubview:controller.view];
}
And, in the TestController class, just this:
-(IBAction)buttonClick:(id)sender {
NSLog(#"%#",sender);
}
I have 2 questions. Why is this happening, and where is the correct mvc place to put IBActions for button methods (shouldn't controller classes handle these)?
First off, I believe in the App Delegate there is already a property called viewController already defined for you. You should use this code self.viewController = [[TestController alloc] initWithNibName:#"TestController" bundle:nil]; instead of TestController *controller = [[TestController alloc] initWithNibName:#"TestController" bundle:nil];. If that doesn't fix it, then I'm not sure what's wrong. Your code should return the attributes of the button you clicked. I created my own sample project and tested out your code (although my app delegate looked different than yours) and it worked fine.
Second, you should elaborate on your second question "Why is this happening, and where is the correct mvc place to put IBActions for button methods (shouldn't controller classes handle these)?". It's not very clear what you mean.
Hope this helps a little.
NSViewController will not respond to IBActions on mac os x on iPhone It is the correct place to put your IBAction as the view should only draw its content (data) not change it. But on Mac os x the NSViewController is for setting up the NSView it's no ment to respond to IBActions you have two choices one is to put your IBAction in your NSView or is create a NSWindowController .
On mac osx you have plenty of screen space and you will alway have views within a window you'll use NSViewController to add them to your window and to setup your view but the window is first in your responder chain then your NSWindowController . eg: you may have one window and two view controller showing the same view which may have 5 test fields in it and view controller one loads that with data but you can not edit the data then view controller two loads the same view with the same data but enable editing for the text fields so. but all action methods will go to the WindowController .
I recommend you to check your viewController's identity inspector.
It may indicate wrong custom class.
viewController created basically usually has name ViewController on the section.

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];

Problem reloading gridView data after dismissing a modal view controller

I am presenting a modal view controller to show detailed information. I have it set up so that any change to the information in the modal view controller will change the information in its parent view controller.
The information is changed, but I cannot manage to reload the gridView data when the modal view controller is dismissed. Right now I have the action to dismiss the modal inside the modal view controller. Everything works good, I just can't reload the data for the gridView from the modal view controller.
I read somewhere that one of the options is to create a delegate that will be able to dismiss the modal view controller from the parent view controller, I just can't seem to find examples or nice tutorials on how to go by doing this. The truth is that I know how to use the delegates, but not quite sure on how to properly implement one.
Can anyone please point me in the right direction here? Maybe someone has a better option. I am open to any suggestions.
If I understand clearly, you want to reload the grid contained in your parent view controller when you dismiss the modal view controller. If so, here how:
Declare a protocol in your ModalViewController by doing something like
#protocol MyViewControllerDelegate;
#interface MyViewController : UIViewController {
id<MyViewControllerDelegate>delegate;
// Your stuff
}
#property (nonatomic, assign) id<MyViewControllerDelegate>delegate;
#end
#protocol MyViewControllerDelegate <NSObject>
-(void)viewControllerWasDismissedOrAnyOtherNameYoudLike;
#end
And in your .m file, just #synthesize delegate.
Just when you call dismissModalViewController:animated:, also call [delegate viewControllerWasDismissedOrAnyOtherNameYoudLike].
In your view controller with the grid view, import the header file of your modalviewcontroller, conform to the protocol
#interface MyGridViewController : UIViewController <MyViewControllerDelegate>
When you init the Modalview controller, set the delegate to self, and implement the viewControllerWasDismissedOrAnyOtherNameYoudLike method.
VoilĂ !