I have two view controllers. My first is my menu which contains my highscore and a button which performs a modal segue to my second view controller which is my game. Whenever my player loses the game if he beat his highscore I want it to update on the menu.
Right now, when my player loses the game, I create a UIAlertView with 2 buttons, the first is main menu and the second is restart. Here is my simplified code with my attempting to update my high score via delegation.
#protocol highScoreProtocol <NSObject>
-(void)updateHighScore:(int) score;
#end
#interface ViewController : UIViewController <UIAlertViewDelegate> //i have this delegate implemented because i have a uiialertview
#property (nonatomic) int score;
#property (nonatomic, weak) id <highScoreProtocol> delegateHighScore;
#implementation ViewController
#synthesize score=_score;
#synthesize delegateHighScore=_delegateHighScore;
-(void)lostGame{
[self.delegateHighScore updateHighScore:self.score]; //this is where i try to call the method that should update my high score if necessary but this doesn't actually work
UIAlertView *losingScreen=[[UIAlertView alloc]initWithTitle:#"Game Over" message:[NSString stringWithFormat:#"Your Score Is %d", self.score] delegate:self cancelButtonTitle:#"Main Menu" otherButtonTitles:#"Restart", nil]; //once the user loses the game i have an alert view show giving the option to either restart the game or go to the main menu where the high score is
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex==0) {
//here i'm segueing back to my main menu because he would have pressed the 'main menu' button [self performSegueWithIdentifier:#"MainMenu" sender:self];
} else if (buttonIndex==1){
//here i just reset my attributes and reset my level because he would have pressed the 'restart button'
}
}
#end
#interface MenuVC : UIViewController <highScoreProtocol>
#property (weak, nonatomic) IBOutlet UILabel *labelHighScore; //the labelhighscore is the highscore number
#end
#implementation MenuVC
- (void)viewDidLoad
{
[super viewDidLoad];
ViewController *vc=[[ViewController alloc]init];
vc.delegateHighScore=self;//here is set the delegate as myself which i think i'm supposed to do for some reason
}
-(void)updateHighScore:(int)score{
if (score>[self.labelHighScore.text integerValue]) {
self.labelHighScore.text=[NSString stringWithFormat:#"%d", score];
}
NSLog(#"does this method even run");
// this is the method that updates the highscore which I want to run
// but it doesn't, notice I even made an 'nslog' to see if the method
// even runs but I never ever even got a log out in the debugger,
// so this method never runs.
}
If I just need a little help, or if I'm doing everything completely wrong and going about this task the wrong way, please say.
This doesn't work because this:
ViewController *vc=[[ViewController alloc]init];
vc.delegateHighScore=self;
Instantiates a NEW viewcontroller, that has completely nothing to do with the one you are interacting with.
I assume you are using storyboards so, create an identifier for your viewcontroller (on the interface builder -> select your viewcontroller -> identity inspector tab -> write a name where it says Storyboard ID)
And then add this instead of the previous code:
ViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
vc.delegateHighScore = self;
Edit:
Add this to your button action (but delete the segue from the interface builder AND delete this code from the viewDidLoad)
ViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
vc.delegateHighScore = self;
[self presentModalViewController:vc animated:YES];
Since you create a local variable, vc, in your viewDidLoad method, this is not the same one that you create in the button method where you create your modal segue. That's not the right place to set the delegate. Set yourself to the delegate in that button method using whatever reference you create (or have) to the instance of ViewController that you're segueing to. If you need more information or a code sample, post that button method, so I can see how you are segueing.
After Edit: Then you should implement prepareForSegue:sender: and do this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
[(ViewController *)[segue destinationViewController] setDelegate:self];
}
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];
}
everywhere on the internet I find examples of how to add rows into a table view by having a special row "Add Row" with a green plus. But I don't want that.
I want to have a plus button in the titlebar of MyTableViewController, and invoke some Modal Add View Controller with a XIB file with just a single text field to fill it in. In this Modal Add View Controller I want to fill in this text field, and after I press Done, Modal Add View Controller dismisses, and I want to find that text added to a MyTableViewController table view.
I have a property in my MyTableViewController to hold all the lines of it:
#property (nonatomic, retain) NSMutableArray *list;;
I just can't get adding rows to work. I don't see where I could do the
[list addObject:];
Here's the code of the MyTableViewController addItem method which I invoke, when a user presses a plus button in the titlebar:
- (IBAction) addItem: (id) sender;
{
NSLog(#"Adding item...");
//Preparing "Add View" which has a single text field
AddViewController *addViewController = [[AddViewController alloc] init];
addViewController.title = #"Add Item";
UINavigationController *modalController = [[UINavigationController alloc]
initWithRootViewController:addViewController];
[addViewController release];
// Showing the prepared Add View controller modally
[self.navigationController presentModalViewController:modalController animated:YES]
NSLog(#"Modal controller has been presented.");
[modalController release];
}
And here is the code in the AddViewController, which is invoked after the textfield is typed in and pressing Done in the titlebar:
- (IBAction) done: (id) sender
{
NSLog(#"Reached Done");
if (textField != nil) {
self.fieldText = textField.text;
}
NSLog(#"About to dissmiss modal controller...");
[[self parentViewController] dismissModalViewControllerAnimated:YES];
NSLog(#"Modal controller has been dismissed.");
}
It is pretty common to create a delegate protocol for such Add Controller and make the parent controller its delegate.
When the Add Controller is "done" (i.e. not cancelled with a possible Cancel button), it calls a delegate method, say, addControllerIsDone: to let the parent table view controller know that it should take the set value, add it to the list, and dismiss the Add controller.
You could also pass the list to the Add Controller and let it add the set value itself before the [parentViewController dismissModalViewControllerAnimated:YES] call.
It depends whether you want to keep the control of the list in your table view controller or you want to pass it to the Add Controller.
And after the Add Controller is dismissed, you can either figure out where the cell for new entry should be added in the tableView and insert it with a nice animation, reload the section (animation also possible) or whole tableView (animation not possible).
First option could looke like this:
#class AddViewController;
#protocol AddViewControllerDelegate <NSObject>
- (void)controllerIsDone:(AddViewController *)controller;
#end
#interface AddViewController : UIViewController
#property (nonatomic, assign) id<AddViewControllerDelegate> delegate;
#end
And the 'done' code
- (IBAction) done: (id) sender
{
......
[self.delegate controllerIsDone:self];
NSLog(#"About to dissmiss modal controller...");
[[self parentViewController] dismissModalViewControllerAnimated:YES];
NSLog(#"Modal controller has been dismissed.");
}
And the MyViewController:
#interface MyViewController : UIViewController <AddViewControllerDelegate>
#end
So it has to implement the controllerIsDone: method. Like this for example:
- (void)controllerIsDone:(AddViewController *)controller
{
[self.list addObject:controller.textField.text];
}
As the AddViewController dismisses itself, MyViewController doesn't have to do it in the delegate method. But the good practice would be that if you popped up the modal view controller, you should also dismiss it, just for symmetry's sake. ;)
In this case, of course the textField has to be a publicly accessible property.
I'm sure you'll figure out the second option.
Read up on Decorator pattern in Cocoa Fundamentals Guide.
After you dismiss your modal controller:
[self addObjectToMyModel:newObject];
such that if you called [tableView reloadData] it would show up, but you don't need to call that, instead:
you need to know where the new object will appear in your table, determine the indexPath, and:
NSIndexPath *indexPathOfInsertedCell = …;
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPathOfInsertCell]
withRowAnimation:UITableViewRowAnimationFade];
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. :)
Is there a way to call code when a modal view is finished dismissing?
EDIT:
I'm sorry, I didn't clarify earlier. I'm trying to dismiss a UIImagePickerController and then show a MFMailComposeViewController and attach the image data to the email. When I try to call
[self presentModalViewController: mailController]
right after
[self dismissModalViewController];
I get errors and such.
You use a delegate pattern for the modal view to inform whoever presented it when it's finished.
MyModalViewController.h:
#protocol MyModalViewControllerDelegate;
#interface MyModalViewController : UIViewController
{
id<MyModalViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<MyModalViewControllerDelegate> delegate;
#end
#protocol MyModalViewControllerDelegate
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController;
#end
MyModalViewController.m:
#synthesize delegate;
// Call this method when the modal view is finished
- (void)dismissSelf
{
[delegate myModalViewControllerFinished:self];
}
ParentViewController.h:
#import "MyModalViewController.h"
#interface ParentViewController : UIViewController <MyModalViewControllerDelegate>
{
}
ParentViewController.m:
- (void)presentMyModalViewController
{
MyModalViewController* myModalViewController = [[MyModalViewController alloc] initWithNibName:#"MyModalView" bundle:nil];
myModalViewController.delegate = self;
[self presentModalViewController:myModalViewController animated:YES];
[myModalViewController release];
}
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController
{
[self dismissModalViewControllerAnimated:YES];
}
EDIT:
I haven't used UIImagePickerController, but looking at the docs, it looks like you already have most of the code done for you, as there is an existing UIImagePickerControllerDelegate class that has three different "dismissal" delegate callbacks (although one is deprecated). So you should make your ParentViewController class (whatever that is) implement the UIImagePickerControllerDelegate pattern and then implement those methods. While each method will do something different (since you have to handle when the user actually selects an image, or if they cancel), they each will do the same thing at the end: call dismissModalViewControllerAnimated: to dismiss the picker.
You have to dismiss the modalViewController somehow right? Either a UIButton, or by code:
- (void)dismissModalViewControllerAnimated:(BOOL)animated
In the IBAction (e.g. delegate) for the UIButton or in the method above, call whatever code you want.
I don't think there is a specific notification yet can subscribe to, to know when dismiss animation is done,...BUT. You can implement viewDidAppear: in the view controller that presented the modal view. This is what I do, when I use the (to UIImagePickerController quite similar) ABPeoplePickerNavigationController.
In the callback from people picker, I remember the person tapped in the picker on an instance variable, like this:
- (void)callbackFromModalView:(id)dataFromModalView {
// remember dataFromModalView as I need it when dismissed
self.dataFromModalView = dataFromModalView;
// now initiate dismissal
[self dismissModalViewControllerAnimated:YES];
}
then, in your view controller, implement this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.dataFromModalView) {
//...present now view here
// don't forget to reset this one
self.dataFromModalView = nil;
}
}
in effect, you are using the combination of viewWillAppear: and the dataFromModalView property as the "notification about modal view dismissed".
I have an app with tabbar and webview. I'm trying to make the app come back to default url each time user taps the bar. Right now I'm intercepting taps and launching a method, however it's not affecting my webview (it's not loading the page). The method works properly when called from the class, but not when it's called from my app delegate, where I'm intercepting taps.
I suspect it's something with the way I create the SecondViewController object that it's not pointing to the webview, but I have no clue what I'm doing wrong.
Here is the code:
Second view header (where the WebView is located)
#interface SecondViewController : UIViewController {
IBOutlet UIWebView *secondView;
}
- (void) goToPage;
Second view implementation
#import "SecondViewController.h"
#implementation SecondViewController
- (void)awakeFromNib
{
[self goToPage];
}
- (void) goToPage
{
NSLog(#"go to page");
NSString *newURL = [NSString stringWithFormat:#"http://pageurl"];
[secondView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:newURL]]];
}
My app delegate, where I call the SecondViewController class method:
#import "RedDragonAppDelegate.h"
#import "SecondViewController.h"
#implementation RedDragonAppDelegate
#synthesize window;
#synthesize rootController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after application launch
[window addSubview:rootController.view];
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"didSelectViewController %d", rootController.selectedIndex);
SecondViewController * sv = [[SecondViewController alloc] init];
if (rootController.selectedIndex == 0){
//NSLog(#"if in didSelectViewController 0");
} else if (rootController.selectedIndex == 1) {
//NSLog(#"if in didSelectViewController 1");
[sv goToPage];
}
}
Thanks fot your help!
If I understand correctly, you've got an instance of SecondViewController with secondView connected to an instance of UIWebView in Interface Builder. What you want to do is call goToPage on that instance of SecondViewController from RedDragonAppDelegate. (In particular, note that I'm talking about instances of these--I believe this is the underlying issue.)
In tabBarController:didSelectViewController:, when you do SecondViewController * sv = [[SecondViewController alloc] init];, you are creating a new instance of SecondViewController and you can call its goToPage method, but sv is not the same instance of SecondViewController that appears in Interface Builder and has secondView connected to the UIWebView (that is, when you create a new instance of SecondViewController, the ivar secondView is unset and seems to be nil, but I don't know that it's guaranteed to be nil).
What you probably(*) want to do is add IBOutlet SecondViewController *sv; to the #interface of RedDragonAppDelegate, make sure that you have an instance of RedDragonAppDelegate in Interface Builder, connect the new IBOutlet sv of RedDragonAppDelegate to the instance of SecondViewController in Interface Builder, and delete the line in tabBarController:didSelectViewController: that defines and initializes sv.
(*) I'm not 100% certain on this because I don't do iPhone stuff and I don't know how your various views/objects/etc. are arranged in XIB/NIB files, but if everything's in one XIB/NIB file, I'm pretty sure it'll work.