从另一个类调用时,Object为nil

时间:2012-10-23 00:09:26

标签: iphone objective-c ios cocoa-touch

当在另一个类中调用方法时,我想更改另一个对象的属性。

更改此对象的属性的代码位于第一个类的方法中,并且在从它自己的类调用它时起作用,但是当从另一个类调用时,该方法中的对象返回nil。

以下是代码:

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 (没什么太重要的,但可能有帮助?)

@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;
    }
}

11 个答案:

答案 0 :(得分:4)

您发布的内容如下:

-(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.

}

所以你正在做NSLog(@"%f", 0);

如果您通过访问view属性加载视图,则menuView将由IB规则初始化。 有关viewController视图加载/卸载的详细信息,请参阅reference docs

答案 1 :(得分:2)

我认为这可能对你有所帮助。

AppDelegate课程中,您必须声明ViewController课程的对象。将其设为YourAppDelegate类的属性。如下。 (这将导入ViewController类并创建YourAppDelegate类的共享对象,以便您只需导入YourAppDelegate即可全局访问YourAppDelegate.h类的成员。

#import "ViewController.h"

#define UIAppDelegate ((YourAppDelegate *)[UIApplication sharedApplication].delegate)

@interface YourAppDelegate : NSObject <UIApplicationDelegate> 
{
     ViewController  *objViewController;
}

@property (nonatomic, retain) ViewController  *objViewController;

@end

并在YourAppDelegate.m文件中合成属性。

@implementation YourAppDelegate

@synthesize objViewController;

@end

然后棘手的部分是,您必须在加载ViewController类时在YourAppDelegate类中备份ViewController类的对象。

首先导入YourAppDelegate.h类和ViewController.h工具ViewController.m代表中的viewWillAppear:,如下所示。

- (void)viewWillAppear:(BOOL)animated 
{
    [super viewWillAppear:animated];
    UIAppDelegate.objViewController = self;
}

然后在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;
            }
    }
}

试试这种方式。我不是说这是正确的方法,但这应该有效。很高兴,如果这对你有所帮助。

答案 2 :(得分:1)

问题是:

- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem {

    ViewController *firstViewController = [[[ViewController alloc] init] autorelease];

    ...

    [firstViewController closeMenu];


}

当您从那里调用closeMenu时,它永远不会被初始化,因为没有足够的时间来初始化视图控制器的viewviewDidLoad的{​​{1}}方法是此时也未被调用。 {nib}也不会从nib创建firstViewController,因此这就是menuView的原因。

也许由于某种原因可能会有一段延迟,所以nil被创建,但这不是你应该在iOS中做的事情。

因此,如果您不想展示自己的menuView,只需在menuView而不是firstViewController中添加一些布尔值:

closeMenu

然后在firstViewController.shouldCloseMenu = YES; ViewController方法中执行以下操作:

viewDidLoad

也许这不是最好的方法,但现在你知道它是如何工作的。

答案 3 :(得分:1)

我相信你的问题与初始化viewController的方式有关。

而不是

ViewController *firstViewController = [[[ViewController alloc] init] autorelease];

使用

ViewController *firstViewController = [[[ViewController alloc] initWithNibName:@"yourNibName" bundle:nil] autorelease];

我假设你有一个笔尖因为你正在使用IBOutlet。但我相信IBOutlet永远不会被设置,因为你还没有加载nib文件。

同时仔细检查您的IBOutlet与界面构建器的连接并使用“self.menuView”

答案 4 :(得分:1)

编辑2:

嗯,您已将代码发送给我,所以现在我再也不能说我没有足够的信息来解决您的问题。

让我们看看。

现在我看到你的ViewController是你应用的rootViewController,如下所示:

- (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;
}

好,现在ViewController如何与您的SDNestedTableViewController相关联?

你在ViewController的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];

}

好吧,所以看起来SDMenuViewController是ViewController的子代。现在,您在SDMenuViewController中有一个名为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;
    }
}

