I have a simple app that uses a UITabBar controller for navigation. Let's say I have ViewA and ViewB on the tabBar. Everything works fine. However I also want to add a UIButton to ViewA that will present the user with ViewB when pressed.
In other words there are two ways to get to ViewB from ViewA. The first is from the tabBar and the second is by pressing the button in ViewA.
What would be the best way of accomplishing this. Thanks.
Ok then I would define a protocol in ViewA.h
#protocol ViewADelegate
-(void)viewAPressButton;
#end
and add a property to ViewA :
#property (nonatomic, assign) id<ViewADelegate> delegate
don't forget to set this property when instantiate this controller.
call this method when the button is pressed in ViewA.m
-(IBAction)buttonPressed:(id)sender {
[delegate viewAPressButton];
}
then implement in the correct place (maybe AppDelegate in your case ?)
Assuming ViewB is at index 1
-(void)ViewAPressButton {
[self.tabBarController setSelectedIndex:1];
}
Related
I have multiple buttons in ViewA. When a button is clicked, it is redirected to ViewB. In ViewB, the user does some interactions, and then clicks the back button when he is done. How do I run a method and pass in a parameter from ViewB into ViewA and then continue working in ViewA?
Using the back button is strongly required, but I am keen to learn if there are other ways.
My idea was to get the ViewA from the stack, and when I am done with ViewB, just call upon it and redirect, but I couldn't figure out how to do it.
Thank you.
You want to define a delegate in ViewB and implement it in ViewA. At the appropriate time (e.g., when the back button is tapped) ViewB call the delegate method, passing the value as a parameter.
Something like this:
ViewB.h
// Define a delegate protocol allowing
#protocol ViewBDelegate
// Method of delegate called when user taps the 'back' button
-(void) backButtonSelected:(id) object;
#end
#interface ViewB : UIViewController
// property to hold a reference to the delegate
#property (weak)id<ViewBDelegate> delegate;
-(IBAction)backButtonSelected:(id)sender;
#end
ViewB.m:
#implementation ViewB
...
-(IBAction)backButtonSelected:(id)sender{
NSObject *someObjectOrValue = nil;
// Invoke the delegates backButtonSelected method,
// passing the value/object of interest
[self.delegate backButtonSelected:someObjectOrValue];
}
#end
ViewA.h:
#import "ViewB.h"
// The identifier in pointy brackets (e.g., <>) defines the protocol(s)
// ViewA implements
#interface ViewA : UIViewController <ViewBDelegate>
...
-(IBAction)someButtonSelected:(id)sender;
#end
ViewA.m:
-(IBAction) someButtonSelected:id
#implementation ViewA
...
// Called when user taps some button on ViewA (assumes it is "hooked up"
// in the storyboard or IB
-(IBAction)someButtonSelected:(id)sender{
// Create/get reference to ViewB. May be an alloc/init, getting from storyboard, etc.
ViewB *viewB = // ViewB initialization code
// Set `ViewB`'s delegate property to this instance of `ViewA`
viewB.delegate = self;
...
}
// The delegate method (defined in ViewB.h) this class implements
-(void)backButtonSelected:(id)object{
// Use the object pass as appropriate
}
#end
In iOS6 you can now use Unwind Segues. In Storyboard you might have noticed the new Exit buttons that can be used for this.
Unwind Segues will allow you to transition back from viewControllerB to viewControllerA and to provide info back through the prepareForSegue method.
Currently documentation is rather limited, but there is a useful WWDC2012 video on how to do this (Session 236)
If you don't want to implement protocol then other way is, you can create property in ViewB of type id like this
#property (nonatomic, unsafe_unretained) id* parentView;
And set it to ViewA when pushing ViewB like this
ViewB.parentView = ViewA;
//push ViewB
Then you can call ViewA's methods directly using this property.
You can define a protocol in ViewControllerA, example name ViewControllerADelegate, create a property delegate in ViewControllerB and in ViewControllerB implement the protocol defined in ViewControllerA.
When you are working in viewControllerB you can notify all your changes to viewControllerA calling methods defined in ViewControllerADelegate, when you will leave viewControllerB you will have viewControllerA updated.
If your ViewA is creating ViewB, you can create a protocol on your ViewB.h and set ViewA as ViewB's delegate. So whenever you change something in ViewB, you can notify ViewA directly.
Depending on your model, you can have a class that holds whichever settings you modify in ViewB. When you create ViewB, you pass the reference normally, and since it's an object, the instance will be shared, so when you go back to ViewA, all the changes are already applied.
viewB.settings = self.settings; // or whatever model object you have
Another option is to create a custom back button (you will lose the pointy look), have it call your custom method that notifies your ViewA and then does a [self.navigationController popViewControllerAnimated:YES];
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.
I've created a button on one viewController that loads another view modally using the UIModalPresentationFormSheet presentation style. On this loaded view, I have two textFields, and I'm forcing the first textField to be first responder so that the keyboard will appear immediately with the new view. I've set up the textFields to have an action method that is hooked up to "Did End on Exit" event. However, whenever I hit "return" on the keyboard for either textField, the keyboard fails to go away (Here is my code):
// addCustomPage method that is called when button from original view is touched
- (IBAction) addCustomPage:(id)sender
{
NSLog(#"Adding Custom Page");
if (!self.customPageViewController)
{
self.customPageViewController =
[[CustomPageViewController alloc] initWithNibName:#"CustomPageViewController" bundle: nil];
}
customPageViewController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:customPageViewController animated:YES];
// force keyboard to appear with loaded page on the first textField
[customPageViewController.firstTextField becomeFirstResponder];
}
#interface CustomPageViewController : UIViewController
#property (strong, nonatomic) IBOutlet UITextField *firstTextField;
#property (strong, nonatomic) IBOutlet UITextField *secondTextField;
- (IBAction)keyboardEndOnExit:(id)sender; // DID END ON EXIT EVENT
#end
//in CustomPageViewController.m
-(IBAction)keyboardEndOnExit:(id)sender
{
[sender resignFirstResponder];
}
This is a fairly straight forward problem, and I have no problem normally dismissing keyboards using this technique with basic views and textFields. I'm not sure if using a view in this presentation format, or set up makes things different. Thanks!
You have confirmed that you keyboardEndOnExit method is actually being called?
You could also take a more direct approach by calling [yourTextView resignFirstResponder] when a specific action is take by the user, such as a key pressed etc. I would still check if that method is ever being called using breakpoints or a log.
Have a look at this question. Pretty sure it is the same problem caused by UIModalPresentationFormSheet.
I know this question has been asked several times and I did read existing posts on this topic but I still need help.
I have 2 UIViewControllers - parent and child. I display the child UIViewController using the presentModalViewController as below:
ChildController *child =
[[ChildController alloc] initWithNibName:#"ChildView" bundle:nil];
[self presentModalViewController:child animated:YES];
[child release];
The child view has a UIPickerView. When user selects an item from UIPickerView and clicks done, I have to dismiss the modal view and display the selected item on a UITextField in the parent view.
In child's button click delegate, I do the following:
ParentController *parent =
(ParentController *)[self.navigationController parentViewController];
[parent.myTextField setText:selectedText];
[self dismissModalViewControllerAnimated:YES];
Everything works without errors. But I don't know how to load the parent view so that it displays the updated UITextField.
I tried
[parent reloadInputViews];
doesn' work. Please help.
Delegation is the way to go. I know some people that may be looking for an easier solution but trust me I have tried others and nothing works better than delegation. So anyone having the same problem, go read up on delegation and follow it step by step.
In your subviewcontroller.h - declare a protocol and declare delegate mthods in it.
#protocol myDelegate
-(void)clickedButton:(subviewcontroller *)subController;
#end
In your subviewcontroller.h, within #interface:
id<myDelegate> delegate;
#property (nonatomic, assign) id<myDelegate> delegate;
NSString *data;
-(NSString *)getData;
In your subviewcontroller.m, synthesize myDelegate. Add the following code to where you want to notify your parentviewcontroller that the subview is done doing whatever it is supposed to do:
[delegate clickedButton:self];
and then handle getData to return whatever data you want to send to your parentviewcontroller
In your parentviewcontroller.h, import subviewcontroller.h and use it's delegate
#import "subviewcontroller.h"
#interface parentviewcontroller : VUIViewController <myDelegate>
{}
In your parentviewcontroller.m, implement the delegate method
- (void)clickedButton:(subviewcontroller *)subcontroller
{
NSString *myData = [subcontroller getData];
[self dimissModalViewControllerAnimated:YES];
[self reloadInputViews];
}
Don't forget memory management!
If a low-memory warning comes in during your modal view's display, the parent's view will be unloaded. Then parent.myTextField is no longer referring to the right text field until the view is reloaded. You can force a reload of the view just by calling parent.view;
However, a better idea might be to have the parent view have a String property that can be set by the child view. Then, when the parent view reappears, put that data into the text field, inside viewWillAppear: for example. You'd want to have the value set to some default value for when the parent view initially shows up too.
-(void) viewWillAppear:(BOOL) animated doesn't get called for me either, exactly when it's a modal view controller. No idea why. Not incorrectly overridden anywhere in this app, and the same problem occurs on the other 2 apps I'm working on. I really don't think it works.
I've used the delegate approach before, but I think that following approach is pretty good as well.
I work around this by adding a private category to UIViewController, like so:
.h file:
#interface UIViewController(Extras)
// returns true if this view was presented via presentModalViewController:animated:, false otherwise.
#property(readonly) BOOL isModal;
// Just like the regular dismissModalViewController, but actually calls viewWillAppear: on the parent, which hasn't been working for me, ever, for modal dialogs.
- (void)dismissModal: (BOOL) animated;
#end
and .m file:
#implementation UIView(Extras)
-(BOOL) isModal
{
return self == self.parentViewController.modalViewController;
}
- (void)dismissModal: (BOOL) animated
{
[self.parentViewController viewWillAppear: animated];
[self dismissModalViewControllerAnimated: animated];
}
#end
which I can now call like this when I want to dismiss the dialog box:
// If presented as a modal view, dismiss yourself.
if(self.isModal)
[self dismissModal: YES];
and now viewWillAppear is correctly called.
And yes, I'm donating a bonus 'isModal' property, so that the modal view can tell how it was being presented, and dismiss itself appropriately.
In Objective C (Cocoa) I have an app running with a modal sheet, but I want to allow the app to quit even when the sheet is displayed (contradicting the definition of modal I think, but I like the animated effect of modal sheets).
I'm already using the -setPreventsApplicationTerminationWhenModal method and it works fine, but I'm wondering... is there any way to keep the close button enabled? The little circle usually red-colored close button that comes with all windows in the top left corner (side by side with minimize and maximize)? Right now it's completely disabled when the sheet is running, and it would be awesome if there is a way to enable it.
Thanks!
Use a delegate method to close the Modal View. You declare the delegate on your modal view controller and that delegate method dismisses the ModalViewController
In the Modal ViewController Interface File:
#protocol MyViewControllerDelegate
-(void)dismissModal;
#end
Then declare the delegate as a class property in the Modal ViewController:
#property (nonatomic, retain) id <MyViewControllerDelegate> delegate;
Now, declare your parent ViewController as a proper delegate implementer for the Modal ViewController:
#interface MyParentViewController : UIViewController
Then in the calling (parent) ViewController implement the delegate method in the implementation file:
-(void)dismissModal
{
// Dismiss the Modal ViewController that we instantiated earlier
[self dismissModalViewControllerAnimated:YES];
}
That should do it. The advised way to handle this is through delegate methods (and delegate methods are so handy to use whenever a process in one controller needs to fire a method in another controller. It is well worth the time to get familiar with using delegates to get work done in Obj C