Dismissing modal view controller from app delegate - objective-c

I am using the facebook SDK to login to my app. If the user is not logged in, the login VC modally appears. Once the user has tapped log in, it alerts the App Delegate if the login was successful. If it was, I want to dismiss the modal login VC. How do I do this from the app delegate?

You could try dismissing the presented ViewController, as something has to present the modal view controller
UINavigationController *navigationController = (id) self.window.rootViewController;
[[navigationController presentedViewController] dismissModalViewControllerAnimated:NO];
If you would want to check if a specific ViewController is being presented (i.e. only dismiss when a certain one is shown) then you could add in a check.
UIViewController *viewController = [navigationController presentedViewController];
if ([viewController isMemberOfClass:[YourViewController class]]) {
[viewController dismissModalViewControllerAnimated:NO];
}

The appDelegate needs some way to know who the hosting viewController is, so it can send the dismiss message. You need to figure out some way to make this happen. One way would be to define an ivar on the appDelegate "callDismissOnMeIfFaceBookFails", and set it when you are in this situation, otherwise its nil.
Note if its nil, the appDelegate can send the dismiss message with no overhead no problems! Use nil messaging to your advantage (I use it all the time). [Aside: I see so much code "if(obj) [obj message];" Don't do the if - just send the message - if obj is nil it has no effect and is handled efficiently!]
EDIT:
So you have a class AppDelegate. In the #interface define a property:
#property (nonatomic, strong) UIViewController *callDismissOnMeIfFaceBookFails;
and in the implementation you #synthesize it. Define a method:
- (void)dismissLoginView
{
[callDismissOnMeIfFaceBookFails dismissModalViewControllerAnimated:YES];
callDismissOnMeIfFaceBookFails = nil; // don't need it now, this unretains it
}
So, before the modal view controller is presented, the presenting object sets the appDelegate property "callDismissOnMeIfFaceBookFails" to itself.
When the user has successfully logged in, the login object sends the message to appDelegate telling it to dismissLoginView.

Related

Connecting button programmatically XCode9, Objective-C, OSX

im on OSX, XCode9, Objective C.
I have a viewController layouted in IB. The view contains a button connected to the corresponding viewController
SHTourViewController.h
#property (weak) IBOutlet SHStandardButton *closeButton;
// SHStandardButton is a subclass from NSBUtton.
The view controller gets instantinated by code in another class (i need to instantinate this viewController from other classes cause i need it more than once).
// Get instance of viewController
SHTourViewController* tourViewController = [storyBoard instantiateControllerWithIdentifier:#"tourViewController"];
Now in viewDidLoad method of my viewController, i like to connect the buttons action and target:
SHTourViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self.closeButton setAction:#selector(closeButtonClicked:)];
[self.closeButton setTarget:self];
}
- (void)closeButtonClicked:(id)sender {
NSLog(#"CLOSE!");
}
}
When i click the button, the app crashes (Thread 1: EXC_BAD_ACCESS). I can't find the mistake i am doing here.
Any help appreciated.
You are not supplying enough information about what you're doing. But, as it stands, the fact that fact that you are getting a Bad Access would suggest that some important object has vanished prematurely in a puff of smoke. My guess is that that object is self, and that the problem has to do with code after this line:
SHTourViewController* tourViewController =
[storyBoard instantiateControllerWithIdentifier:#"tourViewController"];
You are obtaining a completely new instance of this view controller but then you are not getting its view correctly into the view hierarchy and the view controller itself into the view controller hierarchy, so the view controller (I'm guessing) is released.
But you didn't show us the relevant code, so that's just a guess.

UIView Login screen to tabbar logic

Folks,
I'm having trouble with some navigation logic. Currently I have a simple two tabbed tabbar application. But I want to show a loginscreen in front. So that would be an UIView.
Currently the code is as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *viewController1 = [[roosterViewController alloc] initWithNibName:#"roosterViewController" bundle:nil];
UIViewController *viewController2 = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = #[viewController1, viewController2];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
So this pushes a simple tabcontroller. Well, now I want to have a login screen. So that would be a simple UIView which pushes the tabbar controller. But I can't seem to see the logic on how to do this.
I've been trying to present a modal view controller, but the thing is: the tabbar will be loaded on the background. Since I need the username/password information to work on the tabbarview, this won't work.
My Logic would be:
delegate > load loginViewController > load tabbar controller
But, then I need to be able to "logout". So I need to destroy the tabbar controller and present the login screen.
Any thoughts on this?
You could do something like this:
You could create a protocol which your AppDelegate conforms to.
#protocol Authenticator <NSObject>
#required
- (void)authenticateWithUsername:(NSString *)username andPassword:(NSString *)password;
- (bool)authenticated;
#optional
- (void)authenticationSuccess;
- (void)authenticationFailure;
...
#interface AppDelegate : UIResponder <UIApplication, Authenticator>
#property (readonly, nonatomic, assign) bool loggedIn;
...
#implementation AppDelegate
#synthesize loggedIn = _loggedIn;
- (void)authenticateWithUsername:(NSString *)username andPassword:(NSString *)password
{
//if success
_loggedIn = YES;
//check if app responds to the optional authenticateSuccess method
//call it if it does
//else fail
//do stuff
}
- (bool)authenticated
{
if (_loggedIn != NULL) {
return _loggedIn;
}
//do other stuff
}
...
I am a bit fuzzy on proper objective-c conventions and syntax so forgive me if I have a few errors, but anyways that is some logic and pseudo-code to work off of. Tweak that to your needs.
I hope this helps.
EDIT:
I guess my answer was a bit unfinished. It seemed to to me the answer to your question was strongly connected to some kind of authentication structure. If your app is so closely connected to authentication, then why not control its flow through the authentication structure. I guess that was my point, and since all this would be conveniently and readily available in your apps delegate, you could call these methods anywhere you wanted, therefore letting your authentication logic decide which view controller to show for example.
As mentioned have the login as the rootView and if the login is successful enable the other tabbarButtons else don't enable them like this..initially set the bool to false on view load then if successful enable a tabbar button else don't.
login = TRUE;
UITabBarItem *reportit = [[[[self tabBarController]tabBar]items] objectAtIndex:2];
[reportit setEnabled:TRUE];
else {
UITabBarItem *reportit = [[[[self tabBarController]tabBar]items] objectAtIndex:2];
[reportit setEnabled:FALSE];
}
I recommend you set the login screen as root. When the login is successful, you simply change the root of the window to the tabbar controller, with a nice animation.
I would recommend that you set up the tab bar just like you have it right now, but immediately following the makeKeyAndVisible you instantiate and present the login view controller, without animation.
This way once the app has launched the user does not see the tabBarController, but only the modally presented login screen. Once login is done you simply dismiss the login view controller and beneath it appears .... drumroll the tabbarcontroller!
Expanding on Levi's answer, this is how you switch the root view controller continuously (with an animation). Just add this extension:
extension UIViewController
{
func transitionToRootViewController(viewController:UIViewController)
{
UIView.transitionWithView(self.view.window,
duration: 0.3,
options: UIViewAnimationOptions.TransitionCrossDissolve,
animations: {
window.rootViewController = viewController
},
completion: nil
)
}
}
...to the UIViewController class (that will make the method transitionToRootViewController () available to all your view controllers), and call it on the exiting view controller, passing the entering view controller (that you perhaps instantiated form a separate storybord, who knows...) as a parameter.
(The basic idea was taken from here. I simplified the code a bit)
Beware though: I tried this code to insert a tab bar controller, whose selected index (tab) contained a navigation controller. During the transition animation, the navigation controller's navigation bar "underlaps" the status bar (carrier, clock, battery level), and only after the transition animation completes, it "jumps" into its place immediately, creating a horribly distracting (and unpolished) effect.
I fixed it by adding the following code to the child view controller embedded in the navigation controller:
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
self.navigationController?.navigationBar.layer.removeAllAnimations()
// ^ THIS
}
(This fix was taken from this forum.)
I haven't checked, but the same issue might occur whenever you transition into a navigation controller (or any plain-vanilla UIViewController that happens to have a navigation bar attached), whether it is embedded in a tab bar controller or not.

dismissViewControllerAnimated completion block presenter view and modal view flow

I did find an answer to this title and I did do a little research but I'm still not getting the flow. Here is what I want to happen:
1) click a button on the presenter view to open a modal view. 2) retrieve some value and click a button to close the modal view....sending the value to the presentor view and execute a method.
I get that this works like a callback but I still can't figure out where to put the callback stuff.
So, how exactly do I do this? A) In the presentViewController completion block, should I include the presenter view method to execute when modal view is completed?
Or: B) In the modal view's dismissViewControllerAnimated completion block, should I include the presenter view method to execute when modal view is completed?
Can somebody help me with some sample code? Or at least help me get the flow of which block to put the code in?
Thank you, P
You talk about completion blocks so I am assuming you do not want to use delegates.
In the viewController that will be presented modally you need to provide a public completion handler, that will be called when it is dismissed.
#interface PresentedViewController : UIViewController
#property (nonatomic, strong) void (^onCompletion)(id result);
#end
Then in the implementation you need to call this completion block on dismissal. Here I assume the viewController is dismissed on a button click
- (IBAction)done:(id)sender
{
if (self.onCompletion) {
self.onCompletion(self.someRetrievedValue);
}
}
Now back in the viewController that presented the modal you need to provide the actual completion block - normally when you create the viewController
- (IBAction)showModal;
{
PresentedViewController *controller = [[PresentedViewController alloc] init];
controller.onCompletion = ^(id result) {
[self doSomethingWithTheResult:result]
[self dismissViewControllerAnimated:YES completion:nil];
}
[self presentViewController:controller animated:YES completion:nil];
}
This will create the new viewController to be presented modally and define what needs to happen on completion.
You can do this with delegates, that's the way Apple seems to recommend, but that seems like overkill to me. You have a reference to the presenter with the presentingViewController property, so you can just set the value of a property in the presenter from the presented controller in the button click method:
self.presentingViewController.someProp = self.theValueToPass;
[self dismissViewControllerAnimated:YES];
Using delegates is a good way to handle this:
In your PresentedViewController.h
#protocol PresentedViewControllerDelegate <NSObject>
-(void) viewWillDismiss;
#end
#property (nonatomic, weak) id <PresentedViewController> delegate;
Then in your PresentingViewController.h, you would subscribe to this delegate
#interface PresentingViewController : UIViewController <PresentedViewControllerDelegate>
in the .m you must implement the delegate method
- (void) viewWillDismiss {
}
and before you present the view controller set the delegate property you made as self.
presentingViewController.delegate = self;
Obviously not every implementation detail has been done here, but this should get you started.

