I have a simple app with 2 screens.
When I press a button to go from the first to the second, everything is performed successfully (including animation). However, when I click the back button on the second screen, I get the following warning:
Warning: Attempt to present <getTextViewController: 0x8f6aa30> on <SecondViewController: 0x946cc80> whose view is not in the window hierarchy!
EDIT: Please don't refer me to other questions regarding above warning - I already saw those, and they refer to other issues.
However, it still switches back to the first screen. Yet, the animation of the segue does not perform.
Also: Information (such as inputted text) in the first screen remains when I return to the first screen, while information in the second screen resets every time the screen comes up.
Here is how I call both operations:
Segue from View 1 to View 2:
Name: F21, Style: Modal, Transition: Cross Dissolve, Animation: True.
Segue from View 2 to View 1:
Name: F12, Style: Modal, Transition: Cross Dissolve, Animation: True.
Code in getTextViewController.m (View 1):
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([[segue identifier] isEqualToString:#"F21"]){
UIViewController *v = [segue destinationViewController];
[self dismissViewControllerAnimated:NO completion:nil];
v = self;
}
}
-(void)performSegue:(NSString*)str{
[self performSegueWithIdentifier:str sender:self];
}
//In some other method:
[self performSegue:#"F21"];
Code in SecondViewController.m (View 2):
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([[segue identifier] isEqualToString:#"F12"]){
UIViewController *v = [segue destinationViewController];
[self dismissViewControllerAnimated:NO completion:nil];
v = self;
}
}
-(void)performSegue:(NSString*)str{
[self performSegueWithIdentifier:str sender:self];
}
- (IBAction)goBack:(id)sender {
[self performSegue:#"F12"];
}
I would very much appreciate any help to understand why the first segue works while the second doesn't.
Thank you,
Dean
NOTE: Here is the full project - https://github.com/dean13-meet/firstIOSApp
EDIT: Updated git.
Im not exactly sure what you're trying to do in your prepareForSegue, their is no need to be dismissing VC's there. If you want to have a simple app where you go from VC1 to VC2 and then back again, your best bet is to use a segue and an unwindSegue.
So in your storyboard control drag from a button on VC1 to VC2 and select your segue type. Then in VC1.m setup the unwind segue such as:
- (IBAction)unwindFromViewController:(UIStoryboardSegue *)segue
{
//empty implementation
}
Finally, in your VC2 control drag from the back button to the green exit icon on VC2 and select your unwindFromViewController method.
That should do what you're looking for.
For the sake of simplicity, I would suggest using a push segue opposed to modal because it takes care of all the back buttons for you. If you don't like the idea of a navigation controller however, try dismissing the view with the following: Moving back from a Controller to a previous one
Related
How do you make a programmatic segue?
I tried this:
[self performSegueWithIdentifier:#"next" sender:self];
but it only gives me this console message:
Snapshotting a view that has not been rendered results in an empty snapshot.
Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.
What I want to do is Have a button which triggers a image picker to appear. When the user has selected an image, a segue to the next view controller is performed.
I don't have enough reputation to add a comment but if you are attempting to present UIImagePickerController I would advice you to read this post
You could prepare the segue before showing it (Supposing you already connected the segue in the Storyboard and named it)(You wait for the execution block of the picker to complete before performing the Segue).
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"next"]) {
NextViewController *vc = [segue destinationViewController];
// Call anything from the vc
}
}
Hope you get some clues.
I have this problem:
I have two ViewControllers.
I am transitioning to the second view with Segue.
User enters his name on first view controller and taps on a button. If the text is nil, it should not show the second view controller. If some text is there on the text field - it has to show the next view controller.
I am checking the text length here below. As I have only one segue... I am not checking segue identifier.
And, on Storyboard, I have give modal transition CoverVertical. The animation is not working. View is just appearing. UIModalTransitionStyle also I tried. Still not working (on device and simulator)
-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
if(playerNameTextField.text.length == 0)
{
UIColor *tempColor=UIColorFromRGB(0xFF4981);
[self colorizeTextViewForAWhile:playerNameTextField withUIColor:tempColor animated:YES];
return NO;
}
return YES;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"segueMoveToHome"])
{
ViewController *vc = (ViewController *)segue.destinationViewController;
vc.playerName=playerNameTextField.text;
}
}
Your code works perfectly. I just reproduced and I've got a transition with CoverVertical.
In Interface Builder, select the Storyboard Segue and be sure that you have:
I'm linking you a mini demo with your code that works.
I have removed the Identifier text and continued. As it was the only segue or any other reason... it was working. If I put the segue... it is not working. :| Not sure. But I have now my app working.
I am connecting three different views to one view and I am trying to create a back button that goes back to the view that I came from.
Example:
View A, B and C are connected to view D. I want to create a back button that goes back to say B, if I went to D from B. If I went to D from C, I want that button to go back to C and so on. How do I do this programmatically?
Here's some code:
On the sender we are using
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"SegueA"]) {
ViewController *destViewController = segue.destinationViewController;
destViewController.segueName = #"SegueA";
}
}
On the receiver side, we are using the following code :
- (IBAction)backBtn:(id)sender {
if([_segueName isEqualToString: #"SegueA"]){
[self performSegueWithIdentifier: #"SegueAA" sender:self];
}
So we are using a segue to go from A to D and then, if SegueA is identified we want to return via a segue from D to A called SegueAA.
Assuming you have UIViewController instead of UIView, you can use
[self.navigationController popViewControllerAnimated:YES]
You need to make a manual segue. You do this by making a segue from one view to another and then giving it a name in the interface builder.
Control Drag from one view to another
Under the "manual" segue type you probably want to use push
Click on the segue and go to properties and give it a name under the "Identifier" field
Then you can call it like so:
[self performSegueWithIdentifier:#"initalLegalSegue" sender:self];
(this block for example would launch my "initialLegalSegue" manually by code.)
With modal segues, I've been using this:
This goes back one view controller:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
This goes back two view controllers:
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
I have a view controller with 2 different segues that goes to 2 different view controller,and i have to implement the cancel button in both the controllers.When i press the cancel button,in both the controller,the view will return to the initial view controller.My question is how can i implement the buttons?When i try with this code the compiler warning:Multiple declaration of method "cancel:" found and ignored.Thank you.
interface:
-(IBAction)cancel:(UIStoryboardSegue *)segue;
-(IBAction)done:(UIStoryboardSegue *)segue;
-(IBAction)cancel:(UIStoryboardSegue *)segue;
implementation:
-(IBAction)done:(UIStoryboardSegue *)segue
{
if([[segue identifier] isEqualToString:#"ReturnInput"]){
AddSightingViewController *addController = [segue sourceViewController];
if (addController.birdSighting) {
[self.dataController
addBirdSightingWithSighting:addController.birdSighting];
[[self tableView]reloadData];
}
[self dismissViewControllerAnimated:YES completion:NULL];
}
}
-(IBAction)cancel:(UIStoryboardSegue *)segue
{
if([[segue identifier] isEqualToString:#"CancelInput"]){
[self dismissViewControllerAnimated:YES completion:NULL];
}
}
I'm not sure of what you're trying to do. But I think the cancel method needs to be in the 2 child View Controllers, not in the main one. One for each controller (and one cancel button for each view). That way you won't have any problems with multiple declarations of a method.
From your code I conclude that you are using (or want use) exit segues for canceling.
First, you should only have one method declaration and implementation for your cancel method in the initial view controller. In your storyboard create exit segues by control-dragging from your cancel buttons to the green exit icon blow the view controller and select the cancel method defined in the initial view controller. Do that for both view controllers. You should also give your exit segues different identifiers in your storyboard (you need to select the segue in the Document Outline to change its identifier).
Then your cancel method in your initial view controller can look something like this:
-(IBAction)cancel:(UIStoryboardSegue *)segue
{
if([[segue identifier] isEqualToString:#"CancelInput1"]) {
// Do something
} else if([[segue identifier] isEqualToString:#"CancelInput2"]) {
// Do something different
}
}
If you don't want to do anything when canceling just leave the method empty.
If you wan't to go back you need to implement an unwind segue.
To do this, define a method to go back on the original view controller (the one you want to go back). You can leave the method empty.
- (IBAction)methodName:(UIStoryboardSegue *)segue
{
}
Then on IB ctrl + drag from the button (or from the view controller) to the green "exit" icon. Select the methodName from the popup menu. If you did it from the view controller set the identifier on the segue and call it with performSegueWithIdentifier: from the button action.
Considerations:
The method name will be detected in every view controller on the storyboard.
You can define the same method name in different view controllers, but when you execute the unwind segue, you will go back to the most recent on the navigation path.
I have an iOS app that has a log in view (LognnViewController) and once a user is successfully authenticated they are taken to another view (DetailEntryViewController) to enter some simple details.
Once the details are entered the user is taken to the main part of the app that consists of a tab controller (TabViewController) that holds a variety of other views. The LogInViewController performs a modal segue to the DetailEntryViewController and the DetailEntryViewController then performs a modal segue to the TabViewController so I have kind of a modal segue chain going to get into the app. When a user logs out I want to go all the way back to the LogInViewController but when I do a:
[self.presentingViewController dismissModalViewControllerAnimated:YES];
...it pops the TabViewController and I end up back at the DetailEntryViewController instead of the first LogInViewController. Is there any way I can pop back to the first view controller easily or does doing this modal segue chain thing prevent me from that. I got the bright idea to put some code in the DetailEntryViewController viewWillAppear: that would automagically pop itself if the user had logged out but apparent making calls to dismiss a modal controller are not allowed in viewWillAppear: viewDidLoad:, etc.
Any ideas on how to make this happen?
I think this is not the best structure to implement your app. Modal controllers are supposed to be for temporary interruptions to the flow of the program, so using a modal to get to your main content is not ideal. The way I would do this is to make your tab bar controller the root view controller of the window, and then in the first tab's controller, present the login controller modally from the viewDidAppear method, so it will appear right away (you will briefly see the first tab's view unless you uncheck the "animates" box in the segue's attributes inspector). Present the details controller from that one, and then dismiss both modal controllers to get back to your main content. When the user logs out, just present that login controller again. I implement this idea like this. In the first tab's view controller:
- (void)viewDidLoad {
[super viewDidLoad];
_appStarting = YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (_appStarting) {
[self performSegueWithIdentifier:#"Login" sender:self];
_appStarting = NO;
}
}
Then in the last (second in your case) modal view controller, I have a button method:
-(IBAction)goBackToMain:(id)sender {
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
}
Figured it out myself...just had to go up one more level to get to the "root" view controller (LogInViewController) and found that this did the trick:
[[self.presentingViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
As I said I'm just getting the presentingViewController (DetailEntryViewController) and then going up one more level and getting that controller's presenter (LogInViewController).
I had similar problem and my "modal segue chain" was not limited. I agree with the arguments in the answer and comments below about modal segues designed for different thing, but I liked the "horizontal flip" animation of modal segues and I couldn't find the easier way to replicate them... Also in general I don't see anything wrong in using things that were designed for one thing to achieve some other thing, like chaining modal controllers. Repeated "partial curl" animation can also apply to some scenario in some app.
So I implemented the stack of modal controllers as a property of controller:
#interface ModalViewController : UIViewController
#property (nonatomic, retain) NSMutableArray *modalControllers;
#end
When the first modal segue is executed the stack is created in prepareForSegue method of controller that is not modal:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"modalSegue"]) {
ModalViewController *controller =
(ModalViewController *)[segue destinationViewController];
controller.modalControllers = [NSMutableArray arrayWithObject: controller];
}
}
When one modal controller moves to another the destination is added to the stack (in the method of ModalViewCotroller)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"modalSegue"]) {
ModalViewController *destController =
(ModalViewController *)[segue destinationViewController];
// add destination controller to stack
destController.modalControllers = _modalControllers;
[destController.modalControllers addObject: destController];
}
}
To dismiss the whole stack at once was the most tricky part - you can't dismiss the previous controller before the next finished dismissing, so the cycle did not work, only recursive blocks did the trick, with avoiding the memory leak being the trickiest (I'm yet to check it, but I relied on this):
- (IBAction)dismissAllModalControllers: (id)sender
{
// recursive block that dismisses one auth controller
// all these dances are to avoid leaks with ARC
typedef void (^voidBlockType)();
__block void (^dismissController) ();
voidBlockType __weak dismissCopy = ^void(void) {
dismissController();
};
dismissController = ^void(void) {
int count = [_modalControllers count];
if (count > 0) {
// get last controller
UIViewController *controller =
(UIViewController *)[_modalControllers lastObject];
// remove last controller
[_modalControllers removeLastObject];
// dismiss last controller
[controller
// the first controller in chain is dismissed with animation
dismissViewControllerAnimated: count == 1 ? YES : NO
// on completion call the block that calls this block recursively
completion: dismissCopy];
}
};
// this call dismisses all modal controllers
dismissController();
}
[self.navigationController popToRootViewControllerAnimated:YES];