I have some code that shows two UIViewController in a delegate.
RootViewController.m
request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"***some https url here ***"]];
// custom implementation of NSURLConnectionDelegate
dataman = [[DataManager alloc] initWithParentcontroller:self];
mainConn = [[NSURLConnection alloc] initWithRequest:request delegate:dataman];
In AuthenticationViewController.h
#protocol ShowAuthenticationWindowDelegate <NSObject>
#required
- (void) onFinishedEnteringCredentials:(NSURLCredential*)credentials;
- (void) onCancelAuthentication;
#end
in AuthenticationViewController.m
- (IBAction) onClickLogin:(id)sender;
{
....
// authDelegate => id <ShowAuthenticationWindowDelegate>
[authDelegate onFinishedEnteringCredentials:credentials];
[self dismissModalViewControllerAnimated:YES];
....
}
in DataManger.h (DataManager class) implements the NSURLConnectionDelegate and ShowAuthenticationWindowDelegate.
In Datamanager.m
In the didReceiveAuthenticationChallenge delegate function I show the AuthentiationViewController as a modal dialog to gather username/password.
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
AuthenticationViewController *authview = [[AuthenticationViewController alloc] initWithNibName:#"AuthenticationViewController" bundle:[NSBundle mainBundle]];
authview.modalPresentationStyle = UIModalPresentationFullScreen;
authview.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
authview.credentialsDelegate = self;
[rootController presentModalViewController:authview animated:YES];
}
Here I show a UIViewController which is an activity indicator in a view. I am showing it modally after I dismiss the previous AuthenticationViewController dialog in one of the login button event handler by called dismissModalViewController. After sending the credentials with challenge object (previously cached) I am showing the ActivityViewController modally, but it is not shown no matter what I do. I tried to show an UIAlertView which works, but my activityviewcontroller is not shown. I checked the parameters and objects everything is valid. even the delegate wire ups!!! All the code is getting called but the dialog is not shown.
May be I am missing something ???
- (void) onFinishedEnteringCredentials:(NSURLCredential*)credentials;
{
[[authChallenge sender] useCredential:credentials forAuthenticationChallenge:authChallenge];
// create an activity modal dialog
if (activityController == nil) {
activityController = [[ActivityViewController alloc] initWithNibName:#"ActivityViewController" bundle:[NSBundle mainBundle]];
activityController.modalPresentationStyle = UIModalPresentationFullScreen;
activityController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
}
[rootController presentModalViewController:activityController animated:YES];
}
I have figure out the solution, if anyone want to show two modal dialogs back to back, you should set the "animated" parameter to "NO" on the controller that is being dismissed. It seems like the animation transition is not being completed by the time the next controller is shown with "presentViewController" function.
Related
I have a UINavigationController hierarchy, VC1 --> VC2.
VC1 has a table view that I need to reload when VC2 is done with its work, so VC1 has this code:
-(void)viewWillAppear:(BOOL)animated
{
[[self tableView] reloadData];
}
VC2 is essentially working with the server to create a new table row in VC1. When the done button in VC2 is pressed, I call [navController popViewControllerAnimated:YES]. So here's what happens from the user's perspective:
Visit VC2, use it to create a new row for the table in VC1. Press done.
The hierarchy successfully navigates back to VC1, but the tableview does not reload and display the new row.
However, if I then nav forward to VC2, and immediately hit the navController back button, the table does reload and show the new row.
So why does [tableview reload] work on 3 but not 2? Thanks so much.
==
More code in response to answer mentioned below:
In App delegate:
CWLandingVC *lvc = [[CWLandingVC alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:lvc];
[[self window] setRootViewController:navController];
In VC0:
-(void)toSessionMgmtViewController
{
TSessionMgmtViewController *tsmvc = [[TSessionMgmtViewController alloc] init];
[[self navigationController] pushViewController:tsmvc animated:YES];
}
In VC1:
- (IBAction)toCreateSessionView:(id)sender
{
TCreateSession *cs = [[TCreateSession alloc] init];
[[self navigationController] pushViewController:cs animated:YES];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[self tableView] reloadData];
}
In VC2:
Finishes working with server...
UINavigationController *navControler = [self navigationController];
[navControler popViewControllerAnimated:YES];
Also, when VC2 is done working with the server, it updates a data store of TSessions called SessionListStore:
- (TSession *)addSession:(NSString *)code withName:(NSString *) name qs:(int)qs
{
TSession *s = [[TSession alloc] initWithName:name code:code numberQuestions:qs];
[_sessions setObject:s forKey:code];
return s;
}
where sessions is a NSNutatbleDictionary in SessionListStore.
Thanks so much in advance.
EDIT: The solution was to trigger the reloadData call from the completion block of Server call.
Please check this answer,
Popping ViewController doesn't call viewWillAppear when going back
Have you added navigation controller to your view controller or view controllers to your navigation controller?
Also, you can set the desired view controller as the delegate of your navigation controller and implement this method.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
I am trying to open a modalview from a view like that,
SignupViewController *signUpView = [[SignupViewController alloc] initWithNibName:#"SignupViewController" bundle:nil];
[signUpView setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
self.parentViewController.view.transform = CGAffineTransformMakeScale(1.3, 1.3);
self.parentViewController.view.alpha = 0;
[UIView animateWithDuration:.35 animations:^{self.parentViewController.view.alpha = 1.0; self.parentViewController.view.transform = CGAffineTransformMakeScale(1, 1);}];
[self presentModalViewController:signUpView animated:YES];
After login i am closing the modalview and redirecting to anther view, but the parentview is still there,
[self dismissViewControllerAnimated:YES completion:^{
ToolsViewController *gototoolpage = [[ToolsViewController alloc] initWithNibName:#"ToolsViewController" bundle:nil];
[self.navigationController pushViewController:gototoolpage animated:YES];
}
How to dismiss the parentview also. Any idea
You code looks a little confused. What do you intend by references to parentViewController? Check the docs - it is the containing viewController, not the previous or presenting viewController. In a NavigationController context this would be the UINavigationController. In a modal view context there is no parentViewController, but there is a presenting ViewController. I am not sure what you intend by all of those calls to self.parentViewController.
In any case you should really be sending the dismiss request back to your presenting viewController via a delegate so that it is completely clear where the pushViewController message is being passed from and to.
In the header file of your signUpViewController declare a protocol:
#protocol SignUpViewControllerDelegate
- (void) dissmissSignUpVC;
#end
then in your presentingViewController, after
SignupViewController *signUpView = [[SignupViewController alloc] initWithNibName:#"SignupViewController" bundle:nil];
add
[signUpView setDelegate:self];
and implement the delegate method with the same code you now have in your completion block:
- (void) dissmissSignUpVC {
ToolsViewController *gototoolpage = [[ToolsViewController alloc]
initWithNibName:#"ToolsViewController" bundle:nil];
[self.navigationController pushViewController:gototoolpage animated:YES];
}
In signUpView invoke the delegate's method to dismiss:
[[self delegate] dissmissSignUpVC];
Watch out for those stacked animations, I suspect that only the first will be performed (i.e. gototollpage animated:YES might as well be gototoolpage animated:NO)
Perhaps anyway you should reconsider your logic. I imagine the user might have a confusing experience if you do this under-the-hood manipulation of viewControllers. Better that there is a UI control for the user to navigate to toolsViewController so they understand where they are?
I have been struggling with this problem for a while now, so any help would be greatly appreciated.
Here is the situation: My application has a UIViewController subclass called InitialViewController. This view controller has a UIButton, and when that button is pressed it creates a NSObject subclass called MyEngine. Something like this:
#interface InitialViewController : UIViewController <MyEngineDelegate>
...
#end
#implementation InitialViewController
...
-(IBAction)pressedButton:(id)sender {
MyEngine *engine = [[MyEngine alloc] init];
[engine start];
}
Inside start, I present a ViewController (ConflictViewController) modally to get the user's choice:
#interface MyEngine : NSObject <ConflictViewControllerDelegate>
...
-(void) start;
#end
#implementation MyEngine
...
-(void) start {
ConflictViewcontroller *cvc = [[ConflictViewController alloc] initWithNibName:#"ConflictViewController" bundle:nil];
cvc.modalPresentationStyle = UIModalPresentationFormSheet;
cvc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
cvc.delegate = self;
UIWindow *window = [(MyAppDelegate *) [[UIApplication sharedApplication] delegate] window];
[[window rootViewController] presentModalViewController:cvc animated:YES];
}
#end
ConflictViewController is really simple. It just waits for the user to decide, and when the user press the button, it send the message to the delegate, and dismiss itself.
-(IBAction)didSelectConflict:(id)sender {
UISegmentedControl *seg = (UISegmentedControl*) sender;
[self.delegate didResolveConflictChoice:seg.selectedSegmentIndex];
[self dismissModalViewControllerAnimated:YES];
}
I've checked every connection, all the delegates are working properly.
What is going wrong is:
When MyEngine receives the user's choice in it's implementation of didSelectConflict: it cannot continue properly because all of it's properties have gone null.
When the MyEngine presents the ConflictViewController, the program continues the execution and when start finishes, it goes back to pressedButton: and when this method is closed, the MyEngine object gets released.
What i want to know is if there is way around this ? Has anyone done something like this in another way ?
The question here is: How to get the user's choice properly when the choice is too complex to use UIAlertView.
Sorry for the long question, I simplified it as much as I could. Thanks for your time, any links, comments, or any kind of help is greatly appreciated
Why are you initializing MyEngine *engine in the IBAction, if you wish to use a MyEngine object why don't you make a global declaration in your InitialViewController and just call [engine start] in the IBaction. Then when the delegate method returns the selected index you can apply that to a global int in your initial view controller and continue on your way. Hope that makes sense
Make your method start as
-(void) startWithDelegate:(id)del {
ConflictViewcontroller *cvc = [[ConflictViewController alloc] initWithNibName:#"ConflictViewController" bundle:nil];
cvc.modalPresentationStyle = UIModalPresentationFormSheet;
cvc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
cvc.delegate = del;
UIWindow *window = [(MyAppDelegate *) [[UIApplication sharedApplication] delegate] window];
[[window rootViewController] presentModalViewController:cvc animated:YES];
}
and
-(IBAction)pressedButton:(id)sender {
MyEngine *engine = [[MyEngine alloc] init];
[engine startWithDelegate:self];
}
implement didResolveConflictChoice: in InitialViewController and get the delegate call there.
OR you can use UIActionSheet if suitable.
I would like to use the MFMailComposeViewController mailComposeDelegateproperty with completion block syntax, but not sure if that is possible. Something similar to how the TWTweetComposeViewController completionHandler property works:
TWTweetComposeViewController __weak *twee = tweeter;
tweeter.completionHandler = ^(TWTweetComposeViewControllerResult result) {
// code here
};
The code I have creates the MFMailComposeViewController, but then has to save a reference my viewController parameter, since this "share via email" functionality is not in a UIViewcontroller, but a custom MYSharing class.
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
// more code here
_viewController = viewController;
picker.mailComposeDelegate = self;
[viewController presentModalViewController:picker animated:YES];
The mailComposeController:didFinishWithResult:error: from MFMailComposeViewControllerDelegate uses that _viewController to dismiss the modal.
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
[_viewController dismissModalViewControllerAnimated:YES];
}
Not understanding blocks very well, is there some syntax that look like this?
picker.mailComposeDelegate = ^(???)(??? ??? ???) {
[viewController dismissModalViewControllerAnimated:YES];
}
I did find MFMailComposeViewController(BlocksKit) (which has source on github), but even with that, the syntax conversion trips me up.
No completion handler for MFMailComposeViewController now.
if you want to reference the view controller who presented the MFMailComposeViewController, code like below, use presentingViewController property.
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
[controller.presentingViewController dismissModalViewControllerAnimated:YES];
}
Why don't you just call [self dismissModalViewControllerAnimated:YES]; from the MFMailComposeViewControllerDelegate method?
I have an app that is based on a tab bar view with a welcome screen (that leads to either signin or sign up process). basically, if you are logged in - you go straight to the tabbar view and if not, you go to the welcome screen, where you can chose to either go to sign in or sign up. assuming that you go to either sign in or sign up, i would like the tab bar view to reappear, however, all the declarations are in the AppDelegate. how can I "go back" and call the tabbatcontroller? is the structure / flow of my classes correct at all?
so to repeat:
user signed in -> first view is tab bar view
user logged out -> welcome screen view --> sign in / up screen view --> tab bar view
what i am looking for is what do i need to write in this action method that is called once the user clicks on "sign in" in the sign in page:
-(IBAction)done:(id)sender {
?????
}
for reference, my appDelegate is:
if(user signed in)
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *viewController1 = [[FirstTab alloc] initWithNibName:#"FirstTab" bundle:NSBundle.mainBundle];
UIViewController *viewController2 = [[SecondTab alloc] initWithNibName:#"SecondTab" bundle:NSBundle.mainBundle];
UINavigationController *secondNavController = [[UINavigationController alloc]initWithRootViewController:viewController2];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, secondNavController, nil];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
}
else
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
SigninTabBarTemplateViewController *landingPage = [[SigninTabBarTemplateViewController alloc] initWithNibName:#"SigninTabBarTemplateViewController" bundle:nil];
self.window.rootViewController = (UIViewController *) landingPage;
[self.window makeKeyAndVisible];
}
There are many options you can consider.
This can be easily achieved with the use of delegate. If you want to close the VC that you presented modally, give it a delegate property. The delegate will be sent a message when required, letting it dismiss the VC. A good way to go with delegate is to write a custom procotol.
For example :
// the delegate will conform to this protocol
#protocol SignInVCDelegate
// this method will be called when required
//
-(void)signInCompleted;
#end
Now, make the object you want conforms to that protocol, for example the app delegate.
// .h
#import "SignInVCDelegate.h"
#interface YourAppDelegate : NSObject <..., SignInDelegate> {
...
SignInVC *signIn;
...
}
-(void)signInCompleted;
#end
The implementation looks like this :
// .m
...
-(void)signInCompleted {
...
[signIn.view removeFromSuperview];
}
-(BOOL)applicationDidFinishLaunching {
if(!logged) {
...
[signIn setDelegate:self];
[self.tabBarController presentModalViewController:signIn
animated:YES];
}
}
Now give signInVC a delegate property, which will be set before being presented modally, and send the delegate a message when the sign in process is completed.
// in .h
#property(retain) id <SignInDelegate>delegate;
// in .m
#synthesize delegate;
-(IBAction)validateSignIn {
...
[delegate signInCompleted];
}
You can write any method you want, this example is simplist, and it is useful to give the delegate some informations. In this case for example you could pass a user name, or user id, or what ever you want.
Another simple option is using notifications. This option lets any object informed when something happen, as long as it register for it. Given the same objects as the previous example, the app delegate will register for the notification, while the sign in view controller will post it.
// in app delegate .m
-(BOOL)applicationDidFinishLaunching {
...
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(signInCompleted)
name:#"UserSignedInNotification"
object:nil];
}
// in SignInVC.m
-(IBAction)validateSignIn {
...
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UserSignedInNotification"
object:self];
}
More informations about delegates and notifications in Communicating with Objects.
You could try doing something like this in the method where you know the user has successfully logged in. (Assuming SignedInTabbarViewController is your TabBarController)
SignedInTabbarViewController *signedInTabbarViewController = [[SignedInTabbarViewController alloc] init];
id mainDelegate = [[UIApplication sharedApplication] delegate];
[self.navigationController.view removeFromSuperview];
if( [mainDelegate respondsToSelector:#selector(setViewController:)]) {
[mainDelegate setViewController:signedInTabbarViewController];
}
UIWindow *mainWindow = [mainDelegate window];
[mainWindow addSubview: signedInTabbarViewController.view];
[signedInTabbarViewController release];