How to present a view controller from another view controller

I am trying to open a ViewController from within another ViewController if certain conditions are met. The code seems to run without error but the view is never shown. I am new to xcode 4 /ios 5 so I must be missing something.
Here is the code responsible for opening the second viewcontroller:
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
[self presentViewController:createUserController animated:YES completion:nil];
In my project I have a xib called, "CreateUserView". I have added a view controller to this xib and assigned it to, "CreateUserViewController".
Also I noticed in the apple documentation that is shows setting the delegate of the viewcontroller to be presented. But it seems that no property called, "delegate" is on the viewcontroller object. Is this documentation old? This is the document I am trying to use (section 9-1):
View Controller Programming
Can someone give me a hint? Thanks..
edit Adding Custom Constructor
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil keyWrapper:(KeychainItemWrapper *)keyWrapper
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self){
[self setKeyChainWrapper:keyWrapper];
}
return self;
}
Regarding CreateUserView.xib: you don't want to put a CreateUserViewController object in the nib. You want to set the custom class of the File's Owner placeholder to CreateUserViewController. Then you need to connect the view outlet of File's Owner to the top-level view in the nib.
Regarding the delegate property: The UIViewController class doesn't have its own delegate property. The idea is that you add a delegate property to your subclass of UIViewController. The delegate provides a way for your presented view controller to pass custom information back to the presenting view controller.
Why would you want to do that? Let's consider the code you posted. I'll assume you have a UserListViewController that shows a list of User objects, and has a "Create new user" button. When the user touches the "Create new user" button, you create a CreateUserViewController and present it.
The user interacts with the CreateUserViewController to set the attributes of the new User object - name, rank, hairstyle, etc. Then he touches a "Done" button. Your CreateUserViewController creates the new User object and puts it in the database. Then it needs to dismiss itself, so the UserListViewController's list of User objects will appear again.
But you want the User list to include the newly created User object and you want to scroll the list so that the new User is on the screen. So you need a way to have your CreateUserViewController tell the UserListViewController about the newly created User object. This is where the delegate comes in.
You define a protocol like this:
#protocol CreateUserViewControllerDelegate
- (void)didCreateUser:(User *)user;
#end
and you give your CreateUserViewController a delegate property:
#interface CreateUserViewController
#property (weak, nonatomic) id<CreateUserViewControllerDelegate> delegate;
// ...
When your CreateUserViewController's "Done" button is touched, you notify your delegate of the new User:
- (IBAction)doneButtonWasTouched:(id)sender {
User *user = [self createUser];
[self.delegate didCreateUser:user];
[self dismissViewControllerAnimated:YES completion:nil];
}
In your UserListViewController, you adopt and implement the protocol:
#interface UserListViewController <CreateUserViewControllerDelegate, UITableViewDelegate, UITableViewDataSource>
// ...
#end
#implementation UserListViewController
- (void)didCreateUser:(User *)user {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.users count] inSection:0];
[self.users addObject:user];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition: UITableViewScrollPositionNone animated:YES];
}
and when you need to present a CreateUserViewController, you set the new controller's delegate to the UserListViewController:
- (IBAction)createUserButtonWasTouched:(id)sender {
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
createUserController.delegate = self;
[self presentViewController:createUserController animated:YES completion:nil];
}
In iOS5 the method for pushing new view controllers was really changed around quite a bit from iOS4 and Xcode 3. In summary, storyboards are now used to create your application view controller flow. Even though you may use standalone .xib files to build an application it is much less common in iOS5.
Anyway, the main method for pushing new view controllers onto the screen is done using segues. Check out this tutorial for an introduction: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1
It does a good job on explaining how to create a storyboard and use segues. You can still present view controllers in code "the old way" but it is much much less common now with the introduction of these new technologies. There are also some absolutely awesome tutorials on iTunes U - search for CS193P. It's the Stanford Introductory class to Objective-C and programming for iOS. This should get you started and maybe help you think of a way to push your createUserController in a way more up to speed with iOS5.
UPDATE
I just wanted to add. If you configure your program to use storyboards and segues you can use the method performSegueWithIdentifier:sender: to perform the segue to your createUserController view if the proper conditions are met. See the Apple API for UIViewController for information on how to use this method.

UIViewController parentViewController access properties

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.