【问题标题】:Objective-C: Problems accessing objects in other UIViewControllersObjective-C:访问其他 UIViewController 中的对象时出现问题
【发布时间】:2012-04-17 06:43:53
【问题描述】:

所以我有一个自定义 UITableViewCell,它包含对其包含视图控制器(其中包含其表的 VC)的引用。

//  MyCell.h
#import <UIKit/UIKit.h>
#import "RootViewController.h"

@interface MyCell : UITableViewCell

@property (nonatomic, strong) IBOutlet RootViewController *rootViewController;

-(IBAction)checkBoxClicked:(UIButton*)sender;


//  MyCell.m
@implementation MyCell

@synthesize rootViewController = _rootViewController;

-(IBAction)checkBoxClicked:(UIButton*)sender
{
    [self setCheckBoxChecked:!_checkBoxChecked];
    [_rootViewController refreshVisibleViewForCellTagged:self.tag];
}

在我的单元格中,我有一个按钮可以更改一个变量,然后在我的 rootViewController 中调用一个函数。但是,当我尝试访问 refreshVisibleViewForCellTagged 方法内的 RootViewController 中的任何对象时,实际上调用了该方法,它们是 '0x0' / nil;

//  RootViewController.h
@interface RootViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) IBOutlet UITableView *myTableView;


//  RootViewController.m
- (void) refreshVisibleViewForCellTagged:(NSInteger)cellTag
{
    UITableView *tableView = self.myTableView; // nil
    NSIndexPath *indexPath = [self.myTableView indexPathForSelectedRow]; // nil
    MyCell *selectedCell = (MyCell*)[self.myTableView cellForRowAtIndexPath:indexPath]; // nil

    if (selectedCell.tag == cellTag) {
        NSLog(@"Refresh one way.");
    } else {
        NSLog(@"Do something else.");
    }
}

谁能解释一下为什么我不能从“refreshVisibleViewForCellTagged”方法中访问 RootController 中的任何对象/变量?

请,谢谢!

** 我的大问题是为什么在从不同的视图控制器调用视图控制器中的方法时无法访问任何对象。这里有一些我不知道的重要编程事实,这是权限问题吗?在这种情况下,我没有使用@class(前向分类)。

【问题讨论】:

  • 您不应该继承UITableViewCellDelegate 协议并通过委托模型生成反馈吗?
  • @Brooks - 是的,我收到 cellTag 没有任何问题。
  • @trojanfoe - 我试图在根视图控制器中使用此方法执行的操作是查看当前选定的单元格(其具有可见的详细视图,因为 RootView 是显示 50% 表的分屏和50% 带标签的视图)因此,如果当前选定的单元格是单击复选框的单元格,则刷新视图中的标签以反映复选框的新值。我不知道 Delegate 方法如何帮助我完成这样的自定义任务。
  • 它不会让它变得更容易,但它会让你的子类单元更可重用。

标签: iphone objective-c ios ipad


【解决方案1】:

正如@trojanfoe 所说,委托是一种更好的方法。 而不是#import "RootViewController.h",最好采用委托。因为UITableViewCell 是子视图,RootViewController 是父视图。您不希望孩子与父母直接交谈。

  • 采用委托:

  • MyCell.h文件中

  • 删除#import "RootViewController.h"

  • 修改MyCell.h如下:

@protocol MyCellDelegate; // if you need to have forward declaration

@interface MyCell : UITableViewCell
// @property (nonatomic, strong) IBOutlet RootViewController *rootViewController;
@property (nonatomic) id <MyCellDelegate> delegate;
@end

@protocol MyCellDelegate <NSObject>
- (void)refreshVisibleViewForCellTagged:(NSInteger)cellTag;
@end
  • MyCell.m.
@synthesize delegate;

-(IBAction)checkBoxClicked:(UIButton*)sender {
     [self setCheckBoxChecked:!_checkBoxChecked];
     //[_rootViewController refreshVisibleViewForCellTagged:self.tag];
     [self.delegate refreshVisibleViewForCellTagged:self.tag];
}
  • RootViewController.h中采用MyCell的委托
#import "MyCell.h"

