iPhone, need the IF for dismissModalViewControllerAnimated ELSE removeFromSuperview? - iphone

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.

Related

viewDidAppear not getting called

In my main UIViewController I am adding a homescreen view controller as subviews:
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:vc];
controller.navigationBarHidden = YES;
controller.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
[self addChildViewController:controller];
[self.view insertSubview:controller.view atIndex:0];
[controller didMoveToParentViewController:self];
The issue is that viewDidAppear and viewWillAppear is only called once, just like viewDidLoad. Why is this? How do I make this work?
Basically inside vc I am not getting viewDidAppear nor viewWillAppear.
I also just tried adding the UIViewController without the navigation controller and it still doesn't work:
vc.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
[self addChildViewController:vc];
[self.view insertSubview:vc.view atIndex:0];
[vc didMoveToParentViewController:self];
In my case, viewDidAppear was not called, because i have made unwanted mistake in viewWillAppear method.
-(void)viewWillAppear:(BOOL)animated {
[super viewdidAppear:animated]; // this prevented my viewDidAppear method to be called
}
The only way I can reproduce the problem of child controllers not receiving appearance methods is if the container view controller does the following (which I'm sure you're not doing):
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
{
return NO;
}
Perhaps you can try explicitly enabling this:
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
{
return YES;
}
But my child view controllers definitely are getting the viewWillAppear calls (either if I explicitly automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers or if I omit this altogether.
Update:
Ok, looking at the comments under your original question, it appears that the issue is that the child controller (B) in question is, itself, a container view controller (which is perfectly acceptable) presenting another child controller (C). And this controller B's own child controller C is being removed and you're wondering why you're not getting viewWillAppear or viewDidAppear for the container controller B. Container controllers do not get these appearance methods when their children are removed (or, more accurately, since containers should remove children, not children removing themselves, when the container removes a child, it does not receive the appearance methods).
If I've misunderstood the situation, let me know.
#Rob answer in Swift 4 (which helped me on my case which I was adding a childViewController to a UITabBarController)
override var shouldAutomaticallyForwardAppearanceMethods: Bool {
return true
}
Another case where this will not be called at launch time (yet may be called on when you return to the view) will be is if you have subclassed UINavigationController and your subclass overrides
-(void)viewDidAppear:(BOOL)animated
but fails to call [super viewDidAppear:animated];
Had a same problem
My container view controller did retain a child view controller via a property, but did not add a child view controller to its childViewControllers array.
My solution was to add this line of code in the container view controller
[self addChildViewController: childViewController];
After that UIKit started forwarding appearance methods to my child view controller just as expected
I also changed the property attribute from strong to weak just for beauty
When updating my code to 13.0, I lost my viewDidAppear calls.
In Objective-c, my solution was to add the following override all to the parent master view controller.
This allowed the ViewDidAppear call to get called once again...as it did in previous IOS (12 and earlier) version.
#implementation MasterViewController
//....some methods
(BOOL) shouldAutomaticallyForwardAppearanceMethods {
return YES;
}
// ...some methods
#end
My problem was that I was changing the tab in UITabBarController (selectedIndex = x) and then messing with the child view controllers in that tab. The problem is that it needs to be done the other way round: first mess with the child view controllers in other tab and then change the tab by setting the selectedIndex. After this change methods viewWillAppear/viewDidAppear begun to be called correctly.
Presenting view controllers using presentModalViewController or segues or pushViewController should fix it.
Alternatively, if for some reason you want to present your views without the built-in methods, in your own code you should be calling these methods manually. Something like this:
[self addChildViewController:controller];
BOOL animated = NO;
[controller viewWillAppear:animated];
[self.view insertSubview:controller.view atIndex:0];
[controller viewDidAppear:animated];
[controller didMoveToParentViewController:self];

Dismiss two modal (table)view controllers

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];

Memory management, addSubview for a subclass of UIViewController

