I have a main view.
From that main view I show a modal view (MV1).
MV1 may show another modal View (MV2).
From MV2, I may show another modal view (MV3).
All that MV are shown animated.
What I want, is to be able to first display (animated) the next modal view (MVx+1) before "killing" the previous one (MVx).
If I dismiss (animated) MVx before showing MVx+1 : MVx+1 does not appear.
If I dismiss (non-animated) MVx before showing MVx+1 : MVx-1 is seen.
If I show MVx+1 before dismissing (non-animated) MVx : MVx+1 does not appear.
How may I do ?
Some code sample would help if you have time, but just a detailed explanation would be enough.
According to the Apple docs, the accepted way to dismiss modal views is by letting the parent controller (i.e., the view controller that created the modal view) do the dismissing. The best way to do this is by setting the parent controller as the delegate of the modal view controller. The idea here is that the modal controller tells its parent that it's ready to be dismissed, and the parent decides what course of action to take from there.
In order to do this, you have to create a delegate protocol for the modal view controller that the parent controller implements. In your case, you can create a protocol at the top of each of your .h files for your modal views to do this (or a single protocol in a separate file if all of the modal views can use the same method for dismissal). For example:
#protocol MYModalViewDelegate <NSObject>
-(void)dismiss;
#end
Next, in each of your modal view controllers, create an instance variable for the delegate:
#interface MYModalViewController1 : UIViewController {
id<MYModalViewDelegate> delegate;
}
When you display a modal view from a current view controller, set the current controller as the delegate.
MYModalViewController1 * mvc1 = [[MYModalViewController1 alloc] initWithNibName:#"MYModalViewController1" bundle:nil];
mvc1.delegate = self;
[self presentModalViewController:mvc1 animated:YES];
[mvc1 release];
When you want to release the current modal controller, have the modal view controller call the appropriate protocol method on its delegate:
[self.delegate dismiss];
Now, the delegate can handle where to go next. In your case, you can close MV2 automatically when MV3 closes by calling [self.delegate dismiss] in MV3, then implement dismiss in MV2 as:
-(void)dismiss {
[self dismissModalViewControllerAnimated:YES];
[self.delegate dismiss];
}
Related
I am an iOS rookie. I have a Table View embedded in a Navigation Controller. Another Table View is also embedded in a Navigation Controller. The first Table View, ChecklistsView, presents the second Table View, ItemDetailView. The ChecklistView's Navigation Controller '+' button opens the ItemDetailView scene. Tapping on the Cancel button of the ItemDetailView closes the ItemDetailView, but gives the message noted above. Here is the code for the Cancel method:
- (IBAction)cancel
{
[self.delegate itemDetailViewControllerDidCancel:self];
}
And here is the delegate method used in the cancel method:
- (void)itemDetailViewControllerDidCancel:(ItemDetailViewController *)controller
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Any suggestions would be appreciated. I have reviewed other posts about this warning, but they are above my level of expertise.
You shouldn't have the delegate dismiss the viewController.
Rather you should dismiss it by using the .presentingViewController property on the presentedViewController.
Like so:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
I disagree with AdamG. What you're doing is correct; Apple also uses this design, where the delegate dismisses the controller (check out MFMessageComposeViewController).
Back to the question, if I understand you correctly, and that you have the following structure (pardon the terms, but you'll get the gist):
A UINavigationController at the top
ChecklistsViewController is the navigation controller's root view controller
ItemDetailViewController as a modal view controller
ChecklistsViewController is the delegate for ItemDetailViewController
It's just the implementation details in your case.
- (void) itemDetailViewControllerDidCancel: (ItemDetailViewController*) controller
{
[controller dismissViewControllerAnimated: YES
completion: nil];
}
I know there's like 3-5 similar questions here, but non of the answers solves my problem.
I have a ViewController that opens a modal (table)view controller, which opens another one. Both modal view controllers are in fact table view controllers. I'm trying to dismiss both of them from the second one. I tried every accepted answer on similar question, none of them worked for me.
I tried
[self dismissModalViewControllerAnimated:true]
[self.parentViewController dismissModalViewControllerAnimated:true]
[self.parentViewController.parentViewController dismissModalViewControllerAnimated:true]
[self.presentingViewController dismissModalViewControllerAnimated:true]
[self.presentingViewController.presentingViewController dismissModalViewControllerAnimated:true]
When I try options 2, 3 and 5, nothing happens at all. When I use options 1, and 4, I see dismiss modal view animation and the underlying view itself for a moment, and then everything goes back to the second modal view (this time without animation).
I'm starting to think that this have something with the fact that I use tableViewControllers for modal views.
Btw, I'm dismissing modal views in didSelectRowAtIndexPath.
Try this:-
When you dismiss your SecondView set a BOOL flag variable in app delegate file and check that variable in your FirstView's viewWillAppear method whether SecondView was open and close or not. If so, then [self dismissModalViewControllerAnimated:true]
typical model view controller behavior would suggest that you dismiss the modal view controller from the calling view controller rather than from self. not a hard and fast rule, but good practice.
to accomplish this, create a protocol:
#protocol MyModalViewControllerDelegate
- (void)modalViewControllerDidFinish;
#end
and make both the parentViewController and FirstModalViewController be implemntors of this protocol.
#interface FirstModalViewController <MyModalViewControllerDelegate>
then in both FirstModalViewController.h and SecondModalViewController.h, add:
#property id<MyModalViewControllerDelegate> modalViewControllerDelegate
in both parentViewController and FirstModalViewController, right before calling presentModalViewController:... , set the following:
modalViewControllerAboutToAppear.modalViewControllerDelegate = self;
[self presentModalViewController:modalViewControllerAboutToAppear animated:YES];
next, in the SecondModalViewController, in the code where you determine that the item needs to be dismissed, call
[self.modalViewControllerDelegate modalViewControllerDidFinish];
now, in FirstModalViewController, implement the following:
- (void)modalViewControllerDidFinish:(MyModalViewController*)controller {
[self dismissModalViewControllerAnimated:YES]
[self.modalViewControllerDelegate modalViewControllerDidFinish];
}
and finally, in the parent view controller, you should be able to perform:
- (void)modalViewControllerDidFinish:(MyModalViewController*)controller {
[self dismissModalViewControllerAnimated:YES]
}
Since I don't use delegate files, I did the following:
To FirstView add field
BOOL mClose;
To FirstView add method
- (void)close
{
mClose = YES;
}
To FirstView method viewDidAppear add
if (mClose)
{
[self dismissModalViewControllerAnimated:YES];
}
To FirstView method which opens SecondView add
[secondView closeWhenDone:self];
To SecondView add field
FirstView *mParent;
To SecondView add method
- (void)closeWhenDone:(FirstView*)parent
{
mParent = parent;
}
To SecondView method which closes it add
[mParent close];
viewWillAppear is called both when going to the view and when coming back to the view from other views.
I want to select(highlight) and fade-out a cell only when coming back from other views.
Is there a delegate method to do this?
I'm using UINavigationViewController.
If you're on iOS 5, you can use these new properties:
These four methods can be used in a view controller's appearance
callbacks to determine if it is being presented, dismissed, or added
or removed as a child view controller. For example, a view controller
can check if it is disappearing because it was dismissed or popped
by asking itself in its viewWillDisappear: method by checking the
expression ([self isDismissing] || [self
isMovingFromParentViewController]).
- (BOOL)isBeingPresented __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
- (BOOL)isBeingDismissed __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
- (BOOL)isMovingToParentViewController __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
- (BOOL)isMovingFromParentViewController __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
In your code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!(self.isMovingToParentViewController || self.isBeingPresented))
{
// animate
}
}
EDIT:
If you're using a UITableViewController, setting the property -clearsSelectionOnViewWillAppear to YES will do this for you. You only have to do it manually if you're using a regular UIViewController with a UITableView subview.
If you are targeting iOS 5, you can use [self isBeingPresented] and [self isBeingDismissed] to determine if the view controller is being added or removed from the nav controller.
I'm also suspecting that you could improve the logic of when you select/deselect the cell in your table view such that it doesn't matter whether the view controller is coming or going.
The usual way to do it is this: when someone selects a row in the table view in view controller A, it gets selected/highlighted and you push a new view controller B. When view controller B is dismissed, you animate the deselection of the table view row in viewDidAppear (so the user can see it fading out) in view controller A. You wouldn't worry about whether view controller A has just appeared or is re-appearing, because there would only be a selected table view cell in the appropriate case.
viewWillAppear is getting called when the view appears
after the viewDidLoad
after you dismiss or pull a view controller
You could change the viewWillAppear to the following
- (void) viewWillAppear:(BOOL)animated
{
static BOOL firstTime = YES;
if (!firstTime)
{
//Do your alpha animation
}
firstTime = NO;
}
In your UINav Controller you could create a "lastView" property and have each of your view controllers (that are controlled by your UINav Controller) set this property on "viewWillAppear"... in your target view... the one you want to do the highlighting and fading you could check this property of the UINav Controller and see if it's NIL or not.
That's just one way to do it. This wouldn't work if you pop up a modal or the like.
I want to create a modal view with the navigation item (right view on my screenshot) and I want it to have a 'back button'. My app is TabBar application and I don't want this view to have a tab bar, but I want to load a previous view (left view on my screenshot) with a segue similar to the type "push".
I can only create push segue to provide right navigation back to the view on the left, if it's loaded as a modal view, the NavigationBar & TabBar are gone. Any workarounds for this?
Thanks in advance!
Just put a Navbar on the new view with a bar button item. Create an action for the bar button item by control dragging from the bar button item to the .h of the view controller. You can then use a delegate and protocol method to tell the first controller when the button has been pressed and have it use [self dismissModalViewControllerAnimated:YES];
So in your second view create a protocol with a method done, like this:
#protocol SecondViewControllerDelegate <NSObject>
-(void) done;
#end
#interface SecondViewController : UIViewController {
...
id delegate;
}
...
#property (nonatomic, assign) id<SecondViewControllerDelegate> delegate;
-(IBAction)done:(id)sender; //this will be linked to your nav bar button.
#end
then in your action from your button call this:
-(IBAction)done:(id)sender{
[self.delegate done];
}
Your first view controller will need to implement the protocol <SecondViewControllerDelegate>
then in your first view controller, set it up as a delegate for your second view controller before you segue.
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([[segue identifier] isEqualToString:#"Second View Modal Segue Identifier"])
{
SecondViewController *viewController = segue.destinationViewController;
viewController.delegate = self;
}
}
lastly, catch the done call from the delegate in your first view controller:
-(void) done
{
[self dismissModalViewControllerAnimated:YES];
}
That's how I have done it. If you don't have a lot of experience with protocols and delegates it may seem confusing at first, but it has worked well for me.
You will need to wrap your right hand side view controller in a new navigation controller. In IB, select it and choose the menu item Editor -> Embed In -> Navigation Controller and IB will show a nav bar which you can customize to your heart's content.
I need to add this to my dismiss button :-
[self dismissModalViewControllerAnimated:YES];
[self release];
else
[self.view removeFromSuperview];
I thought
if( self.navigationController.modalViewController ) {
would work be it nevers true
A couple of things:
1) You shouldn't ever release yourself in an object. If you're presenting a modal view controller, you should perform the release there since the view controller will now be retained by the view controller's .modalViewController property:
(In the parent):
UIViewController *someViewController = [[UIViewController alloc] init];
[self presentModalViewController:someViewController animated:YES];
[someViewController release];
2) The parent will store its child modal view controller in .modalViewController. The child will have its .parentViewController property set in this case. If the view has been added as a subview, its .superview property will be set. These are not mutually exclusive, however, so be careful. Generally speaking, UIViewControllers are intended to host full-screen views, and if you're adding the view as a subview, you should ask yourself if the view should just be a UIView subclass, and move the logic into the parent view controller.
That said, I suppose you could check your case (assuming you don't present modal view controller and add as a subview simultaneously):
if (self.parentViewController) {
[self dismissModalViewControllerAnimated:YES];
} else if (self.view.superview) {
[self.view removeFromSuperview]
}
In the latter superview case, the view controller will still be hanging around, so you'd need to let the other view controller know via delegate method or something to release you. In the first case, if you have released the presented view controller already as I described above, it will be released automatically when the parent view controller sets its .modalViewController property to nil.
Normally for a "dismiss" button I would call a method in the controller that presented the modal controller (use a delegate), not try to dismiss the modal view controller from within itself. I don't quite get what youre trying to do though, but that [self release] looks bad. I don't think you ever want to release self like that.
Try this in you modal viewcontroller:
- (IBAction)close:(id)sender {
[self.parentViewController dismissModalViewControllerAnimated:YES];
}
Then just connect the button's action to that method.