【问题标题】:IBOutlet properties does not update when using prepareForSegue method使用 prepareForSegue 方法时,IBOutlet 属性不会更新
【发布时间】:2011-12-23 10:50:20
【问题描述】:

我在将值传递给 destinationViewController 的 IBOutlet 属性时遇到问题,但它在普通属性上工作正常,请参阅下面的代码

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"NewsCellToDetail"]) {        
    testViewController *viewController = segue.destinationViewController;
    viewController.titleLabel.text = @"test"; // set the IBOutlet label text to something
    NSLog(@"%@",viewController.titleLabel.text); // this will output to nil
    viewController.textTest = @"testing2"; // set the property to something
    NSLog(@"%@", viewController.textTest) // this will output the string testing2
}

这是头文件testviewcontroller.h的代码

#import <UIKit/UIKit.h>
@interface NewsDetailViewController : UIViewController
@property (strong, nonatomic) IBOutlet UILabel *titleLabel;
@property (strong, nonatomic) NSString *textTest;
@end

我已经合成了这两个属性。

感谢您的帮助。

【问题讨论】:

    标签: objective-c ios5 storyboard xcode4.2


    【解决方案1】:

    来自目标控制器的 viewDidLoad 方法完全按照它说的做。它加载所有视图组件。因此,在此之前对其进行修改的任何尝试都将失败。

    传递数据的最佳方式是将其存储在在 viewDidLoad 期间未加载但在对象初始化时加载的属性中。

    通常在 FirstViewController.m 中:

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
     if ([[segue identifier] isEqualToString:@"segueName"]) {
         SecondViewController *destinationVC = [segue destinationViewController];
         destinationVC.test = @"Test string";
     } }
    

    在 SecondViewController.m 文件中

     -(void)viewDidLoad {
     [super viewDidLoad];
     // Do any additional setup after loading the view.
     self.testLabel.text = self.test; 
    }
    

    【讨论】:

      【解决方案2】:

      我得到这个答案有点晚了,但我最近遇到了这个问题,如果我们仍然手动实例化事物而不是让情节提要处理它,那么原因就很明显了。这恰好与您在手动实例化视图控制器时操纵视图的原因相同:segue 的destinationViewController 尚未调用loadView:,它在情节提要中通过反序列化运行相关 nib 中的所有视图对象。

      一个非常简单的方法来查看它的实际效果:

      1. 创建两个 ViewController 场景(ViewController1 和 ViewController2)
      2. 向 ViewController1 添加一个按钮,并从按钮向 ViewController2 添加一个动作 segue
      3. 向 ViewControler2 添加一个子视图,并向该子视图添加一个 IBOutlet
      4. 在 ViewController1 的 prepareForSegue: 中,尝试引用 ViewController2 的子视图出口 - 您会发现它为 nil,并且其框架/边界为 null。

      这是因为 ViewController2 的视图还没有被添加到视图栈中,但是控制器已经被初始化了。因此,您应该永远尝试在 prepareForSegue: 中操作 ViewController2 的视图,否则您所做的任何事情都会丢失。在此处参考 Apple 的 ViewController Programming Guide 了解生命周期:https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html

      这里接受的答案是强制 loadView 在 prepareForSegue 中运行:通过访问目标的 .view 属性,这就是事情出现乱序的原因,如果您尝试在 viewDidLoad 中进行任何类型的视图操作以解决任何数据负载,则具有未知/难以重现的结果,因为没有将视图加载到 viewStack,对框架引用的父视图的任何引用都将为零.

      TL;DR; - 如果您有像 OP 一样需要传递的数据,请使用目标上的公共属性设置它,然后在目标控制器的 viewDidLoad 中,将该数据加载到视图的子视图中。

      编辑:

      这里有类似的问题 - IBOutlet is nil inside custom UIView (Using STORYBOARD)

      您可能还想利用 viewDidLayoutSubviews: 进行任何子视图操作。

      【讨论】:

      • 我同意这一点。调用 prepareForSegue 时,仅实例化视图控制器 - 稍后创建视图层次结构。因此,使用属性来传达任何特定于视图的更改是理想的。如果我们遵循拇指规则“视图元素在对象内部并且不应该在 .m 文件之外可见”会更容易
      • 正确。这为我省去了很多麻烦。在我的情况下,我需要在目标视图控制器上加载几个 UITextView。由于您描述的所有原因,这不起作用。添加几个 NSString 属性并加载它们,然后使用它们在 viewDidLoad 方法中加载 UITextViews 是解决方案。谢谢!
      【解决方案3】:

      secondViewController = 目的地视图控制器;

      另一种方法是在 secondViewController 中声明委托方法。 在 secondViewController 上:

      @protocol SecondViewControllerDelegate <NSObject>
      @optional
      - (void) InitilizeSecondViewController:(SecondViewController*) listVC;
      @end
      

      在firstViewController中实现,在该方法中使用secondViewController的视图子类的属性。 在 firstViewController 上:

      - (void) InitilizeSecondViewController:(SecondViewController*) listVC
      {
          listVC.btn0.textLabel.text = @"Aha";
      }
      

      将 firstViewController 初始化为 prepareForSegue 中 secondViewController 的委托。 在 firstViewController 上:

      - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
      {
          SecondViewController* list = segue.destinationViewController;
          list.delegate = self;
      }
      

      在 secondViewController 中的 viewDidLoad 方法之后调用此委托方法。 在 secondViewController 上:

      - (void)viewDidLoad
      {
          [super viewDidLoad];
          [delegate InitilizeSecondViewController:self];
      }
      

      【讨论】:

        【解决方案4】:

        我最近也遇到了同样的问题。但是当我一步步调试它时,我找到了一个可能的原因。 (对不起,我也是Objective C的新手,所以我下面的解释可能不是那么准确和专业......我之前的背景主要是Web开发。)

        如果你在你调用的那行之后设置一个断点

        testViewController *viewController = segue.destinationViewController;

        当你构建并运行项目时,你会发现destinationViewController中的UITextField属性并没有在断点处分配和启动(内存为0x0)。同时 NSString 属性已经分配和初始化(所以你可以设置它的值)。

        我认为 UITextfield 可能是一个子视图,因此它仅在其父视图(目标视图)启动时才启动。但是 NSString 是一个不与任何视图关联的属性,所以它是由声明它的视图控制器分配和初始化的。

        当我对此进行进一步测试时,我发现了一件非常有趣的事情:第二个视图是在 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 运行期间加载的。我做了如下测试代码:

        在第一个视图控制器.m文件中:

        - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
            NSLog(@"1. %@, %@",[segue identifier],segue.destinationViewController);
        
            Scene2Controller *scene2ViewController = [segue destinationViewController];
            [txtScene2 resignFirstResponder];
        
            NSLog(@"2. scene2ViewController: %@", scene2ViewController);
            NSLog(@"3. txtScene1: %@; passValue: %@", [scene2ViewController txtScene1], scene2ViewController.passValue);
            NSLog(@"4. View2: %@; passValue: %@", [scene2ViewController view], scene2ViewController.passValue);
            NSLog(@"5. txtScene1: %@; passValue: %@", [scene2ViewController txtScene1], scene2ViewController.passValue);
        
            //txtScene1 is the UITextfield property I declared in the second view controller
            //txtScene2 is the UITextfield property I declared in the first view controller
            //passValue is the NSString property I declared in the second view controller 
        
        }
        

        在第二个视图控制器.m文件中:

        - (void)viewDidLoad
        {
            NSLog(@"6. txtScene1: %@; passValue: %@", txtScene1,passValue);
        
        
            [super viewDidLoad];
        
        }
        

        注意到我在 NSLog 消息之前添加了序列号。我发现最终的日志结果序列是 1,2,3,6,4,5 而不是 1,2,3,4,5,6。并且在日志3中,txtScene1结果为null(未启动),但是在日志4(第二个视图加载完毕)之后,在日志5中,txtScene1结果为NOT null并且已经启动。这表明在 segue 执行期间加载了第二个视图。所以我猜想在 segue 转换期间,第二个视图控制器对象的启动顺序是:第二个视图控制器 -> NSString 属性(以及其他类似的属性,如 NSInteger 等) -> 第二个视图 -> UITextfield 属性(和其他子视图属性)。

        所以我在第一个视图控制器 .m 文件中更改了我的代码,如下所示:

        - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
        
            Scene2Controller *scene2ViewController = [segue destinationViewController];
            [txtScene2 resignFirstResponder];
        
            if ([scene2ViewController view]) {
                if ([txtScene2.text isEqualToString:@""]) {
                    scene2ViewController.txtScene1.text = @"No Value";
                } else {
                    scene2ViewController.txtScene1.text = txtScene2.text;
                }
            }
        
        }
        

        然后这段代码可以正常工作,并且值会直接传递给第二个视图中的 UITextfield 属性。

        希望上面的解释清楚,对你有帮助。

        【讨论】:

        • 我认为这是有道理的,因为我尝试通过加载视图来测试它并直接在 prepareForSegue 中设置属性并且它有效,谢谢!
        • 投反对票,因为这在技术上是不正确的建议 - 请参阅下面我的回答,了解此处实际发生的情况,以及为什么 prepareForSegue 视图操作是一个坏主意™
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多