@interface RootViewController : UIViewController <MyCellDelegate>
  • RootViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
      UITableViewCell *cell = // your implementation
      //assuming all your cells are of MyCell kind
      // set RootViewController as the delegate of each cell
      ((MyCell *)cell).delegate = self;

      return cell;
}
  • RootViewController.m中实现委托方法。
- (void)refreshVisibleViewForCellTagged:(NSInteger)cellTag {
     // whatever you have
}

附:以上代码用于说明。我没有运行它们。如果某些部分不起作用,请告诉我,我会修改它。

RootViewController 中的那些对象在您调用的方式中为 nil 的原因是因为您访问的不是同一个 RootViewController 实例。它是一个不同的(新)实例,因此所有对象都是 nil。

【讨论】:

  • OMG Face Palm - 你是对的,我没有访问 RootViewController 的同一个实例 - 在我的公司,我们尝试在 IB 中保留尽可能多的引用,以便我们的设计人员可以修改某些内容而无需程序员——所以在 xib 的 Interface Builder 中,我添加了一个新的 RootViewController 并将其连接到 _rootViewController 所以它是一个全新的实例。很抱歉让你们去追逐野鹅,非常感谢你们的帮助!!
【解决方案2】:

忽略视图控制器甚至参与的事实。你所拥有的是对象,以某种模式连接在一起。访问另一个视图控制器中的数据与访问任何其他对象中的数据没有什么不同。视图控制器没有“魔法”,除了它们与其他对象有一些标准化的连接。

