I want to change properties of another object, when a method is called in another class.
The code to change the properties of this object sits in a method of the first class, and works when calling it from it's own class, but when called from the other class the object in the method returns nil.
Here is the code:
ViewController.h
#interface ViewController : UIViewController {
UIView *menuView; //the object
}
#property (nonatomic, retain) IBOutlet UIView *menuView;
-(void)closeMenu; //the method
#end
ViewController.m
#implementation ViewController
#synthesize menuView;
-(void)closeMenu{
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
NSLog(#"%f", menuView.frame.size.height); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
SDNestedTableViewController.h (nothing too important, but might help?)
#interface SDMenuViewController : SDNestedTableViewController{
}
SDNestedTableViewController.m
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
What you posted looks like:
-(void)closeMenu{
// menuView is never initialized, == nil
[nil setFrame:CGRectMake(0, -0, 0, 0)];
NSLog(#"%f", 0); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
So you are doing NSLog(#"%f", 0);.
If you do load the view by accessing the view property, the menuView will be initialized by IB rules.
For the details of viewController view loading/unloading see the reference docs.
I think this may help you.
At Your AppDelegate class, you have to declare an object of ViewController class. Make it as a property of the YourAppDelegate class. like below. (This would import ViewController class and creates a shared object of YourAppDelegate class so that you can access the members of YourAppDelegate class globally by simply importing the YourAppDelegate.h).
#import "ViewController.h"
#define UIAppDelegate ((YourAppDelegate *)[UIApplication sharedApplication].delegate)
#interface YourAppDelegate : NSObject <UIApplicationDelegate>
{
ViewController *objViewController;
}
#property (nonatomic, retain) ViewController *objViewController;
#end
And synthesize the property at YourAppDelegate.m file.
#implementation YourAppDelegate
#synthesize objViewController;
#end
Then the tricky part is, you have to backup the object of ViewController class in the YourAppDelegate class at the time you are loading the ViewController class.
For that first import the YourAppDelegate.h in the ViewController.h class and at the ViewController.m implement viewWillAppear: delegate as follows.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIAppDelegate.objViewController = self;
}
Then at SDNestedTableViewController.m,
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = (ViewController *)UIAppDelegate.objViewController;
if(firstViewController && [firstViewController isKindOfClass:[ViewController class]])
{
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
}
Try this way. I am not saying this as the right way but, this should works. Glad if this helps you.
EDIT 2:
Well, you shipped your code over to me, so now I can no longer say that I don't have enough information to solve your problem.
Let's see.
Now I see that your ViewController is the rootViewController of your app, like so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
Good, now how does the ViewController relate to your SDNestedTableViewController?
You have this in your ViewController's viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
SDMenuViewController *mvc = [[[SDMenuViewController alloc] initWithNibName:#"SDNestedTableView" bundle:nil] autorelease];
[self addChildViewController:mvc];
[mvc didMoveToParentViewController:self];
[menuView addSubview:mvc.view];
// Some other stuff with gesture recognizers I'm omitting...
[self openMenu];
}
Alright, so it looks like SDMenuViewController is the child of ViewController. Now, you have a method in SDMenuViewController called item:subItemDidChange:
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[firstViewController closeMenu];
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
So, you want the reference back to the existing ViewController object, right? Because right there you're making another one. So, you can do this:
ViewController *firstViewController = self.parentViewController;
That gets you a reference to SDMenuViewController's parent, which is the instance of ViewController. This property is set when you do your addChildViewController: call.
Okay, this is confusing though:
In your post, you say that your item:subItemDidChange: method is in SDNestedTableViewController, but in the code you sent me it's in the SDMenuViewController.
In the SDNestedTableViewController, I found this method:
- (void) mainItem:(SDGroupCell *)item subItemDidChange: (SDSelectableCell *)subItem forTap:(BOOL)tapped
{
if(delegate != nil && [delegate respondsToSelector:#selector(item:subItemDidChange:)] )
{
[delegate performSelector:#selector(item:subItemDidChange:) withObject:item withObject:subItem];
}
}
So it looks like you're not using the same code as in the original post, but close enough, whatever.
Now, if you want to get a reference to the ViewController instance from anywhere in the app, not just your SDMenuViewController (which happens to be the child of the ViewController instance) you should use #Mathew Varghese's answer.
Here's a restatement of this method:
Add the line + (AppDelegate *)instance; to your AppDelegate.h file.
Add the following method to your AppDelegate.m file.
Like so:
+ (AppDelegate *)instance
{
AppDelegate *dg = [UIApplication sharedApplication].delegate;
return dg;
}
Then, in whatever object you want that reference, you #import AppDelegate.h and say ViewController *vc = AppDelegate.instance.firstViewController;
Anyway, it's just another way of saying what Mathew mentioned earlier.
the problem is:
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem {
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
...
[firstViewController closeMenu];
}
When you call closeMenu from there, it is never initialized, because not enough time has passed to initialize view of view controller, viewDidLoad method of your firstViewController is not called at this point either. menuView is not created from nib either, so this is the reason why it is nil.
Maybe for some reason there might be a delay long enough so menuView is created, but this is not how you should do things in iOS.
So, if you don't want to show your menuView, just add some boolean value to your firstViewController and instead of closeMenu do:
firstViewController.shouldCloseMenu = YES;
Then in your ViewController in viewDidLoad method do something like:
if (self.shouldCloseMenu ) {
[self closeMenu];
}
Maybe this is not the best way to do it, but now you have an idea how it suppose to work.
I believe your problem is the related to the way you have initialized the viewController.
Instead of
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
use
ViewController *firstViewController = [[[ViewController alloc] initWithNibName:#"yourNibName" bundle:nil] autorelease];
I'm assuming you have a nib because you are using an IBOutlet. But I believe the IBOutlet is never setup because you have not loaded the nib file.
Also double check your IBOutlet connection with interface builder and use "self.menuView"
I would suggest you to solve this problem in the following steps.
Do not use any instance or variable of firstViewController in the SDMenuViewController.
In the case check block, post a message to the NSNotificationCenter
In the ViewController register the message with the same message Id, use the closeMenu method as its handler.
For me, use the message center to dispatch the handling can decouple the relationship between controllers. This is a better way that you would concern less about the lifecycle of the controller within another one.
Hope it would be helpful.
There is a difference between alloc-init'ing a ViewController and alloc-init'ing that view controller's properties.
Regarding your second example (calling from another class). Your current code indicates that you alloc-init firstViewController, but then don't do anything with it. Assuming you have not overriden your ViewController's init method, its properties and iVars should be nil (or undefined at worst). You need to alloc-init your firstViewController.menuView first. I.e:
firstViewController.menuView = [[UIView alloc] initWithFrame]; // Don't do this.
The problem with this approach is that you're setting up firstViewController's properties form another class, and that's generally fairly average design practice. This required setup would usually happen in viewDidLoad but because you haven't done anything with firstViewController yet, it never gets called.
In contrast, when you call closeMenu from its own View Controller, the odds are you are actually doing something with the view and viewDidLoad (or wherever menuView = [[UIView alloc] init];is found) is called first, thus initialising your menuView object.
You need to ensure that your menuView object is initialised first before you try and do anything with it, just initialising the View Controller that contains it is not enough.
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
// why are we allocating this object here, if it is only required in case Checked :
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
Change it to
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
// why are we allocating this object here, if it is only required in case Checked :
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
// here no need to put object in autorelease mode.
ViewController *firstViewController = [[ViewController alloc] init];
[firstViewController closeMenu]; //called from other class
[firstViewController release];
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
try to remove UIView *menuView; //the object from the interface file
#interface ViewController : UIViewController {
// try to remove this line
UIView *menuView; //the object
}
and update this method
-(void)closeMenu{
[self.menuView setFrame:CGRectMake(self.menuView.frame.origin.x, -self.menuView.frame.size.height, self.menuView.frame.size.width, self.menuView.frame.size.height)];
NSLog(#"%f", self.menuView.frame.size.height);
}
Everything is correct, change the -(void)closeMenu method like...
-(void)closeMenu
{
menuView=[[UIView alloc]initWithFrame:CGRectMake(50.0,50.0,200.0,200.0)]
NSLog(#"%f", menuView.frame.size.height); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
Try this and let me know.
I suggest you use this:
if(menuView) {
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
} else {
NSLog(#"menuView is nil");
}
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
ChatViewController *chatView;
if(contactView==nil)
{
chatView=[[ChatViewController alloc] initWithNibName:#"ChatViewController" bundle:nil];
}
[self.navigationController pushViewController:chatView animated:YES];
[messageDelegate newMessageReceived:m];
}
The above delegate method called for every incoming message.When it called, it goes to a new UIViewController.Here my problem is a view pushed multiple tinmes,so error will be occered.how can i fix this error in iphone
Add this snippet before pushing the view controller
BOOL viewControllerAlreadyPushed = NO;
for (UIViewController *controller in self.navigationController.viewControllers) {
if ([controller isKindOfClass:[ChatViewController class]]) {
viewControllerAlreadyPushed = YES;
}
}
if(!viewControllerAlreadyPushed) //if not pushed, push it
{
ChatViewController *chatView;
if(contactView==nil)
{
chatView=[[ChatViewController alloc] initWithNibName:#"ChatViewController" bundle:nil];
}
[self.navigationController pushViewController:chatView animated:YES];
[messageDelegate newMessageReceived:m];
}
in buttonpress callback:
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
[self presentModalViewController:mailViewController animated:YES];
Delegate Implementation:
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
NSLog (#" Inside MAIL COMPOSER CONTROLLER DELIGATE ");
// Remove the mail view
[self dismissModalViewControllerAnimated:YES];
}
When i press cancel button in the MailComposerView, delete is not getting invoked. what am i doing wrong ?
Set your viewController as a MFMailComposeViewControllerDelegate:
#interface CurrentViewController : UIViewController <MFMailComposeViewControllerDelegate>
Set your mailComposer delegate right after instantiation:
MFMailComposeViewController * mailComposer = [[MFMailComposeViewController alloc]init];
mailComposer.mailComposeDelegate = self;
Did you actually make your class a MFMailComposeViewControllerDelegate?
#interface MyViewController : UIViewController <MFMailComposeViewControllerDelegate>
I would like to know if the rootViewController in a navigation controller stack is of specific class. How to do such a thing ?
Thx for helping,
Stephane
Here you go buddy
id rootController = [self.navigationController rootViewController];
if([rootController isKindOfClass:[YourDesiredController class]]){
//do something
}
The navigation controllers root view controller is,
id rootVC = [[navigationController viewControllers] objectAtIndex:0];
And check the class of rootVC like this,
if ([rootVC isKindOfClass:[YourClass class]]) {
You can check the class with:
if ([rootViewController isKindOfClass:[YourClass class]]){
}else if ([rootViewController isKindOfClass:[AnotherClass class]]){
}else{
}
Perhaps you can use the method isKindOfClass
[rootViewController isKindOfClass: [RootViewController class]];
How to check your current rootViewController and use it in an if statement :
// Get your current rootViewController
NSLog(#"My current rootViewController is: %#", [[[UIApplication sharedApplication].delegate.window.rootViewController class]);
// Use in an if statement
UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
if ([rootViewController isKindOfClass:[MyViewController class]])
{
NSLog(#"Your rootViewController is MyViewController!!");
}
I am using the following code in my view controller and I want it to present another view controller called "chooserViewController" modaly
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
{
[self presentModalViewController:chooserViewController animated:YES];
}
I am getting a compile error not recognizing "chooserViewController". Am I doing it wrong?
Update:
- (void)add:(id)sender
{
RoutineExerciseChooserViewController *routineExerciseChooserViewController = [[RoutineExerciseChooserViewController alloc] initWithNibName:#"RoutineExerciseChooserViewController" bundle: nil];
[self presentModalViewController:routineExerciseChooserViewController animated:YES];
[routineExerciseChooserViewController release];
}
You need to create chooserViewController:
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated {
ChooserViewController *chooserViewController = [[ChooserViewController alloc] initWithNibName:#"ChooserView" bundle: nil];
[self presentModalViewController:chooserViewController animated:YES];
[chooserViewController release];
}
If you're not loading from a nib, obviously you'll use a different way to create chooserViewController, but you have to do something to ensure it exists, and can then be presented.