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.
Related
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];
}
I have two view controllers, firstViewController and secondViewController. I am using this code to switch to my secondViewController (I am also passing a string to it):
secondViewController *second = [[secondViewController alloc] initWithNibName:nil bundle:nil];
second.myString = #"This text is passed from firstViewController!";
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];
I then use this code in secondViewController to switch back to the firstViewController:
[self dismissModalViewControllerAnimated:YES];
All of this works fine. My question is, how would I pass data to the firstViewController? I would like to pass a different string into the firstViewController from the secondViewController.
You need to use delegate protocols... Here's how to do it:
Declare a protocol in your secondViewController's header file. It should look like this:
#import <UIKit/UIKit.h>
#protocol SecondDelegate <NSObject>
-(void)secondViewControllerDismissed:(NSString *)stringForFirst
#end
#interface SecondViewController : UIViewController
{
id myDelegate;
}
#property (nonatomic, assign) id<SecondDelegate> myDelegate;
Don't forget to synthesize the myDelegate in your implementation (SecondViewController.m) file:
#synthesize myDelegate;
In your FirstViewController's header file subscribe to the SecondDelegate protocol by doing this:
#import "SecondViewController.h"
#interface FirstViewController:UIViewController <SecondDelegate>
Now when you instantiate SecondViewController in FirstViewController you should do the following:
// If you're using a view controller built with Interface Builder.
SecondViewController *second = [[SecondViewController alloc] initWithNibName:"SecondViewController" bundle:[NSBundle mainBundle]];
// If you're using a view controller built programmatically.
SecondViewController *second = [SecondViewController new]; // Convenience initializer that uses alloc] init]
second.myString = #"This text is passed from firstViewController!";
second.myDelegate = self;
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];
Lastly, in the implementation file for your first view controller (FirstViewController.m) implement the SecondDelegate's method for secondViewControllerDismissed:
- (void)secondViewControllerDismissed:(NSString *)stringForFirst
{
NSString *thisIsTheDesiredString = stringForFirst; //And there you have it.....
}
Now when you're about to dismiss the second view controller you want to invoke the method implemented in the first view controller. This part is simple. All you do is, in your second view controller, add some code before the dismiss code:
if([self.myDelegate respondsToSelector:#selector(secondViewControllerDismissed:)])
{
[self.myDelegate secondViewControllerDismissed:#"THIS IS THE STRING TO SEND!!!"];
}
[self dismissModalViewControllerAnimated:YES];
Delegate protocols are EXTREMELY, EXTREMELY, EXTREMELY useful. It would do you good to familiarize yourself with them :)
NSNotifications are another way to do this, but as a best practice, I prefer using it when I want to communicate across multiple viewControllers or objects. Here's an answer I posted earlier if you're curious about using NSNotifications: Firing events accross multiple viewcontrollers from a thread in the appdelegate
EDIT:
If you want to pass multiple arguments, the code before dismiss looks like this:
if([self.myDelegate respondsToSelector:#selector(secondViewControllerDismissed:argument2:argument3:)])
{
[self.myDelegate secondViewControllerDismissed:#"THIS IS THE STRING TO SEND!!!" argument2:someObject argument3:anotherObject];
}
[self dismissModalViewControllerAnimated:YES];
This means that your SecondDelegate method implementation inside your firstViewController will now look like:
- (void) secondViewControllerDismissed:(NSString*)stringForFirst argument2:(NSObject*)inObject1 argument3:(NSObject*)inObject2
{
NSString thisIsTheDesiredString = stringForFirst;
NSObject desiredObject1 = inObject1;
//....and so on
}
I could be way out of place here, but I am starting to much prefer the block syntax to the very verbose delegate/protocol approach. If you make vc2 from vc1, have a property on vc2 that you can set from vc1 that is a block!
#property (nonatomic, copy) void (^somethingHappenedInVC2)(NSString *response);
Then, when something happens in vc2 that you want to tell vc1 about, just execute the block that you defined in vc1!
self.somethingHappenedInVC2(#"Hello!");
This allows you to send data from vc2 back to vc1. Just like magic. IMO, this is a lot easier/cleaner than protocols. Blocks are awesome and need to be embraced as much as possible.
EDIT - Improved example
Let's say we have a mainVC that we want to present a modalVC on top of temporarily to get some input from a user. In order to present that modalVC from mainVC, we need to alloc/init it inside of mainVC. Pretty basic stuff. Well when we make this modalVC object, we can also set a block property on it that allows us to easily communicate between both vc objects. So let's take the example from above and put the follwing property in the .h file of modalVC:
#property (nonatomic, copy) void (^somethingHappenedInModalVC)(NSString *response);
Then, in our mainVC, after we have alloc/init'd a new modalVC object, you set the block property of modalVC like this:
ModalVC *modalVC = [[ModalVC alloc] init];
modalVC.somethingHappenedInModalVC = ^(NSString *response) {
NSLog(#"Something was selected in the modalVC, and this is what it was:%#", response);
}
So we are just setting the block property, and defining what happens when that block is executed.
Finally, in our modalVC, we could have a tableViewController that is backed by a dataSource array of strings. Once a row selection is made, we could do something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selectedString = self.dataSource[indexPath.row];
self.somethingHappenedInModalVC(selectedString);
}
And of course, each time we select a row in modalVC, we are going to get a console output from our NSLog line back in mainVC. Hope that helps!
hmm, look for the notification centre and pass back info in a notification. here is apples take on it
- I take this approach personally unless any one has any other suggestions
Define a delegate protocol in the second view controller and make the first one the delegate of the second.
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 asked this question earlier with way too much code.
The ViewController initializes a UIView chain, Controller>>View>>SubView, in the ViewController. After the SubView is initialized the ViewController is set as its delegate:
aSubView.delegate = self;
NSLog(#"$#",aSubview.delegate), returns the ViewController, so I know it is set.
In the SubView, NSLog(#"$#",self.delegate),returns random crap such a hr.lproj or a file path to the Foundation framework.
It crashes when attempting to implement any of the delegates methods, since the delegate doesn't link to the ViewController but instead randomness.
This is what the SubView.h file looks like:
#import "TestDelegate.h"
#interface TestSubView : UIView {
id<TestDelegate> delegate;
}
#property (assign) id<TestDelegate> delegate;
EDIT: ViewController is initialized in the app delegate as such:
ViewController *controller = [[ViewController alloc] init];
[window addSubview:controller.view];
[controller release];
The only other thing I added to the App Delegate, over the default is an import of the ViewController header
Is it possible the view controller object is being released/dealloced between the two calls to NSLog?
any-body tried a scenario like, a login screen to show for the first time and after validation of username the application starts with uitabbar controller.
i tried with the application with uitabbar only. with the login screen put as first-view in tabbar controller, with "TabBarController.tabBar.hidden=TRUE;" but the view is getting distorted (the space for tabbar is still empty) and here some one can help me in getting the view properly displayed?
thanks,
abhayadev s
another possibility is to show the login viewController as a modal viewController. Modal VC hide the tabbar.
Create another viewController (e.g. LoginViewController). In your AppDelegate in applicationDidFinishLaunching: add (isLogged is just for exemple):
if (self.isLogged) {
[window addSubview:self.tabBarViewController.view];
} else {
LoginViewController *loginVC = [[LoginViewController alloc] initWithNibName:#"login" bundle:nil];
[window addSubview:loginVC.view];
}
And you should call a method when login is successful that removes loginVC view and add tabBarController.view on the window.
It's no more complicated than that.
The application only needs to use the LoginViewController until the user is authenticated, signed up, linked with FB or whatever, and then that LoginViewController should be released as it is no longer needed. This solution might look like overkill but I think it extends well. Here's how I do it, and in my case I just had a Splash screen that displayed some information and then the user goes to the main part of the application:
First I actually made a simple protocol which I can implement in my MainAppDelegate.m file, called SplashDelegate:
//SplashDelegate.h
#protocol SplashDelegate <NSObject>
- (void) splashExit : (id) sender;
#end
Then my MainAppDelegate implements that. First after the application launches, self.window's rootViewController will point to my SplashViewController, then after that is closed, the MainViewController (in OP's case his TabViewController) will be instantiated.
Here's the important part of my main app delegate h file:
#import "SplashDelegate.h"
#class MainViewController;
#class SplashViewController;
#interface MainWithLoginPageAppDelegate : NSObject <UIApplicationDelegate, SplashDelegate> {
MainViewController *_viewController;
UIWindow *_window;
SplashViewController *_splashViewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet MainViewController *viewController;
#property (nonatomic, retain) IBOutlet SplashViewController *splashViewController;
And the important part of the m file:
#import "MainWithLoginPageAppDelegate.h"
#import "MainViewController.h"
#import "SplashViewController.h"
#implementation MainWithLoginPageAppDelegate
#synthesize window=_window;
#synthesize viewController=_viewController;
#synthesize splashViewController = _splashViewController;
- (void) splashExit : (id) sender
{
_viewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[_splashViewController release];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//initialize my splash controller
_splashViewController = [[SplashViewController alloc] initWithNibName:#"SplashViewController" bundle:nil];
self.splashViewController.parentDelegate = self;
self.window.rootViewController = self.splashViewController;
[self.window makeKeyAndVisible];
return YES;
//note that _viewController is still not set up... it will be setup once the login phase is done
}
Then all you need in the SplashViewController (or LoginViewController) is your login test and your property for the parentDelegate. Inside of SplashViewController.h I make my parentDelegate an instance variable, as id <SplashDelegate> _parentDelegate. Another good idea would be to extend the protocol above to offload the responsibility of checking the user's login so that you're not checking the login inside of the view controller class.
Edit: Inside of the LoginViewController, then, you can call [self.parentDelegate splashExit:self], and modify the idea above to include your login checks as well.
Hope this is helpful to someone!
Have the login page in the firstview controller.Add the tabbar to the window only after navigating to the second view controller.Thats it.
All the best.