dismissing modalViewController from UITabBarController issue - iphone

so in my app delegate I am trying to present a modalViewController from a UITabBarController, by doing the following:
self.tabBarController = [[UITabBarController alloc] init];
LoginViewController* loginViewController = [[LoginViewController alloc] init];
loginViewController.delegate = self;
[self.tabBarController presentModalViewController:loginViewController animated:NO];
[loginViewController release];
and the delegate defined in the app delegate is:
- (void)userDidLogin:(LoginViewController *) loginViewController
{
NSLog(#"DELEGATE CALLED, DISMISSING");
[self.tabBarController dismissModalViewControllerAnimated:NO];
}
Here's my LoginViewController:
protocol LoginViewControllerDelegate;
#interface LoginViewController : UIViewController <MBProgressHUDDelegate>
{
id<LoginViewControllerDelegate> delegate;
}
#property (assign) id<LoginViewControllerDelegate> delegate;
#end
#protocol LoginViewControllerDelegate
- (void)userDidLogin:(LoginViewController *) loginViewController;
#end
The issue is that this (userDidLogin:(LoginViewController *) loginViewController) is never called... why is this?
I have called the following in my LoginViewController implementation and this is called
[self.delegate userDidLogin:self];
UPDATE:
I got the delegate called now. The issue now is that when I call [self.tabBarController dismissModalViewControllerAnimated:YES] it doesn't dismiss the modal view controller.

You didn't post any code from LoginViewController, but within that class's code you need to add the following lines when you are ready to dismiss it (perhaps when the user clicks the "Login" button and the login is successful).
if (delegate && [delegate respondsToSelector:#selector(userDidLogin:)])
[delegate performSelector:#selector(userDidLogin:) withObject:self];
UPDATE:
I think I understand what the issue is here. According to Apple's documentation, when you call presentModalViewController:animated: the method sets the value of the "modalViewController" property of UIViewController (in this case your UITabBar). However that property only maintains a weak reference to the modalViewController. That's important because you initialize the LoginViewController, pass it in to presentModalViewController:animated: and then you release it. Since presentModalViewController:animated: is not retaining a strong reference to the LoginViewController, the UITTabBar is unable to dismiss it later on. In fact I'm surprised what you have done is not resulting in an EXC_BAD_ACCESS crash. I suggest you remove the "[loginViewController release]" statement and instead release it after you call "[self.tabBarController dismissModalViewControllerAnimated:NO]"

Related

Calling method from another class, uiwebview and modal view issues

I'm experiencing a few issues when trying to call a method from another class. Here's my code
Appdelegate.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{
if ([url isFileURL]){
RootViewController *theview = [[RootViewController alloc] init];
[theview handleOpenURL:url];
}
return YES;
}
RootViewController.h
#interface RootViewController : UIViewController <UIWebViewDelegate>{
IBOutlet UIWebView* mainwebView;
}
- (void)handleOpenURL:(NSURL *)url;
#property (nonatomic, retain) UIWebView* mainWebView;
RootViewController.m
- (void)handleOpenURL:(NSURL *)url{
NSLog(#"Method handleOpenURL completed")
[mainwebView setDelegate:self];
[self.mainwebView loadRequest:[[NSurLRequest alloc] initWithURL:url];
[self performSelector:#selector(handleIt) withObject:nil afterDelay:3.0];
}
- (void)handleIt{
docView *docController = [[docView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:docController animated:YES];
}
So, basically I'm trying to update a UIWebView and then present a modal view controller once the appdelegate calls handleOpenURL. The problem is that both of these things (updating webview and presenting view controller) refuse to work in the method handleOpenURL. They work perfectly fine in viewdidload, which makes me think it has something to do with the method being called from appdelegate.
The webview simply refuses to respond when accessed from the handleOpenURL method, and when I present the modal view controller I get the error
Warning: Attempt to present ... whose view is not in the window hierarchy!
But, again the modalview works fine when presented from viewdidload (using the time delay selector)
An explanation to why this is happening or a workaround would be great, thanks for your help.
You have to #import "docView.h" in your RootViewController.m.
Warning: Attempt to present ... whose view is not in the window hierarchy!
The warning states the view is not recognized, meaning you don't have access to the class properly, if it's not importing, then you didn't set up the code in docView.m properly.
If that doesn't work, than i need more info to help solve your problem.
Edit for Clarification:
The app-delegate is called at the start and when pointed to, not
constantly.
You should call it from when the modal view loads, a method
pointer([self myFunctionNameHere];) would work just fine.
So it's that your not accessing the app-delegate properly.
I don't think you should even be calling it from the app-delegate,
call it from your .m.
Calling it from your main(.m) file would solve everything.

Iphone UIView parent method call

I'm stuck with an issue.... Please help!
I have a navigation controller project. From the root controller I am pushing a view onto the stack that has some info and 2 buttons (YES, NO). (it can't be an AlertView). If they press the YES button, I call a method on the parent view (using a delegate method) and pop the view from the stack. The method on the parent pushes a new view onto the stack. I assumed the parent method would remain in memory after the deallocation of the child view because the method exists on the parent, but when the child is deallocated it also deallocates everything created inside the method on the parent. Does anyone have a solution for responding to events that happen on a child view in a parent view that will remain after the child is deallocated?
Thanks!
in header file:
#import <UIKit/UIKit.h>
#protocol decrementDelegate;
#interface decrement : UIViewController {
int currentCount;
IBOutlet UILabel *countLabel;
id <decrementDelegate> delegate;
}
#property (nonatomic, assign) id <decrementDelegate> delegate;
#end
#protocol decrementDelegate <NSObject>
-(void)decrementControllerDidFinish:(decrement *)controller
withString:(NSString *)stringValue;
#end
in implementation file:
[self.delegate decrementControllerDidFinish:self
withString:countLabel.text];
[self.navigationController popViewControllerAnimated:YES];
caller:
-(void)decrementControllerDidFinish:(decrement *)controller
withString:(NSString *)stringValue {
// Show our details
myNewChildController *viewController = [[myNewChildController alloc]
initWithNibName:#"myNewChildController" bundle:nil];
viewController.delegate = self;
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
}
Using non-delegate and just calling a method on the parent:
caller:
mySummary *parent = [self.navigationController.viewControllers objectAtIndex:0];
[mySummary setMyAllowOnParent:#"Allowed"];
[self.navigationController popViewControllerAnimated:YES];
parent:
-(void)setMyAllowOnParent:(NSString *)aAllow {
newView *viewController = [[newView alloc] initWithNibName:#"newView" bundle:nil];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
}
newView crashes with deallocated error when caller view is deallocated. Does anyone have an idea how to resolve this issue?
How did you create your delegate? You didn't accidentally used retain instead of assign (assuming you use a property to access it)? Because if you used retain, this might have caused the issue.
By the way, you mention something about a UIAlertView that cannot be used, which leads me to think that you created a view with a similar design as a UIAlertView. Perhaps the following link might be useful:
how can i make popup login window similar as on istore in objective-C/cocoa-touch

UIAlertView Question

I have a small doubt. I have a NSObject class where I am trying to display an alert view. So after the alert view is displayed when I tap on OK button I want to push a navigation controller onto the stack. Can I push a navigation controller from general NSObject class? Please let me know guys..thanks for your time..
This is the code..
- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex{
SettingsViewController *homeView = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[self.navigationController pushViewController:homeView animated:NO];
[homeView release];
}
I am creating a property called navigationController of type UINavigationController and when I catch the error I am displaying an alert view and I am using above method to push the view controller but it doesn't work..
Yes and no... depending on how you have your application set up. To push views onto the navigation stack you need to have a navigation controller.
Does your NSObject have access to this navigation controller - you might have to set up a delegate method that gets called from your delegate view when the alert view delegate gets called in your NSObject.
I'm just wondering why you're displaying a UIAlertView in an NSObject, why aren't you displaying it in a UIView or a UIViewController?
CustomObject.h
#protocol CustomObjectDelegate<NSObject>
#optional
- (void)customObjectAlertViewDidClickOk;
#end
#interface CustomObject : NSObject <UIAlertViewDelegate>{
id<CustomObjectDelegate> delegate;
}
#property (nonatomic, assign) id<CustomObjectDelegate> delegate;
#end;
CustomObject.m
#synthesize delegate;
// then put this:
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
[delegate customObjectAlertViewDidClickOk];
}
Then your ViewController .h file needs to include the custom object and assign the delegate methods:
#include "CustomObject.h"
#interface MyViewController : UIViewController <CustomObjectDelegate> {
}
#end
and the .m viewDidLoad (or similar):
- (void)viewDidLoad{
CustomObject *obj = [[CustomObject alloc] init];
[obj setDelegate:self];
}
- (void)customObjectAlertViewDidClickOk{
AnotherViewController *page = [[AnotherViewController alloc] initWithNibName:nil bundles:nil];
[self.navigationController pushViewController:page];
}
Thats how I would do it - given I'm not too sure i understand quite what you're asking. :) thats all off the top of my head as well - so don't take it letter for letter, but you have the basis there to start off with. You can build on it. Look up #protocols and delegate methods, its all in there. :)

Call Function in Underlying ViewController as Modal View Controller is Dismissed

I have a mainViewController. I call [self pushModalViewController:someViewController] which makes someViewController the active view.
Now I want to call a function in mainViewController as someViewController disappears with [self dismissModalViewController].
viewDidAppear does not get called probably because the view was already there, just beneath the modal view. How does one go about calling a function in the mainViewController once the modalView dismisses itself?
Thanks a lot!
This answer was rewritten/expanded to explain the 3 most important approaches (#galambalazs)
1. Blocks
The simplest approach is using a callback block. This is good if you only have one listener (the parent view controller) interested in the dismissal. You may even pass some data with the event.
In MainViewController.m
SecondViewController* svc = [[SecondViewController alloc] init];
svc.didDismiss = ^(NSString *data) {
// this method gets called in MainVC when your SecondVC is dismissed
NSLog(#"Dismissed SecondViewController");
};
[self presentViewController:svc animated:YES completion:nil];
In SecondViewController.h
#interface MainViewController : UIViewController
#property (nonatomic, copy) void (^didDismiss)(NSString *data);
// ... other properties
#end
In SecondViewController.m
- (IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
if (self.didDismiss)
self.didDismiss(#"some extra data");
}
2. Delegation
Delegation is the recommended pattern by Apple:
Dismissing a Presented View Controller
If the presented view controller must return data to the presenting view controller, use the delegation design pattern to facilitate the transfer. Delegation makes it easier to reuse view controllers in different parts of your app. With delegation, the presented view controller stores a reference to a delegate object that implements methods from a formal protocol. As it gathers results, the presented view controller calls those methods on its delegate. In a typical implementation, the presenting view controller makes itself the delegate of its presented view controller.
MainViewController
In MainViewController.h
#interface MainViewController : UIViewController <SecondViewControllerDelegate>
- (void)didDismissViewController:(UIViewController*)vc;
// ... properties
#end
Somewhere in MainViewController.m (presenting)
SecondViewController* svc = [[SecondViewController alloc] init];
svc.delegate = self;
[self presentViewController:svc animated:YES completion:nil];
Somewhere else in MainViewController.m (being told about the dismissal)
- (void)didDismissViewController:(UIViewController*)vc
{
// this method gets called in MainVC when your SecondVC is dismissed
NSLog(#"Dismissed SecondViewController");
}
SecondViewController
In SecondViewController.h
#protocol SecondViewControllerDelegate <NSObject>
- (void)didDismissViewController:(UIViewController*)vc;
#end
#interface SecondViewController : UIViewController
#property (nonatomic, weak) id<SecondViewControllerDelegate> delegate;
// ... other properties
#end
Somewhere in SecondViewController.m
[self.delegate myActionFromViewController:self];
[self dismissViewControllerAnimated:YES completion:nil];
(note: the protocol with didDismissViewController: method could be reused throughout your app)
3. Notifications
Another solution is sending an NSNotification. This is a valid approach as well, it might be easier than delegation in case you only want to notify about the dismissal without passing much data. But it's main use case is when you want multiple listeners for the dismissal event (other than just the parent view controller).
But make sure to always remove yourself from NSNotificationCentre after you are done! Otherwise you risk of crashing by being called for notifications even after you are deallocated. [editor's note]
In MainViewController.m
- (IBAction)showSecondViewController:(id)sender
{
SecondViewController *secondVC = [[SecondViewController alloc] init];
[self presentViewController:secondVC animated:YES completion:nil];
// Set self to listen for the message "SecondViewControllerDismissed"
// and run a method when this message is detected
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(didDismissSecondViewController)
name:#"SecondViewControllerDismissed"
object:nil];
}
- (void)dealloc
{
// simply unsubscribe from *all* notifications upon being deallocated
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didDismissSecondViewController
{
// this method gets called in MainVC when your SecondVC is dismissed
NSLog(#"Dismissed SecondViewController");
}
In SecondViewController.m
- (IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
// This sends a message through the NSNotificationCenter
// to any listeners for "SecondViewControllerDismissed"
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SecondViewControllerDismissed"
object:nil userInfo:nil];
}
Hope this helps!
Using exit (unwind) segue
When you're using storyboards and segues you can use a very handy approach with minimal code to dismiss a modal view controller and inform the underlying view controller that the modal view controller has been closed.
Using exit (unwind) segues you gain 3 advantages:
you don't need to write any code to dismiss the modal view controller and
you can have iOS call a callback method inside the underlying view controller that has presented the model view controller.
you use the very same semantics that you already know from implementing prepareForSegue:
Implement it with only 2 steps
Create an action method in the Parent view controller that presents another (modal) view controller:
Swift
#IBAction func unwindFromSegue(segue: UIStoryboardSegue) {
print("Unwind from segue", segue.identifier)
}
Objective-C
- (IBAction)unwindFromSegue:(UIStoryboardSegue *)segue {
NSLog(#"Unwind from segue %s", segue.identifier);
}
In the storyboard, On child view controller right click the exit segue (aka unwind segue, it's the last of the icons at the top of your view controller), drag & drop unwindFromSegue: to your button and select action.
You're done! Now the modal view controller closes when you click the dismiss button and unwindFromSegue: informs your underlying view controller(Parent) that the modal view controller(Child) has closed.
Here's a callback solution which takes less modifications to your modal and parent:
In the Model's .h add:
#property (nonatomic, copy) void (^dismissed)();
In the Model's .m put this in the completion when you dismiss the modal:
[self dismissViewControllerAnimated:YES completion:^{
if(self.dismissed)
self.dismissed();
}];
In the parent view controller when you instantiate your modal set the dismissed callback:
Modal = //Init your modal
[Modal setDismissed:^{
//do stuff you wanted when it's dimissed
}];
[self presentViewController:Modal animated:YES completion:nil];

UIView notification when modal UIImagePickerController is dismissed?

Is there a way to call code when a modal view is finished dismissing?
EDIT:
I'm sorry, I didn't clarify earlier. I'm trying to dismiss a UIImagePickerController and then show a MFMailComposeViewController and attach the image data to the email. When I try to call
[self presentModalViewController: mailController]
right after
[self dismissModalViewController];
I get errors and such.
You use a delegate pattern for the modal view to inform whoever presented it when it's finished.
MyModalViewController.h:
#protocol MyModalViewControllerDelegate;
#interface MyModalViewController : UIViewController
{
id<MyModalViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<MyModalViewControllerDelegate> delegate;
#end
#protocol MyModalViewControllerDelegate
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController;
#end
MyModalViewController.m:
#synthesize delegate;
// Call this method when the modal view is finished
- (void)dismissSelf
{
[delegate myModalViewControllerFinished:self];
}
ParentViewController.h:
#import "MyModalViewController.h"
#interface ParentViewController : UIViewController <MyModalViewControllerDelegate>
{
}
ParentViewController.m:
- (void)presentMyModalViewController
{
MyModalViewController* myModalViewController = [[MyModalViewController alloc] initWithNibName:#"MyModalView" bundle:nil];
myModalViewController.delegate = self;
[self presentModalViewController:myModalViewController animated:YES];
[myModalViewController release];
}
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController
{
[self dismissModalViewControllerAnimated:YES];
}
EDIT:
I haven't used UIImagePickerController, but looking at the docs, it looks like you already have most of the code done for you, as there is an existing UIImagePickerControllerDelegate class that has three different "dismissal" delegate callbacks (although one is deprecated). So you should make your ParentViewController class (whatever that is) implement the UIImagePickerControllerDelegate pattern and then implement those methods. While each method will do something different (since you have to handle when the user actually selects an image, or if they cancel), they each will do the same thing at the end: call dismissModalViewControllerAnimated: to dismiss the picker.
You have to dismiss the modalViewController somehow right? Either a UIButton, or by code:
- (void)dismissModalViewControllerAnimated:(BOOL)animated
In the IBAction (e.g. delegate) for the UIButton or in the method above, call whatever code you want.
I don't think there is a specific notification yet can subscribe to, to know when dismiss animation is done,...BUT. You can implement viewDidAppear: in the view controller that presented the modal view. This is what I do, when I use the (to UIImagePickerController quite similar) ABPeoplePickerNavigationController.
In the callback from people picker, I remember the person tapped in the picker on an instance variable, like this:
- (void)callbackFromModalView:(id)dataFromModalView {
// remember dataFromModalView as I need it when dismissed
self.dataFromModalView = dataFromModalView;
// now initiate dismissal
[self dismissModalViewControllerAnimated:YES];
}
then, in your view controller, implement this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.dataFromModalView) {
//...present now view here
// don't forget to reset this one
self.dataFromModalView = nil;
}
}
in effect, you are using the combination of viewWillAppear: and the dataFromModalView property as the "notification about modal view dismissed".