Свойства IBOutlet не обновляются при использовании метода prepareForSegue - PullRequest
4 голосов
/ 07 ноября 2011

У меня проблема с передачей значения в свойство IBOutlet destinationViewController, но оно нормально работает с обычным свойством, см. Код ниже

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

Я уже синтезирую оба свойства.

Спасибо за помощь.

Ответы [ 4 ]

15 голосов
/ 25 августа 2013

Я немного опаздываю с ответом, но недавно столкнулся с этой проблемой, и причина этого была бы очевидна, если бы мы по-прежнему создавали вещи вручную, а не позволяли раскадровке справиться с этим. Это происходит по той же причине, по которой вы никогда не манипулируете представлением при создании экземпляров контроллеров представлений вручную: у целевого объекта targetViewController не было вызвано loadView: пока что в раскадровке выполняется десериализация всех просмотр объектов со связанного пера.

Очень простой способ увидеть это в действии:

  1. Создание двух сцен ViewController (ViewController1 и ViewController2)
  2. Добавление кнопки в ViewController1 и переход от действия кнопки к ViewController2
  3. Добавление подпредставления для ViewControler2 и IBOutlet к этому подпредставлению
  4. В prepareForSegue: of ViewController1 попытайтесь сослаться на этот выход subview ViewController2 - вы обнаружите, что он равен нулю, а его рамки / границы равны нулю.

Это потому, что представление ViewController2 еще не было добавлено в стек просмотра, но контроллер был инициализирован. Таким образом, вы должны никогда пытаться манипулировать представлением ViewController2 в prepareForSegue: иначе все, что вы делаете, будет потеряно. Ссылка на руководство по программированию Apple ViewController здесь для жизненного цикла: https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html

Принятый ответ здесь - принудительно запустить loadView в prepareForSegue: путем доступа к свойству .view пункта назначения, из-за чего вещи появляются не по порядку, и будет неизвестным / трудно воспроизвести результаты, если вы пытаетесь выполнить какие-либо манипуляции с представлением в viewDidLoad для учета любых загрузок данных, потому что без загрузки представления в viewStack любые ссылки на родительское представление для ссылок на фреймы будут равны нулю.

TL; DR; - Если у вас есть данные, которые нужно передать, как в случае с OP, установите их, используя открытые свойства в месте назначения, а затем в viewDidLoad контроллера назначения загрузите эти данные в подпредставления представления.

Edit:

Подобный вопрос здесь - IBOutlet равен нулю внутри пользовательского UIView (с помощью STORYBOARD)

Вы также можете использовать viewDidLayoutSubviews: для любых манипуляций с подвидом.

10 голосов
/ 11 ноября 2011

Я только что получил ту же проблему в последнее время. Но когда я отлаживаю шаг за шагом, я нахожу возможную причину. (Извините, я также новичок в Objective C, поэтому моё следующее объяснение может быть не таким точным и профессиональным ... Мой предыдущий опыт в основном был веб-разработкой.)

Если вы установите точку останова сразу после линии, которую вы вызываете

testViewController *viewController = segue.destinationViewController;

когда вы создадите и запустите проект, вы обнаружите, что свойство UITextField в destinationViewController не выделяется и не инициируется (память 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 был нулевым (не инициирован), но после журнала 4 (было загружено второе представление), в журнале 5 txtScene1 НЕ был нулевым и был инициирован. Это предполагает, что второй вид был загружен во время выполнения 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 во втором представлении.

Надеюсь, что приведенное выше объяснение понятно и полезно для вас.

2 голосов
/ 07 августа 2014

Метод 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; 
}
0 голосов
/ 08 сентября 2012

secondViewController = destinationViewController;

Другой способ - объявить метод делегата в secondViewController. на втором ViewController:

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

Реализованный в firstViewController, используйте свойство подкласса представления secondViewController в этом методе. на firstViewController:

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

Инициализируйте firstViewController как делегата secondViewController в prepareForSegue. на firstViewController:

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

вызовите этот метод делегата после метода viewDidLoad в secondViewController. на втором ViewController:

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