How to use Storyboards with multiple segues to one view controller? - ios

My app will use a simple login type selcector view controller, and from that it'll branch out to different view controllers, i.e. Sign Up, Sign In, and Sign In with Social networks. Now how do I add a segue from all of these to one single destination view controller. How do I add these multiple segues.

Every ViewController should have their own seque. To create, press "Ctrl" and move to the ViewController you want
And every ViewController should implement "prepareForSegue"
- (void)prepareForSegue:(UIStoryboardPopoverSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[MyDestinationVC class]]) {
//do something
//Example
MyDestinationVC *screen = (MyDestinationVC *)segue.destinationViewController;
screen.someValue = self.sendedValue;
}
}

If you have segues between one view(LoginVC) to others (login, social login, etc...)
System already knows last viewcontroller from which you came to newer viewcontroller.
You just execute
[self dismissViewControllerAnimated:YES completion:nil]; or put system barbuttons like UIBarButtonSystemItemDone to take care of dismissal by system itself
to go back.
Unless you have some other problem, this solution should work for you.

Related

Does an unwind segue release memory use of intermediate VCs, if no navigation controller is used? [duplicate]

I have an iPad app which has a lot of screens and a lot of segue options.
At the moment, I am simply using performSegueWithIdentifier to initiate these segues, and my fear is that I'm taking up a lot of memory as the user performs more and more segues.
I've seen that people recommend using the function popToRootViewControllerAnimated: if using a UINavigationController, but the problem is that I'm not using one.
How can I stop the number of VC's proliferating?
The way the app works, the user does constantly return to the root VC - effectively a search screen. So if I could clear the stack of VC's when such a segue is needed, that would solve my issue I think, but I have no idea how to go about this.
Thanks for any suggestions.
When you are using segues the flow moves backwards and forwards. When the user moves backwards (ie presses "back") then it will not push to a new VC but it will pop to a VC that already existed. When you pop, the current VC is removed from the stack and memory.
If you have segues to move backwards in the flow then this is wrong. You only need segues to move forward.
A PROPER PREPARE FOR SEGUE
In prepare for segue you should never create your own view controllers and push to them. The storyboard is there to do all of this for you.
A proper prepareForSegue method should look something like this...
- (void)prepareForSegue:(UIStoryBoardSegue*)segue
{
if([segue.identifier isEqualToString:"SomeSegue"])
{
MyNewViewController *controller = segue.destinationViewController;
controller.someProperty = "some value to pass in";
}
}
That is all you need. Note that you only need this if you intend to pass some information to the new view controller. If you are not passing anything forward then you don't need this method at all.
When the method ends the new VC will get pushed onto the screen by the storyboard file.
UNWIND SEGUES
If you have a random flow (like in your comment) then you can use unwind segues to achieve this.
In you 'A' view controller have a function like...
- (IBAction)someUnwindAction:(UIStoryboardSegue*)sender
{
//some action to run when unwinding.
}
It needs to receive a UIStoryboardSegue object. If set up as an IBAction you can also access it from Interface Builder.
Now when you want to go A > B > C > B > A then just used the standard push and pop (from the segue and back button).
When you want to go A > B > C > A then you can use the unwind segue from controller C.
If you have a cancel button or something in controller C and this is in the Interface Builder and this should take you back to controller A. Then in the Interface Builder underneath controller C you will have a little green square with a door and an arrow pointing out of it. Just point the action of the cancel button to this symbol and choose "someUnwindAction". (Note, unwindAction is in A, button is in C.) XCode then uses this to pop you all the way back to A and deals with removing any memory and stuff. If you want you can send additional information back to A too.
If you want to access this unwind segue from C programmatically then you can run...
[self performSegueWithIdentifier:"someUnwindAction" sender:nil];
This will also pop back to A.
imho I don't see any issues with using segues, it is much more simple than anything else. If you worry about memory consumed then just profile your app and see how much it eats and how often your "memory pressure handler" is called.
ok I believe I've figured this out. It appears to work.
Since I keep returning to a common VC, I am placing code in the places where I want to segue back to this 'root' VC to clear the VC stack.
I still perform the actual segue in my normal code
[self performSegueWithIdentifier:#"editBackToSearch" sender:self];
However in the 'prepareForSegue:' method, I no longer create a new VC object, instead I perform any work I have outstanding (save my data), and then clear the VC stack with the following code.
UIViewController *vc = self;
while ([vc presentingViewController] != NULL)
{
vc = [vc presentingViewController];
}
[vc dismissViewControllerAnimated:NO completion:nil];
It appears to run smoothly and has the desired impact (confirmed via the profiler) of releasing the memory that these VC's sucked up.
One last comment - for some reason I could not animate the dismissal command, this appeared to trigger an infinite loop and resulted with an EXC_BAD_ACCESS. With animation set to NO, it works great though.
Thanks for your help.
Swift 3: this is to prevent memory leaks in Swift 3 & 4
#IBAction func entryViewSelected(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}

Creating a central log out view controller in storyboard

I am working with Parse, and one thing I have implemented in my app is their built in PFLogInViewController. This controller will be presented at two times in the application - when the app first starts and the user is not logged in, and when the user taps the "Log out" button of my application (logging out takes them back to the PFLogInViewController, as you are required to sign in to use the app). I would like to set this up using Storyboard, as that is how the rest of my app is laid out. How could I set up a central view controller (a PFLogInViewController) that is accessed at these two times? I have already Subclassed PFLogInViewController and set it up, I just need advice on how to place it in Storyboard and how to connect it to my views. To make this question help as many people as possible, the general theme of my question is how does one establish a central Login/ViewController that can be accessed at different points in the application using Storyboard. Attached is the basic idea of what I'm trying to accomplish. I haven't been able to successfully segue to the initial TabBarController, and I'm not sure how I should make the LoginController the initial ViewController if I can't segue. I am programming in Swift, if it matters.
There are a few ways to do this depending upon your application. One way is drop a UIViewController onto the storyboard, but don't wire it up to anything (no segue). Create a storyboard id for it such as "MyLoginVC". Do the necessary subclassing of UIViewController and attach the class to your VC. Then, when you want to display the VC simply do the following or wire this up to your logout button
id destinationVC = [self.storyboard instantiateViewControllerWithIdentifier:#"MyLoginVC"];
[self.navigationController pushViewController:destinationVC animated:YES];
In addition, if you want to show the login VC as the very first VC when you launch your app, then perhaps in your AppDelegate
// Load Root view controller
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.rootVC = [storyboard instantiateInitialViewController];
self.window.rootViewController = _rootVC;
[self.window makeKeyAndVisible];
// Load Login view controller
id initialVC = [storyboard instantiateViewControllerWithIdentifier:#"MyLoginVC"];
[initialVC setModalPresentationStyle:UIModalPresentationFullScreen];
[_rootVC presentModalViewController:initialVC animated:NO];
When you finish with your login VC (i.e. successful login) then within login VC
[self dismissViewControllerAnimated:NO completion:nil];
and alternatively instantiate your first VC with something similar to the following from within login VC. Note, since you loaded the root VC above first, it is already there with the login VC sitting over it. When you dismiss login VC, the underlying root VC should be ready to rock and roll. Otherwise you can do the following:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
RootTabBarController *tbController = (RootTabBarController *)[self.storyboard instantiateViewControllerWithIdentifier:#"rootTabBarController"];
[self presentViewController:tbController animated:YES completion:NULL];
}
I think what you want is an unwind segue. Here are the instructions I follow for an unwind segue: https://github.com/bradley/iOSUnwindSegueProgramatically
If the link dies, here is what it said:
In your storyboard create two view controllers.
Subclass UIViewController twice, once for each of the view controllers in your storyboard.
Connect these view controllers to the view controllers in your storyboard.
Make a segue between the first view controller and the second by control+dragging from the first to the second.
Click on the segue you created and give it an identifier in the attributes inspector.
Make a button on the first view controller and link it to an IBAction in its UIViewController subclass.
When this button is pressed, the second storyboard should appear. To make this happen (we are doing it programatically) put the following into the implementation of the action you just created:
[self performSegueWithIdentifier:#"nameOfTheSegueBetweenOneAndTwo" sender:self];
Create a second method in the implemention of the first view controller with the following:
- (IBAction)returnToStepOne:(UIStoryboardSegue *)segue {
NSLog(#"And now we are back.");
}
This method will work to unwind any view controller back to this view controller. Notice that we implement the method in the view controller we wish to return to.
Go back to the storyboard. Focus in on the second view controller. If it is active, you should see a dark bar beneath it with 3 symbols on it. One of these is orange and when hovered over will show the name of the UIViewController subclass that this view controller represents. Control drag from this symbol woth the green symbol that means 'Exit'. You should see all available segue unwinds, which XCode automatically enumerates when you create segue unwind implementations inside UIViewController subclasses that you have shown on your stroryboard. Hence, you should see the segue 'returnToStepOne' as an option. Select it.
In your storyboard's document outline, find the section for the second view controller. You should see an item listed below it with a grey symbol that says something like "Unwind segue from ... to Exit." Click on this item.
Important and easily missed step follows!
On the right side of your storyboard, in the attributes inspector, you should see two fields. One for 'Identifier' and one for 'Action'. In most cases, the 'Action' field will have the text 'returnToStepOne:', which is what we want, but the 'Identifier' field will be blank. Fill this field with the text: 'returnToStepOne' (note that we leave out the colon).
Create a button on the second view controller and link it to an IBAction in its UIViewController subclass.
In the implementation for the method you just created, put the following code:
[self performSegueWithIdentifier:#"returnToStepOne" sender:self];
Run the application. You should now be able to unwind from the second view controller to the first.

CollectionView -> changing flow based on if logged in

I have an App using a Story Board, CollectionViews and a NavigationController setup. This works fine, but now I want to add in login.
So if the user is already logged in, I want to bypass the default behavior of going to the login screen.
What is the best way to change this flow?
thanks!
phil
Put logic in the viewDidAppear method of the navigation controller's root view controller that determines whether to show the login view or not. Present it modally with the animation set to no, and it will be the first thing the user sees.
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (!self.loggedIn) {
UIViewController *login = [self.storyboard instantiateViewControllerWithIdentifier:#"Login"];
[self presentViewController:login animated:NO completion:nil];
}
}
You can either tell the regular first view controller to instigate the segue in code when it appears (e.g. login view says: show next view if logged in).
Or something more radical in your app delegate, but I think it is safer to leave the flow as it is and just make it move on to the next screen and leave your storyboard untouched.

How to use segue to pass taken photo

I want to pass a photo taken in the first view controller to a second view controller. I want the user to take the photo in the first view controller and then crop in in the second view controller and then save.. So its like.. User take photo→crop→save. I just want to do this simple task but taking me days to get the segue go right.. Is segue the best way to do this? or is there a more easier way to do this task. I am a beginner in objective -c so its making me confused with segues and all the stuff.
You need to implement prepareForSegue:sender: in the source view controller. This will give you access to the destinationViewController via the passed storyboard. Then you can set the image so its available when the destination view controller is displayed.
As far as segue is concerned you need to create a segue arrow using storyboard by dragging arrow from first view controller to second view controller, and most importantly you need to give the segue an identifier or name.
For firing you segue you may do it programatically, like:
[self performSegueWithIdentifier:#"ShowSecondScreen" sender:self];
For handling things when segue is fired you need to write prepareForSegue() method, you may pass any object from current viewController to next viewController:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowSecondScreen"])
{
SecondViewController *secondViewController = segue.destinationViewController;
secondViewController.imageObj = image;
}
}

Drawing in segue vs writing code to transition views - why does data sometimes not transfer?

Let's say I have a UILabel on ViewControllerA and and UITextField on ViewControllerB. I want to go to ViewControllerB and input text then press a button to go back to ViewControllerA. The UILabel should now read whatever was typed in the UITextField.
I was able to accomplish the above by using NSUserDefaults and also using delegation. I am using Storyboards to do this. My question is about the segues used in the storyboards.
It seems when using delegation I must go to and from the storyboard with code and not visually connect the view controllers with a segue in order for the data to transfer. Here is the code when I press a button on my ViewController A:
- (IBAction)pressFirstButton:(id)sender {
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
RBViewController2 *vc2 = [sb instantiateViewControllerWithIdentifier:#"ViewController2"];
[vc2 setDelegate:self];
[self presentViewController:vc2 animated:YES completion:NULL];
}
when passing the data back from ViewControllerB to ViewControllerA I do this:
- (IBAction)buttonSegueBackTo1:(id)sender {
NSString *sendThis = self.textFieldVC2.text;
[self.delegate passTextFieldInput:sendThis];
[self dismissViewControllerAnimated:YES completion:NULL];
}
No segue has been drawn between the view controllers and everything works fine. If I don't write this same code, and draw in a segue, the data won't pass backwards. However when I try passing data like this using NSUserDefaults I don't have to write the code to go to and from the view controllers. Instead I can simply connect the view controllers with a drawn segue. The weird thing is, if I'm trying to pass the data in using NSUserDefaults when manually coding the view controllers (an not drawing the segue) the data doesn't transfer.
I'm thinking maybe instead of writing the code in the -(IBAction) pressFirstButton:(id) sender method, I should be putting the code in the prepareForSegue method.
My question is why do drawn segues sometime cause data to be lost? Must all delegation be done without drawn segues? If NSUserDefaults require segues to transfer properly and delegation require code to transfer properly then if I have a view that requires both, it seems that NSUserDefaults will trump the delegation b/c the manual segue being used "resets" the view and only the NSUserDefaults data remains.
Normal segues (any other than unwind segues) ALWAYS create new view controllers. So, if you're using anything other than an unwind segue to go back to A, you're really not "going back", you're creating a new ViewControllerA. Unwind segues aren't normally used in a case like you're presenting, just going back one controller, but you could.
This situation also isn't a good place to use user defaults. The Apple recommended way, is to use delegation, like you do in the code your question. The way you show, is probably the best way to do it, rather than using a segue to go back. You certainly could use a segue to go forward though, and in that case you would implement prepareForSegue: so you can set yourself as the delegate and/or pass any data forward.
Your question is a little dense to parse but I think you are asking if there is a way to get pass data through segues. The answer is yes, and much better than the workarounds you are trying.
In your button you would call:
[self performSegueWithIdentifier:#"scrollerSegue" sender:self];
This will trigger the method below and it is here that you can set the data on the destination viewController. You can set abstract properties on the destination viewController but you can't populate an UIKit elements (labels, imageViews, etc.) because they don't exist yet. Instead set properties and then in viewWillAppear in the destination viewController, do the set up as needed. (alternatively, instead of passing data, you could just set the delegate and then call methods on the delegate to get the data as needed).
For getting data back, using the delegate and calling methods on it seems to be the Apple recommended way of doing things.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"scrollerSegue"])
{
ScrollViewController * target = segue.destinationViewController;
target.assetsArray = self.assetsArray;
target.delegate = self;
}
}

Resources