I'm stuck with an issue.... Please help!
I have a navigation controller project. From the root controller I am pushing a view onto the stack that has some info and 2 buttons (YES, NO). (it can't be an AlertView). If they press the YES button, I call a method on the parent view (using a delegate method) and pop the view from the stack. The method on the parent pushes a new view onto the stack. I assumed the parent method would remain in memory after the deallocation of the child view because the method exists on the parent, but when the child is deallocated it also deallocates everything created inside the method on the parent. Does anyone have a solution for responding to events that happen on a child view in a parent view that will remain after the child is deallocated?
Thanks!
in header file:
#import <UIKit/UIKit.h>
#protocol decrementDelegate;
#interface decrement : UIViewController {
int currentCount;
IBOutlet UILabel *countLabel;
id <decrementDelegate> delegate;
}
#property (nonatomic, assign) id <decrementDelegate> delegate;
#end
#protocol decrementDelegate <NSObject>
-(void)decrementControllerDidFinish:(decrement *)controller
withString:(NSString *)stringValue;
#end
in implementation file:
[self.delegate decrementControllerDidFinish:self
withString:countLabel.text];
[self.navigationController popViewControllerAnimated:YES];
caller:
-(void)decrementControllerDidFinish:(decrement *)controller
withString:(NSString *)stringValue {
// Show our details
myNewChildController *viewController = [[myNewChildController alloc]
initWithNibName:#"myNewChildController" bundle:nil];
viewController.delegate = self;
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
}
Using non-delegate and just calling a method on the parent:
caller:
mySummary *parent = [self.navigationController.viewControllers objectAtIndex:0];
[mySummary setMyAllowOnParent:#"Allowed"];
[self.navigationController popViewControllerAnimated:YES];
parent:
-(void)setMyAllowOnParent:(NSString *)aAllow {
newView *viewController = [[newView alloc] initWithNibName:#"newView" bundle:nil];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
}
newView crashes with deallocated error when caller view is deallocated. Does anyone have an idea how to resolve this issue?
How did you create your delegate? You didn't accidentally used retain instead of assign (assuming you use a property to access it)? Because if you used retain, this might have caused the issue.
By the way, you mention something about a UIAlertView that cannot be used, which leads me to think that you created a view with a similar design as a UIAlertView. Perhaps the following link might be useful:
how can i make popup login window similar as on istore in objective-C/cocoa-touch
Related
I have a UIViewController called ShowListViewController that uses a Modal View Controller to push another view onto the stack:
AddShowViewController *addShowViewController = [[AddShowViewController alloc] init];
[addShowViewController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self presentModalViewController:addShowViewController animated:YES];
I would then like to call my method populateTableData of the ShowListViewController class when the addShowViewController disappears.
I would think that the answer found here would work, but it doesn't. My method populateTableData is not detected as an optional method to use.
Essentially my questions is: How do I detect when a Modal View Controller disappears so as to call a method within the class that pushed it on the stack?
This may not be a best solution, but can do what you want at this time.
In your showlistcontroller add an instance variable like
BOOL pushedView;
#implementation ShowListViewController
and before you do the modal presentation set its values as YES like
pushedView = YES;
[self.navigationController presentModalViewController:popView animated:YES];
in the viewWillAppear of ShowListViewController you can detect whether it is appearing because pop getting dismissed or not like
if (pushedView) {
NSLog(#"Do things you would like to on pop dismissal");
pushedView = NO;
}
I think you would like something like this.
You make a delegate inside ur modalVC like this:
#protocol ModalViewDelegate <NSObject>
- (void)didDismissModalView;
#end
and implement it in your MainVC like this:
#interface MainViewController : UIViewController <ModalViewDelegate>
{
Then u will make a delegate property in your modalVC like this:
#interface ModalShizzle : UIViewController
{
id<ModalViewDelegate> dismissDelegate;
}
You set the dismissDelegate of your ModalVC to your MainVC and then you make the delegate method. Before you dismiss it however you will call the ModalVC to do one last thing. (which is populate your table). You will call for the data inside your MainVC and then do whatever you feel like it, just before you dismissed your modalVC.
-(void)didDismissModalView
{
//call ModalVC data here...
//then do something with that data. set it to a property inside this MainVC or call a method with it.
//method/data from modalVC is called here and now u can safely dismiss modalVC
[self dismissModalViewControllerAnimated:YES];
}
Hope it helps ;)
OK so it appears that in Apple's template for Utility App's they ignore what the docs for [UIViewController][1] say and actually go out of their way to call dismissModalViewControllerAnimated: from the UIViewController that pushed the modal view onto screen.
The basic idea in your case will be
Define a protocol for AddShowViewControllerDelegate
Make ShowListViewController implement this protocol
Call a method on the delegate to ask it to dimiss the modal view controller
For a full example just create a new project with Utility template and look at the source for FlipsideViewController and MainViewController
Here is an example adapted for your needs:
AddShowViewController.h
#class AddShowViewController;
#protocol AddShowViewControllerDelegate
- (void)addShowViewControllerDidFinish:(AddShowViewController *)controller;
#end
#interface AddShowViewController : UIViewController
#property (nonatomic, assign) id <AddShowViewControllerDelegate> delegate;
- (IBAction)done:(id)sender;
#end
AddShowViewController.m
- (IBAction)done:(id)sender
{
[self.delegate addShowViewControllerDidFinish:self];
}
ShowListViewController.h
#interface ShowListViewController : UIViewController <AddShowViewControllerDelegate>
{
...
}
ShowListViewController.m
- (void)addShowViewControllerDidFinish:(AddShowViewController *)controller
{
[self dismissModalViewControllerAnimated:YES];
[self populateTableData];
}
so in my app delegate I am trying to present a modalViewController from a UITabBarController, by doing the following:
self.tabBarController = [[UITabBarController alloc] init];
LoginViewController* loginViewController = [[LoginViewController alloc] init];
loginViewController.delegate = self;
[self.tabBarController presentModalViewController:loginViewController animated:NO];
[loginViewController release];
and the delegate defined in the app delegate is:
- (void)userDidLogin:(LoginViewController *) loginViewController
{
NSLog(#"DELEGATE CALLED, DISMISSING");
[self.tabBarController dismissModalViewControllerAnimated:NO];
}
Here's my LoginViewController:
protocol LoginViewControllerDelegate;
#interface LoginViewController : UIViewController <MBProgressHUDDelegate>
{
id<LoginViewControllerDelegate> delegate;
}
#property (assign) id<LoginViewControllerDelegate> delegate;
#end
#protocol LoginViewControllerDelegate
- (void)userDidLogin:(LoginViewController *) loginViewController;
#end
The issue is that this (userDidLogin:(LoginViewController *) loginViewController) is never called... why is this?
I have called the following in my LoginViewController implementation and this is called
[self.delegate userDidLogin:self];
UPDATE:
I got the delegate called now. The issue now is that when I call [self.tabBarController dismissModalViewControllerAnimated:YES] it doesn't dismiss the modal view controller.
You didn't post any code from LoginViewController, but within that class's code you need to add the following lines when you are ready to dismiss it (perhaps when the user clicks the "Login" button and the login is successful).
if (delegate && [delegate respondsToSelector:#selector(userDidLogin:)])
[delegate performSelector:#selector(userDidLogin:) withObject:self];
UPDATE:
I think I understand what the issue is here. According to Apple's documentation, when you call presentModalViewController:animated: the method sets the value of the "modalViewController" property of UIViewController (in this case your UITabBar). However that property only maintains a weak reference to the modalViewController. That's important because you initialize the LoginViewController, pass it in to presentModalViewController:animated: and then you release it. Since presentModalViewController:animated: is not retaining a strong reference to the LoginViewController, the UITTabBar is unable to dismiss it later on. In fact I'm surprised what you have done is not resulting in an EXC_BAD_ACCESS crash. I suggest you remove the "[loginViewController release]" statement and instead release it after you call "[self.tabBarController dismissModalViewControllerAnimated:NO]"
I have a small doubt. I have a NSObject class where I am trying to display an alert view. So after the alert view is displayed when I tap on OK button I want to push a navigation controller onto the stack. Can I push a navigation controller from general NSObject class? Please let me know guys..thanks for your time..
This is the code..
- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex{
SettingsViewController *homeView = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[self.navigationController pushViewController:homeView animated:NO];
[homeView release];
}
I am creating a property called navigationController of type UINavigationController and when I catch the error I am displaying an alert view and I am using above method to push the view controller but it doesn't work..
Yes and no... depending on how you have your application set up. To push views onto the navigation stack you need to have a navigation controller.
Does your NSObject have access to this navigation controller - you might have to set up a delegate method that gets called from your delegate view when the alert view delegate gets called in your NSObject.
I'm just wondering why you're displaying a UIAlertView in an NSObject, why aren't you displaying it in a UIView or a UIViewController?
CustomObject.h
#protocol CustomObjectDelegate<NSObject>
#optional
- (void)customObjectAlertViewDidClickOk;
#end
#interface CustomObject : NSObject <UIAlertViewDelegate>{
id<CustomObjectDelegate> delegate;
}
#property (nonatomic, assign) id<CustomObjectDelegate> delegate;
#end;
CustomObject.m
#synthesize delegate;
// then put this:
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
[delegate customObjectAlertViewDidClickOk];
}
Then your ViewController .h file needs to include the custom object and assign the delegate methods:
#include "CustomObject.h"
#interface MyViewController : UIViewController <CustomObjectDelegate> {
}
#end
and the .m viewDidLoad (or similar):
- (void)viewDidLoad{
CustomObject *obj = [[CustomObject alloc] init];
[obj setDelegate:self];
}
- (void)customObjectAlertViewDidClickOk{
AnotherViewController *page = [[AnotherViewController alloc] initWithNibName:nil bundles:nil];
[self.navigationController pushViewController:page];
}
Thats how I would do it - given I'm not too sure i understand quite what you're asking. :) thats all off the top of my head as well - so don't take it letter for letter, but you have the basis there to start off with. You can build on it. Look up #protocols and delegate methods, its all in there. :)
I have a mainViewController. I call [self pushModalViewController:someViewController] which makes someViewController the active view.
Now I want to call a function in mainViewController as someViewController disappears with [self dismissModalViewController].
viewDidAppear does not get called probably because the view was already there, just beneath the modal view. How does one go about calling a function in the mainViewController once the modalView dismisses itself?
Thanks a lot!
This answer was rewritten/expanded to explain the 3 most important approaches (#galambalazs)
1. Blocks
The simplest approach is using a callback block. This is good if you only have one listener (the parent view controller) interested in the dismissal. You may even pass some data with the event.
In MainViewController.m
SecondViewController* svc = [[SecondViewController alloc] init];
svc.didDismiss = ^(NSString *data) {
// this method gets called in MainVC when your SecondVC is dismissed
NSLog(#"Dismissed SecondViewController");
};
[self presentViewController:svc animated:YES completion:nil];
In SecondViewController.h
#interface MainViewController : UIViewController
#property (nonatomic, copy) void (^didDismiss)(NSString *data);
// ... other properties
#end
In SecondViewController.m
- (IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
if (self.didDismiss)
self.didDismiss(#"some extra data");
}
2. Delegation
Delegation is the recommended pattern by Apple:
Dismissing a Presented View Controller
If the presented view controller must return data to the presenting view controller, use the delegation design pattern to facilitate the transfer. Delegation makes it easier to reuse view controllers in different parts of your app. With delegation, the presented view controller stores a reference to a delegate object that implements methods from a formal protocol. As it gathers results, the presented view controller calls those methods on its delegate. In a typical implementation, the presenting view controller makes itself the delegate of its presented view controller.
MainViewController
In MainViewController.h
#interface MainViewController : UIViewController <SecondViewControllerDelegate>
- (void)didDismissViewController:(UIViewController*)vc;
// ... properties
#end
Somewhere in MainViewController.m (presenting)
SecondViewController* svc = [[SecondViewController alloc] init];
svc.delegate = self;
[self presentViewController:svc animated:YES completion:nil];
Somewhere else in MainViewController.m (being told about the dismissal)
- (void)didDismissViewController:(UIViewController*)vc
{
// this method gets called in MainVC when your SecondVC is dismissed
NSLog(#"Dismissed SecondViewController");
}
SecondViewController
In SecondViewController.h
#protocol SecondViewControllerDelegate <NSObject>
- (void)didDismissViewController:(UIViewController*)vc;
#end
#interface SecondViewController : UIViewController
#property (nonatomic, weak) id<SecondViewControllerDelegate> delegate;
// ... other properties
#end
Somewhere in SecondViewController.m
[self.delegate myActionFromViewController:self];
[self dismissViewControllerAnimated:YES completion:nil];
(note: the protocol with didDismissViewController: method could be reused throughout your app)
3. Notifications
Another solution is sending an NSNotification. This is a valid approach as well, it might be easier than delegation in case you only want to notify about the dismissal without passing much data. But it's main use case is when you want multiple listeners for the dismissal event (other than just the parent view controller).
But make sure to always remove yourself from NSNotificationCentre after you are done! Otherwise you risk of crashing by being called for notifications even after you are deallocated. [editor's note]
In MainViewController.m
- (IBAction)showSecondViewController:(id)sender
{
SecondViewController *secondVC = [[SecondViewController alloc] init];
[self presentViewController:secondVC animated:YES completion:nil];
// Set self to listen for the message "SecondViewControllerDismissed"
// and run a method when this message is detected
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(didDismissSecondViewController)
name:#"SecondViewControllerDismissed"
object:nil];
}
- (void)dealloc
{
// simply unsubscribe from *all* notifications upon being deallocated
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didDismissSecondViewController
{
// this method gets called in MainVC when your SecondVC is dismissed
NSLog(#"Dismissed SecondViewController");
}
In SecondViewController.m
- (IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
// This sends a message through the NSNotificationCenter
// to any listeners for "SecondViewControllerDismissed"
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SecondViewControllerDismissed"
object:nil userInfo:nil];
}
Hope this helps!
Using exit (unwind) segue
When you're using storyboards and segues you can use a very handy approach with minimal code to dismiss a modal view controller and inform the underlying view controller that the modal view controller has been closed.
Using exit (unwind) segues you gain 3 advantages:
you don't need to write any code to dismiss the modal view controller and
you can have iOS call a callback method inside the underlying view controller that has presented the model view controller.
you use the very same semantics that you already know from implementing prepareForSegue:
Implement it with only 2 steps
Create an action method in the Parent view controller that presents another (modal) view controller:
Swift
#IBAction func unwindFromSegue(segue: UIStoryboardSegue) {
print("Unwind from segue", segue.identifier)
}
Objective-C
- (IBAction)unwindFromSegue:(UIStoryboardSegue *)segue {
NSLog(#"Unwind from segue %s", segue.identifier);
}
In the storyboard, On child view controller right click the exit segue (aka unwind segue, it's the last of the icons at the top of your view controller), drag & drop unwindFromSegue: to your button and select action.
You're done! Now the modal view controller closes when you click the dismiss button and unwindFromSegue: informs your underlying view controller(Parent) that the modal view controller(Child) has closed.
Here's a callback solution which takes less modifications to your modal and parent:
In the Model's .h add:
#property (nonatomic, copy) void (^dismissed)();
In the Model's .m put this in the completion when you dismiss the modal:
[self dismissViewControllerAnimated:YES completion:^{
if(self.dismissed)
self.dismissed();
}];
In the parent view controller when you instantiate your modal set the dismissed callback:
Modal = //Init your modal
[Modal setDismissed:^{
//do stuff you wanted when it's dimissed
}];
[self presentViewController:Modal animated:YES completion:nil];
I create a UIViewController with a button on it. The button's only job is to create a window for drawing some rectangles. (I've taken some code from "QuartzDemo" from Apple's example applications.) Objective C is really giving me fits...
When I try this, nothing happens.
Here's the code for the button:
- (IBAction)startButtonAct:(id)sender
{
QuartzViewController *controller;
NSLog(#"1");
controller = [[QuartzViewController alloc] initWithTitle:#"Dotsie"];
controller.quartzViewDelegate = [[[RectDrawing alloc] init] autorelease];
[[self navigationController] pushViewController:controller animated:YES];
// [controller release];
}
I'm honestly not sure what else anyone needs to see, but here's some of the other stuff:
#interface QuartzViewController : UIViewController
{
QuartzView *quartzView;
UIBarStyle barStyle;
UIStatusBarStyle statusStyle;
}
#protocol QuartzViewDelegate;
#interface QuartzView : UIView {
id<QuartzViewDelegate> delegate;
}
#property(assign) id<QuartzViewDelegate> delegate;
#end
#protocol QuartzViewDelegate<NSObject>
#required
// Draw contents into the given view, using the given context and bounds.
-(void)drawView:(QuartzView*)view inContext:(CGContextRef)context bounds:(CGRect)bounds;
#end
Please help -- this is driving me crazy -- I've been fighting with the same basic problem now for a week...
First, the [controller release] line should be there, uncommented. I'm sure you just disabled it for debugging.
If you insert the line:
NSLog(#"%#", [self navigationController]);
In your button action, does it log a valid controller or does it say nil? If it's nil, my guess is that this view controller wasn't properly pushed onto a navigation controller itself.