Проблемы с передачей данных в DetailViewController из TableView в iOS5

Я был бы признателен за любую помощь в решении моей проблемы, из-за которой я рвал на себе волосы в последний день!

Отказ от ответственности 1 Я новичок в Objective-C, поэтому ответ на этот вопрос может быть очень простым (у меня многолетний опыт работы с PHP). Пожалуйста будь вежлив.

Отказ от ответственности 2 Да, я много гуглил и просматривал документацию Apple, но не смог понять свою проблему.

Сводка

Я пытаюсь передать значение, выбранное в табличном представлении, другому контроллеру из синглтона. Я обнаружил, что метод viewDidLoad во втором контроллере представления завершается раньше, чем метод tableView: didSelectRowAtIndexPath:, который инициировал переход. Это приводит к «устаревшим» данным доступа к контроллеру второго представления.

Подробности

Я создал проект с очень простой StoryBoard, используя ARC для iOS5.

Раскадровка

Я создал синглтон для хранения NSString между контроллерами представления.

Singleton.h

#import <Foundation/Foundation.h>
@interface Singleton : NSObject
@property (strong, nonatomic) NSString *sharedString;
+ (id)singletonManager;
@end

Singleton.m

#import "Singleton.h"

static Singleton *sharedInstance = nil;

    @implementation Singleton
    @synthesize sharedString = _sharedString;
    +(id)singletonManager
    {
        @synchronized(self){
            if (sharedInstance == nil)
                sharedInstance = [[self alloc] init];
        }
        return sharedInstance;
    }
    -(id) init {
        if (self = [super init]) {
            _sharedString = [[NSString alloc] initWithString:@"Initialization String"];
        }
        return self;
    }

    @end

TableView в раскадровке связан со следующим TableViewController

TableViewController.h

#import <UIKit/UIKit.h>

@interface TableViewController : UITableViewController
@end

TableViewController.m

#import "TableViewController.h"
#import "Singleton.h"

@interface TableViewController ()
@property NSArray *tableData;
@end

@implementation TableViewController
@synthesize tableData;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
    }
    return self;
}

- (void)viewDidLoad
{

    [super viewDidLoad];
    self.tableData = [NSArray arrayWithObjects:@"First", @"Second", @"Third", nil];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [self.tableData count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Configure the cell...
    cell.textLabel.text = [self.tableData objectAtIndex:indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{    
    NSLog(@"TableViewController: didSelectRowAtIndexPath start");
    Singleton *sharedInstance = [Singleton singletonManager];
    sharedInstance.sharedString = [self.tableData objectAtIndex:indexPath.row];
    NSLog(@"TableViewController: Finished storing data into shared string: %@", sharedInstance.sharedString);
    NSLog(@"TableViewController: didSelectRowAtIndexPath start");
}

@end

Ячейка таблицы переходит (push) к базовому ViewController с одной меткой.

DetailViewController.h

#import <UIKit/UIKit.h>

@interface DetailViewController : UIViewController
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;

@end

DetailViewController.m

#import "DetailViewController.h"
#import "Singleton.h"

@interface DetailViewController ()

@end

@implementation DetailViewController
@synthesize displayLabel = _displayLabel;

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"DetailViewController: viewDidLoad start");
    Singleton *sharedInstance = [Singleton singletonManager];
    self.displayLabel.text = sharedInstance.sharedString;
    NSLog(@"displayLabel.text: %@", self.displayLabel.text);
    NSLog(@"DetailViewController: viewDidLoad end");
}

- (void)viewDidUnload
{
    [self setDisplayLabel:nil];
    [super viewDidUnload];
}

@end

Когда выбрана ячейка таблицы, я хочу

  • TableViewController для установки значения в общем сингле NSString
  • чтобы начать переход к подробному просмотру
  • DetailViewController, чтобы получить значение в общем сингле NSString и отобразить его в метке
  • Детальный вид для отображения на экране

К сожалению, это не работает должным образом с моим кодом. Если я посмотрю на детали NSLog в консоли, то увижу, что функция viewDidLoad подробного представления на самом деле завершается до запуска метода табличного представления (didSelectRowAtIndexPath).

2012-05-10 23:17:45.756 TestSingleton[12105:f803] DetailViewController: viewDidLoad start
2012-05-10 23:17:45.758 TestSingleton[12105:f803] displayLabel.text: Initialization String
2012-05-10 23:17:45.759 TestSingleton[12105:f803] DetailViewController: viewDidLoad end
2012-05-10 23:17:45.763 TestSingleton[12105:f803] TableViewController: didSelectRowAtIndexPath start
2012-05-10 23:17:45.764 TestSingleton[12105:f803] TableViewController: Finished storing data into shared string: First
2012-05-10 23:17:45.765 TestSingleton[12105:f803] TableViewController: didSelectRowAtIndexPath start

Арррррррррррр!

Если я помещу настройку текста метки в метод viewDidAppear подробного представления, будет отображаться правильное значение из общего синглтона NSString. Однако при таком подходе вы можете фактически увидеть, как приложение меняет значение текстовой метки (например, если вы нажмете на первую строку, когда загрузится экран подробного просмотра, вы увидите изменение метки с «Строка инициализации» на «Первый ").

Может кто-нибудь объяснить, как я могу передать общее значение из синглтона в другое представление и сделать его доступным для второго контроллера представления до того, как представление будет фактически отображено на экране.

Если потребуются какие-либо разъяснения - дайте мне знать.

Я уверен, что это как-то связано с моей неспособностью оценить тонкости жизненного цикла представления.

ОБНОВЛЕНИЕ

Благодаря *8vius.

Мой новый метод в TableViewController.m, который решает мою проблему:

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([segue.identifier isEqualToString:@"mySegue"]) {
        NSLog(@"Preparing for Segue to detail view");
        Singleton *sharedInstance = [Singleton singletonManager];
        NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];
        sharedInstance.sharedString = [self.tableData objectAtIndex:selectedRow.row];
        NSLog(@"TableViewController: Finished storing data into shared string: %@", sharedInstance.sharedString);

    }
}