所以,你想要引用回到现有的 ViewController对象,对吗?因为那里你正在制作另一个。所以,你可以这样做:

ViewController *firstViewController = self.parentViewController;

这将引导您对SDMenuViewController的父级进行引用,该父级是ViewController的实例。当您进行addChildViewController:来电时,会设置此属性。


好吧,但这很令人困惑:

在你的帖子中,你说你的item:subItemDidChange:方法在SDNestedTableViewController中,但在你发给我的代码中它是在SDMenuViewController中。

在SDNestedTableViewController中,我找到了这个方法:

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

所以看起来你没有使用与原帖相同的代码,但足够接近,无论如何。


现在,如果你想在应用程序中从任何地方获得对ViewController实例的引用,不仅仅是你的SDMenuViewController(恰好是ViewController实例的子代),你应该使用@Mathew Varghese的answer

以下是对此方法的重述:

  1. 将行+ (AppDelegate *)instance;添加到AppDelegate.h文件中。
  2. 将以下方法添加到AppDelegate.m文件中。
  3. 像这样:

    + (AppDelegate *)instance
    {
        AppDelegate *dg = [UIApplication sharedApplication].delegate;
        return dg;
    }
    

    然后,在您想要该引用的任何对象中,您#import AppDelegate.h并说ViewController *vc = AppDelegate.instance.firstViewController;

    无论如何,这只是另一种说法Mathew之前提到过的方式。

答案 5 :(得分:0)

我建议您按以下步骤解决此问题。

  1. 请勿在{{1​​}}中使用firstViewController的任何实例或变量。

  2. 在案例检查栏中,将讯息发布到SDMenuViewController

  3. NSNotificationCenter注册具有相同消息ID的消息时,请使用ViewController方法作为其处理程序。

  4. 对我来说,使用消息中心调度处理可以解耦控制器之间的关系。这是一种更好的方式,您可以更少地关注另一个控制器的生命周期。

    希望它会有所帮助。

答案 6 :(得分:0)

ViewController alloc-init和视图控制器属性alloc-init之间存在差异。

关于你的第二个例子(从另一个类调用)。您当前的代码表明您alloc-init firstViewController,但随后不对其执行任何操作。假设您没有覆盖ViewController的init方法,其属性和iVars应该是nil(或者在最坏的情况下未定义)。您需要先alloc-init firstViewController.menuView firstViewController.menuView = [[UIView alloc] initWithFrame]; // Don't do this. 。即:

viewDidLoad

这种方法的问题在于你将firstViewController的属性设置为另一个类,这通常是相当平均的设计实践。这个必需的设置通常会在closeMenu中进行,但是因为你还没有对firstViewController做任何事情,所以它永远不会被调用。

相比之下,当您从自己的View Controller中调用menuView = [[UIView alloc] init];时,您实际上正在使用视图执行某些操作并且首先调用viewDidLoad(或找到{{1}}的任何地方),因此初始化你的menuView对象。

在尝试对其执行任何操作之前,您需要确保首先初始化menuView对象,只需初始化包含它的View Controller是不够的。

答案 7 :(得分:0)

#import "SDMenuViewController.h"
#import "ViewController.h"

- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
  

//为什么我们在这里分配这个对象,如果仅在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;
    }
}

将其更改为

#import "SDMenuViewController.h"
#import "ViewController.h"

- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
  

//为什么我们在这里分配这个对象,如果仅在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;
    }
}

答案 8 :(得分:0)

一切正确,更改 - (void)closeMenu 方法,如...

-(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.
}

试试这个并告诉我。

答案 9 :(得分:0)

尝试删除 UIView * menuView; //来自界面文件的对象

@interface ViewController : UIViewController {

    // try to remove this line
    UIView *menuView; //the object

}

并更新此方法

-(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);

}

答案 10 :(得分:0)

我建议你用这个:

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");
}