【讨论】:

    【解决方案3】:

    恕我直言,这是一个糟糕的设计。对于初学者,您的单元格不需要引用它所在的表所在的视图控制器(读两遍,这几乎没有意义,因为它的想法很混乱)。您对此视图控制器有很强的参考。那么当操作系统试图释放你的视图控制器时会发生什么?它将永远无法做到,因为表格视图单元格作为对其的强引用,将其保留计数保持在 1。同样的情况也适用于单元格。您可能会在这里遇到保留周期。一般来说,子视图应该对其父视图有弱引用。

    但这甚至不是真正的父母/孩子关系。我建议改为这样的方法,这一切都发生在包含表格视图的视图控制器中:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        // Assuming you set a reuse identifier "cellId" in the nib for your table view cell...
        MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:@"cellId"];
        if (!cell) {
            // If you didn't get a valid cell reference back, unload a cell from the nib
            NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"MyCell" owner:nil options:nil];
            for (id obj in nibArray) {
                if ([obj isMemberOfClass:[MyCell class]]) {
                    // Assign cell to obj, and add a target action for the checkmark
                    cell = (MyCell *)obj;
                    [cell.checkMarkButton addTarget:self action:@selector(checkPressed:) forControlEvents:whateverEventYouWant];
                    break;
                }
            }
         }
         // Set the tag of the cell here, since we may get a different cell back from the reuse queue
         cell.checkMarkButton.tag = indexPath.row;
    
         return cell;
    }
    

    现在设置单击复选标记按钮的方法

    - (void)checkPressed:(id)sender {
        UIButton *checkmark = (UIButton *)sender;
    
        // This will give you the row of the checked button
        int checkedCellRow = checkmark.tag;
    
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:checkedCellRow inSection:0];
    
        // Now you can grab a reference to that cell if you need to
        MyCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    }
    

    这样,您可以将所有与控制器相关的内容保留在控制器类中(即如何处理被按下的复选标记按钮),并且您不需要处理引用单元格的视图控制器的这种古怪问题表。

    编辑:我想我也应该帮助回答您的问题...首先,如果您说在您的 refreshVisibleViewForCell 方法中,您将获得 self.myTableView 的 nil 值,你确定它在 IB 中连接正确吗?即使已经挂上,也可以点击小 x 将其解开,然后再次挂上以确保。还要确保你 @synthesized 你的 myTableView 属性。在没有看到更多代码的情况下,IB 问题是我最好的猜测,为什么你会得到 tableView 的 nil 值。这里的 nil 值也会导致 indexPath 和 selectedCell 为零。至于您的大问题,您可以在视图控制器中访问对象的 properties。当然,这些属性可以是对象。因此,在您的示例中,如果您在 selectedCell 上有一个标签属性,您可以从任何对 selectedCell 有 valid 引用的地方访问它。如果 selectedCell 为 nil,则该属性将为 nil。 @class 更适合头文件。例如,如果您想让自定义单元格成为视图控制器的属性,您可能会说:

    #import <UIKit/UIKit.h>
    
    @class MyCell;
    
    @interface RootViewController : UIViewController
    
    @property (nonatomic, strong) MyCell *cell;
    
    @end
    

    然后,在您的实现文件中,您将实际导入 MyCell.h。提供@class 前向声明只是让您不必在头文件中导入有关 MyCell 类的所有详细信息。标头不需要知道 MyCell 的所有属性和方法,只需要您打算在实现文件中使用它即可。所以你在标题中@class,在实现中#import。

    【讨论】:

    • 我发现 nil 问题正在发生,因为我没有访问我想要的 RootViewController 实例(我在界面生成器中创建了一个新实例)这是一个菜鸟错误。但是,您说得对,这不是最好的设计,我将实施您的一些建议来改进我的代码。非常感谢您的帮助。
    【解决方案4】:

    在 RootViewController.h 中:

    @interface RootViewController : UITableViewController <UITableViewDelegate>
    

    在 RootViewController.m 中:

    - (void) refreshVisibleViewForCellTagged:(NSInteger)cellTag {
    NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
    MyCell *selectedCell = (MyCell*)[self.myTableView cellForRowAtIndexPath:indexPath]; // nil
    

    等等……

    【讨论】:

    • 我不希望 RootViewController 成为 UITableViewController - 它与一半屏幕上的表格和另一半屏幕上的变化视图分开。 (仅限 iPad 应用)
    • 您能否发布代码,告诉我们您在哪里创建了一个名为 myTableView 的属性?
    • 我修改了我的原始帖子以显示 RootViewController.h,其中添加了 myTable 的 IBOutlet - 还有 (at)synthesize。这个变量实际上是在 Interface Builder 中设置的。
    • 您可以这样做 - 因为您使用的是 IB,您可能希望以 RootViewController 的成员身份访问您的子视图控制器 (UiTableView):[self.childViewControllers objectAtIndex:0]
    【解决方案5】:

    我在您的RootViewController 中没有看到myTableView 的声明。但是如果你的RootViewController 实现了UITableViewController,你可以使用self.tableView 来访问tableview。您不需要自己保留对它的引用。

    @RachelD,如果您的 RootViewUITableViewController 更复杂,请考虑使用单独的类,例如 RootTableViewController。然后在您的RootView xib 中,为RootTableViewController 创建IBOutlet 以引用它。像这样:

    // RootTableViewController definition
    @interface RootTableViewController : UITableViewController
    {
    }
    
    // RootViewController definition
    @interface RootViewController : UIViewController
    {
        RootTableViewController *table_c;
    }
    
    @property (nonatomic, retain) IBOutlet RootTableViewController *table_c;
    

    请注意,您需要将“对象”拖到界面构建器中的“对象”部分(对于 RootViewController),并在该对象的“自定义类”部分中键入 RootTableViewController。右键单击该对象,确保其 IBOutlet、视图、2 个代理设置正确。

    myTableView 为 nil 的原因是它没有正确初始化。我的意思是,如果您不使用UITableViewController,您有责任通过界面生成器或其他方式手动分配它。

    【讨论】:

    • (at)interface RootViewController : UIViewController (at)property (nonatomic, strong) IBOutlet UITableView *myTableView;
    • 有什么特别的原因不是您从UITableViewController 派生的吗?通过使用UITableViewController,您无需使用IBOutlet 来保留您自己的myTableView 变量。
    • 它在一半屏幕上拆分为表格,在另一半屏幕上显示变化的视图。 (仅限 iPad 应用)
    • 我已经修改了我的答案,希望它对你更有意义。
    • 哦,我看到制作了一个新的 UITableViewController 并将其添加到现有的 RootViewController 中,这绝对可以。这不会让我处于同样的境地吗?用户单击 UITableCell 上的复选框,因此 UITableCell 的委托 ping UITableViewController 但随后 UITableViewController 仍需要粉红色 RootViewController 以刷新 rootView 中的视图 - 当从其他视图控制器调用其方法时,我无法访问 rootView 中的任何变量.
    猜你喜欢
    • 2012-08-13
    • 2013-07-04
    • 1970-01-01
    • 2011-07-11
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 2012-12-14
    • 1970-01-01
    相关资源
    最近更新 更多