I am working on an iOS 8 application that requires authentication to a backend. I would like to make the app in such a way that, if the app receives an HTTP 403 error, the user is presented with a login screen. After the login screen view controller successfully authenticates the user, the app should navigate back to the previous screen (whatever screen it was).
In Xcode 6, I can see that the preferred way to navigate between scenes is via segues. As such, I am using segues to navigate back from the login screen, with this code:
[self performSegueWithIdentifier:#"UnwindToNewsfeed" sender:self];
The problem that this presents is, I would have to establish segues to all scenes within the app, as any of them might trigger an authentication request. Therefore, I would like to ask for a recommended approach to implement this navigation requirement.
I'm new to iOS8 and to iPhone programming in general, so it could be that I don't have iOS 8's navigation concepts entirely clear and I'm using a wrong approach.
Unwind segues are different to forward segues and this makes it quite simple to support the functionality you are after.
Prior to creating an unwind segue you need to add a method to the view controller that you want to unwind to. For example -
- (IBAction)unwindFromLogin:(UIStoryboardSegue*)sender {
}
You can then create an unwind segue by ctrl-dragging between an object in your scene (or the UIViewController object for your scene if you want to trigger the unwind with performSegueWithIdentifier) and the exit icon at the bottom of the screen. Interface Builder will then display the list of methods it found that match the signature above (so it will display unwindFromLogin:). You can give this segue an identifier so that you can invoke it with performSegueWithIdentifier as you would normally do. So far so good.
Now, for the clever bit. At runtime when the unwind segue is invoked, iOS looks through the current view controller stack to find the first view controller that implements the nominated method - so if you implement the same unwindFromLogin: method in each of your view controllers, your login view will unwind to the view controller it came from and you only need a single unwind segue in your login view controller scene.
Apple has a good Tech Note that describes the unwind process in more detail and how you can customise it by implementing additional methods in your UIViewController subclass, but the default implementation should suit your needs.
If you don't want to create a segue from each source view controller to the login view controller you can present it directly using something like -
LoginViewController *loginVC=(LoginViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"loginVC"];
[self presentViewController:loginVC animated:YES completion:nil];
You can still use the unwind segue to get back
I don't think using segues is always the best approach. In a case like this, it might be better to use -popToViewController:animated:.
That said, using an unwind can still work here. I would recommend creating a custom view controller that subclasses UIViewController. This view controller implements your -unwindXXX: method.
#interface MYBaseViewController : UIViewController
- (IBAction)unwindXXX:(UIStoryboardSegue *)sender;
…
#end
Have all the view controllers which can be unwound to from the login view controller inherit this custom view controller.
#interface MYNewsFeedViewController : MYBaseViewController
…
#end
I consider the two answers below to be valid, and I voted them both up.
However, after testing, I found this approach to be simpler and less cumbersome to my scenario.
On any screen that can trigger a login prompt, I force the login screen to be presented by calling the following method:
LoginViewController *loginView = [self.storyboard instantiateViewControllerWithIdentifier:#"LoginView"];
[self presentViewController:loginView animated:YES completion:nil];
Then, in order to enable the back navigation functionality required after the user successfully authenticates in the login screen, the login screen calls this method, which returns to the previews scene:
[self dismissViewControllerAnimated:YES completion:nil];
As I am new to iOS dev, I am not sure if this is following recommended practices, so I'd like people to comment on whether this is a recommended approach or not.
Related
In storyboard we have great feature that allow us to make Show (e.g. push). So seems the logic is next:
If we don't have navigation controller then view controller will use present modal logic. My question is there any inverse action that I can use with Show?
I have a UIButton that close current view controller screen:
- (IBAction)onTappedCloseButton:(id)sender
{
[self.navigationController popViewControllerAnimated:YES];
}
But in case if I don't have navigation controller, how can I simple use inverse action to go back? So my solution is to check if self.navigationController is nil then use dismissing option:
[self dismissViewControllerAnimated:YES completion:nil];
But maybe there is another cool solution like Show (e.g push). But Close (e.g. pop)?
Yes, you can use an unwind segue to go back, and it will be the reverse of whatever the forward segue was.
You have two options on how to do this:
1) The Unwind segue
To make an unwind segue you have to add a method in the view controller you want to "unwind" to with the following format:
-(IBAction)someSelectorName:(UIStoryboardSegue *)sender;
You will then be able to drag from your UIButton up to the "exit" icon in your storyboard.
Wire it up to the selector you just defined and UIKit will figure out how to get back to that view controller without you having to write any code. This can be especially useful as it can figure out when it needs to call -dismissViewControllerAnimated: more than once and can call those methods successfully. It can even unwind from within a view controller embedded in a navigation controller when the view controller you're unwinding to has the navigation controller presented on top of it. (i.e. it will do a dismissViewController instead of a pop to unwind)
2) The Custom unwind method
Say you don't want to or cant trigger this action from a storyboard. There is still an option and its detailed over at this question here:
Whats the programmatic opposite of showViewController:sender:
The gist is you can write your own generic dismiss method by implementing categories on the UIKit container View controllers (or on your own container)
I have a category on UIViewController that deals with errors coming from my networking layer. If I get an authentication error in response to a network call, I want to perform an unwind segue which takes me back to my LoginViewController.
However, I don't want to have to add the appropriate unwind segue to every single view controller in my Storyboard. Can I simply declare the unwind segue in the UITabBarController that is at the "top" of my view controller navigation, and then say
[self.tabBarController performSegueWithIdentifier:#"UnwindToLoginSegueIdentifier" sender:self]
... from inside my UIViewController+ErrorHandling category?
No, you can't do that. The unwind segue has to come from the controller you're unwinding from, and all segues need to be connected from a particular instance in the storyboard. The login view controller really should be one that's presented modally, not be one of the tabs, since you only need it briefly, then it should go away. If you set up your app that way, you can present and dismiss it from any controller (present it without animation from the controller in the first tab, if that's what you want the user to see when the app launches). You'll still have to have code in every controller to do that, unless you make a common base controller with that functionality that all the other controllers inherit from.
I have an screen (My Profile) which can be accessed from two paths:
Login -> Content -> Profile
Login -> Register -> Profile.
In both paths, view's are shown with:
[self.navigationController pushViewController...];
But my problem is, after the user registers and completes his profile, it should go "Back" to the Content view. (Obviously it won't work with navigation controller stack, since Content isn't in the navigation controller).
My question is, what suggestions do you have?
PS: I know this isn't an actual question, but I've been thinking about this for a few hours now and I didn't come up with anything. Maybe some of you have had to deal with similar cases.
Edit: Basically the question can be generalised to:
How do you deal with a circular application flow?
Edit: I've solved this by pushing from Register to Content and then Profile in viewWillAppeare without animation (so what I need is in the stack), but I'm still interested in dealing with circular application flows.
My suggestion would by to change [UINavigationController viewControllers].
So after you end your registration you can do something like
ContentVC *content = [[ContentVC alloc] init];
[self.navigationController setViewControllers:[NSArray arrayWithObject:content]];
[self.navigationController popToRootViewControllerAnimated:YES];
Push Contentview controller after profile complated in second case , while in first case you have already pushing it.
1)Make your Login viewController the rootController of the a UINavigationController.
2)From Login you can Push Content ViewController
3)From Content VC you can push to Profile VC.
Now if you want to get back to Content from profile do this:
[self.navigationController popViewControllerAnimated:YES];
And if you want o get back from Profile directly to Login do this:
[self.navigationController popToRootViewControllerAnimated:YES];
If you do not want the navigation bar you can hide the bar.
Update
Take a different approach, other than the navigation controller stack:
1) Make a controller class with a 2 functions:
-(void)loadViewControllerWithIndex:(int)index;
and
-(void)unLoadCurentViewController;
2) You can call these functions and load and unload the view controllers from a this controller class.
3) So you initially load this class and import file in this class of other view controllers. You can take a UIViewController object topViewController, so that you can you can keep a track on which controller is currently displayed and it will help when you want to unload a controller.
4) In load controller with Index function you can add the controller view and in unload you may remove them.
I have an app that has the following basic layout, please understand I have done a lot of programming, but I am relatively new to IOS and am yet to wrap my head around the Storyboards/segues properly yet.
Effectively, my app has the following screens:
WelcomeViewConroller ---ModalSeque--> MenuViewController --modalSegue---> newProjectVC || loadprojectVC ---modalSegue-->ProjectScreenVC.
From the project the screen, the user can return to the menuVC screen.
Now, I understand that every segue creates a new instance of a view controller, which is great, I want this to happen, however, when I segue back from my ProjectScreen, and then reenter it again, I get a huge memory leak and very strange behaviour.
I understand that I need to dismiss my View controllers, especially my ProjectScreen when I leave it, however, I can not get this to happen, no matter what I try.
Any help would be greatly appreciated.
In How should I chain viewcontrollers in xcode storyboard? I enumerate a series of ways of going back multiple scenes in a storyboard. But in short, the two easiest options are:
Unwind segues: If only supporting iOS 6 or higher, use unwind segues. So, for example, in your main menu's view controller, implement a unwind segue:
- (IBAction)gotoMainMenu:(UIStoryboardSegue *)segue
{
// if you need to do anything when you return to the main menu, do it here
}
Also make sure to define that in the main menu's .h. Then control+drag from the button that you want the segue to the "exit" button in the panel below the scene, and choose the "gotoMainMenu" option:
Navigation controller: If you need iOS 5 support, then just use a navigation controller and replace the modal segues with push segues. Then you can use popToViewController or popToRootViewControllerAnimated to jump back multiple scenes. If you don't want to show the navigation bar, then select the navigation controller in your storyboard, and in the attributes inspector, uncheck "Shows Navigation Bar":
In this scenario, I actually think it's easiest to make sure your menu scene is the root (and have it do a little detour to the welcome screen, like I discuss in point 4 of that other answer), in which case you can just call popToRootViewController whenever you want to return to the main menu. But, if the main menu is not the root view controller, and you want to pop back to it, you can either pass a point menu controller from scene to scene, or you can have subsequently presented view controllers do something like the following when they want to get back to the main menu:
for (UIViewController *controller in [self.navigationController viewControllers])
{
if ([controller isKindOfClass:[MenuViewController class]])
{
[self.navigationController popToViewController:controller animated:YES];
break;
}
}
Elsewhere on Stack Overflow, you'll see people contemplating ways to nest calls to dismissViewControllerAnimated, or other variations like that. I personally think that navigation controllers and unwind segues are far easier and more elegant.
I am creating an app using iOS 5 SDK. I managed to push views using the Storyboard's Segues, but I cannot find the proper way to pop the current view and go back to the previous one.
I am not using any navigationController (the app doesn't have any top or bottom bars).
I don't think using modal or push segue the other way would be the solution as it instantiates a new controller.
Do I have to use a custom Segue with the opposite animation and deletion of the view at the end ? Or is there a better way ?
Storyboards in iOS 5 don't provide a "no-code" way to return from a segue -- that's something you'll need to implement yourself.
If you use "push" segues (which require a navigation controller), use the navigation controller's popViewControllerAnimated: method to undo the last push segue. (Or other methods to undo more; see the UINavigationController documentation.)
If you use "modal" segues, call dismissViewControllerAnimated:completion: on the view controller which presented the current view controller (which you can get from its presentingViewController property).
Update: In iOS 6 and later there's unwind segues for going "back" in a storyboard. It's still not a no-code solution -- and it shouldn't be, because you need to be able to do things like differentiating between "Done" and "Cancel" exits from a modal view controller. But it does let you put more of the semantic flow of your app into the storyboard. Apple has a tech note that describes them in detail, and they're also covered in the video from WWDC 2012 Session 407.
You could try calling [self dismissViewControllerAnimated:YES completion:nil]; from the controller you want to dismiss (whether the controller has been pushed, or shown modally).
Here is the related documentation : UIViewController Class Reference
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
Just to clarify.
In the class that was pushed. Simply wire up the following and the controller and view will be popped off.
[self.navigationController popViewControllerAnimated:YES];
Create Segue type "Custom" on your stroyboard. This can be from a button.
Create a new UIStoryboardSegue class named "popSegue"
In the popSegue.m file add the following;
-(void)perform{
UIViewController *sourceViewContreoller = [self sourceViewController];
[sourceViewContreoller.navigationController popViewControllerAnimated:YES];
}
-In the storyboard editor.
-Select the segue and change the Segue Class to "popSegue"
-Set the Identifier to "popSegue"
Done!
You can use the same "popSegue" class throughout your project.
Hope this helps
I'm using Xcode 5 also and here's how it's done. First, in the view code file that pushed the other, create an IBAction method in the .h file such as this:
- (IBAction)exitToHere:(UIStoryboardPopoverSegue *)segue sender:(id)sender;
Then in the .m file add this:
- (IBAction)exitToHere:(UIStoryboardPopoverSegue *)segue sender:(id)sender {
}
You can add any cleanup code you want executed in this method. Next go to your storyboard and select the pushed view. I assume you've got some kind of button on the view that the user taps to signal he's finished. Click on that button, hold down the key and drag to the the green box below the view which is the Exit. Release the mouse button but continue to hold the key. A popup will appear and your method will show in the list. Select that method. Now when the user clicks on the button, the view will pop and you'll be returned to the starting method.