I have a view that shows a map. I have a custom subclass of UIViewController (DetailViewController) that gets shown when the detailDisclosureButton of the callout above the pin is pressed. While in my map class, I create my detailview and add it to the subview like this:
DetailViewController *detailView = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
detailView.locationPoint = locationPoint;
detailView.locationCoordinate = locationCoordinate;
[self.view addSubview:detailView.view];
[detailView release];
My DetailViewController has a TableView and parses the data in DetailViewController. However I get an error of sending the numberOfSectionsInTable message to a dealloc'd instance. I'm assuming it is this since I originally had this as a property and it worked fine with (nonatomic, retain). I'm assuming that I'm releasing it before the next view is done with it. If that is the case, when would I clean up the memory??? It seems like this would be the place to do it. Thanks.
I am not sure what makes you adding the view of DetailViewController into this mapviewcontroller's view. Don't you think right approach would be to either presentModalViewController or pushNavigationController?
DetailViewController *detailView = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
detailView.locationPoint = locationPoint;
detailView.locationCoordinate = locationCoordinate;
//[self.view addSubview:detailView.view];
[self.navigationController pushViewController:detailView animated:YES];
//OR
[self presentModalViewController:detailView animated:YES];
[detailView release];
You are getting the error because you are only using the view and deallocating the view controller immediately and hence tableview datasource and delegates are hitting a nil object.
Views do not retain their view controllers. Someone needs to retain the VC or else it will get released, and then the app will crash when the view makes a call into its delegate. When you use a navigation controller, the navcon has a stack of view controllers that it retains. Likewise with presentModalViewController, the OS takes care of retaining the detail VC.
Adding a detail view as a subview is not the normal way to navigate to a new view. Instead, one either uses a navigation controller and [navcon pushViewController::], or a modal subview and [self presentModalViewController::]. If the detail view occupies only a portion of the parent view, then it is normal to retain the view controller for the subview within the parent controller. That is, within the parent VC (your map class) add a property for the detail VC. Actually, it's more common to not even use a VC for a subview, but rather for screen-filling detail views.

what's called after returning from presentModalViewController / dismissModalViewControllerAnimated:

to show a modal uiview out of my mainView I use:
[self presentModalViewController:myController animated:YES];
and in MyController I close that view with:
[self dismissModalViewControllerAnimated:YES];
But how can I know in the mainView that the modal was finished (to redraw my table)?
Currently I set a local variable to YES in my mainView after starting the modal view an react on viewWillAppear:
[self presentModalViewController:myController animated:YES];
_reloadTableData = YES;
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_reloadTableData) {
_reloadTableData = NO;
[_tableView reloadData];
}
}
Is there a better way to do so ?
Generally speaking, it's not appropriate to dismiss the modal view by the modal view itself.
Instead, you should set your main view as the delegate of the modal view. When you modal view finishes its task, it can let its delegate know and let its delegate dismiss it. This is the very common so-called delegate design pattern in Objective-C.
btw, you may want to consult with some code samples to gain a better understanding of this delegate pattern. I suggest you take a look at one of Xcode's default templates - the Utility Application template. It has a very succinct and simple and straightforward delegate structure built inside.

How do be notified when a ViewController is removed from a NavigationController

Is there a way to be notified when a ViewController is removed from a UINavigationController because the back button was pressed?
You can use viewWillDisappear: in the view controller that is disappearing. If the other view controller needs to be notified, you can use a delegate method to notify it:
//in the disappearing view controller, class MYViewController
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
//do stuff you need to do
if ([self.delegate respondsToSelector:#selector(myViewControllerDidDisappear:)])
[self.delegate myViewControllerDidDisappear:self]; //bottom view controller is delegate
}
In conjunction with eman's method, check
[navController.viewcontrollers count]
If it is one greater than before (you need to maintain a count) then something was pushed. If it is one less, and viewWillDisappear: was called, then the view controller was removed.