person So Over It    schedule 10.05.2012    source источник


Ответы (1)


В целевом контроллере представления настройте свойство (общедоступное), чтобы вы могли установить значение, которое хотите передать. Реализуйте prepareForSegue в исходном контроллере представления и импортируйте заголовок целевого контроллера представления.

Чтобы получить indexPath ячейки табличного представления, у вас должен быть установлен IBOutlet для вашего табличного представления, таким образом вы можете получить indexPath, используя метод indexPathForCell в вашем методе prepareForSegue.

В вашем prepareForSegue у вас должно быть что-то вроде этого:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
  if ([segue.identifier isEqualToString:@"[identifier for your segue]"]) {
     [segue.destinationViewController setValueIWantToSet:value];
  }
}

Вы также можете установить значение текста метки здесь, если хотите.

Причина, по которой ваш viewDidLoad завершается раньше, заключается в том, что iOS настраивает целевой контроллер представления перед переходом к нему, использование метода prepareForSegue гарантирует, что ваш целевой контроллер представления уже создан и настроен, поэтому вы можете установить желаемые значения.

person 8vius    schedule 10.05.2012
comment
Спасибо. При тестировании я вижу, что prepareForSegue запускается до viewDidLoad подробного представления. Я все еще не на 100% понимаю, как передать выбранный indexPath с помощью prepareForSegue. - person So Over It; 10.05.2012
comment
Спасибо 8vius. Вопрос обновлен моим окончательным решением, основанным на вашем предложении. - person So Over It; 10.05.2012
comment
Не вставляйте код в комментарии, просто отредактируйте свой вопрос. Если вы хотите передать путь индекса, это будет то же самое, настройте свойство для получения значения пути индекса в заголовке вашего целевого контроллера представления и установите его из метода prepareForSegue. Отправителем в методе будет выбранная ячейка, поэтому вы можете получить из нее индексный путь. Для этого вы должны отправить отправителя в UITableViewCell, я отредактирую свой ответ, чтобы отразить это. - person 8vius; 10.05.2012
comment
Еще одно предложение для вас, если вы начинаете с iOS. В iTunes вы можете бесплатно загрузить курс по разработке приложений для iPad и iPhone от Стэнфордского университета, это очень поможет вам начать работу. - person 8vius; 10.05.2012
comment
Действительно полезно. я могу добиться этого с помощью didSelectRowAtIndexPath? - person Kishor Kundan; 11.09.2012
comment
Я полагаю, что есть свойство PresentViewController, которое вы можете вызвать в контроллере представления, которое будет похоже на целевое представлениеViewController. Вы не используете раскадровки? Вы создаете контроллер в коде? - person 8vius; 11.09.2012