dismissModalViewController И передать данные обратно

84

У меня есть два контроллера представления, firstViewController и secondViewController . Я использую этот код для переключения на свой secondViewController (я также передаю ему строку):

secondViewController *second = [[secondViewController alloc] initWithNibName:nil bundle:nil];

second.myString = @"This text is passed from firstViewController!";

second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;

[self presentModalViewController:second animated:YES];

[second release];

Затем я использую этот код во secondViewController, чтобы вернуться к firstViewController:

[self dismissModalViewControllerAnimated:YES];

Все это прекрасно работает. Мой вопрос: как передать данные в firstViewController? Я хотел бы передать в firstViewController другую строку из secondViewController.

Эндрю Дэвис
источник

Ответы:

142

Вам нужно использовать протоколы делегатов ... Вот как это сделать:

Объявите протокол в заголовочном файле вашего secondViewController. Должно получиться так:

#import <UIKit/UIKit.h>

@protocol SecondDelegate <NSObject>
-(void)secondViewControllerDismissed:(NSString *)stringForFirst
@end


@interface SecondViewController : UIViewController
{
    id myDelegate;  
}

@property (nonatomic, assign) id<SecondDelegate>    myDelegate;

Не забудьте синтезировать myDelegate в файле своей реализации (SecondViewController.m):

@synthesize myDelegate;

В заголовочном файле FirstViewController подпишитесь на протокол SecondDelegate, выполнив следующие действия:

#import "SecondViewController.h"

@interface FirstViewController:UIViewController <SecondDelegate>

Теперь, когда вы создаете экземпляр SecondViewController в FirstViewController, вы должны сделать следующее:

// If you're using a view controller built with Interface Builder.
SecondViewController *second = [[SecondViewController alloc] initWithNibName:"SecondViewController" bundle:[NSBundle mainBundle]];
// If you're using a view controller built programmatically.
SecondViewController *second = [SecondViewController new]; // Convenience initializer that uses alloc] init]
second.myString = @"This text is passed from firstViewController!";
second.myDelegate = self;
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];

Наконец, в файле реализации для вашего первого контроллера представления (FirstViewController.m) реализуйте метод SecondDelegate для secondViewControllerDismissed:

- (void)secondViewControllerDismissed:(NSString *)stringForFirst
{
    NSString *thisIsTheDesiredString = stringForFirst; //And there you have it.....
}

Теперь, когда вы собираетесь отклонить второй контроллер представления, вы хотите вызвать метод, реализованный в первом контроллере представления. Эта часть проста. Все, что вам нужно сделать, это добавить в свой второй контроллер представления некоторый код перед кодом отклонения:

if([self.myDelegate respondsToSelector:@selector(secondViewControllerDismissed:)])
{
    [self.myDelegate secondViewControllerDismissed:@"THIS IS THE STRING TO SEND!!!"];
}
[self dismissModalViewControllerAnimated:YES];

Протоколы делегатов ЧРЕЗВЫЧАЙНО, КРАЙНЕ, ЧРЕЗВЫЧАЙНО полезны. Вам стоит с ними ознакомиться :)

NSNotifications - еще один способ сделать это, но я предпочитаю использовать его, когда хочу обмениваться данными между несколькими viewController'ами или объектами. Вот ответ, который я опубликовал ранее, если вам интересно использовать NSNotifications: событий через несколько контроллеров представления из потока в appdelegate

РЕДАКТИРОВАТЬ:

Если вы хотите передать несколько аргументов, код перед отклонением выглядит следующим образом:

if([self.myDelegate respondsToSelector:@selector(secondViewControllerDismissed:argument2:argument3:)])
{
    [self.myDelegate secondViewControllerDismissed:@"THIS IS THE STRING TO SEND!!!" argument2:someObject argument3:anotherObject];
}
[self dismissModalViewControllerAnimated:YES];

Это означает, что ваша реализация метода SecondDelegate внутри вашего firstViewController теперь будет выглядеть так:

- (void) secondViewControllerDismissed:(NSString*)stringForFirst argument2:(NSObject*)inObject1 argument3:(NSObject*)inObject2
{
    NSString thisIsTheDesiredString = stringForFirst;
    NSObject desiredObject1 = inObject1;
    //....and so on
}
Сид
источник
Согласно руководству по программированию контроллера представления Apple для iOS, второй контроллер представления должен быть отклонен в представленном контроллере представления, а не в представленном.
Майкл
Похоже, вы не установили делегата UITableView. Не могли бы вы опубликовать это как вопрос с имеющимся у вас кодом и обвести его обратно? Возможно, я смогу вам помочь.
Сид
1
@Michael В документации говорится, что при вызове dismiss on self перенаправляется вызов контроллеру представления представления. Кроме того, вызов self становится более чистым, так как вам не нужно беспокоиться о переключении между представлениемViewController и parentViewController в зависимости от целевой версии iOS (5 или более ранней).
Сид,
1
@Resty Согласен; блоки удивительно полезны. В какой-то момент я подумывал об изменении этого ответа на поддержку блоков. Однако в этом случае я оставил ответ делегата видимым, потому что он дает нам немного больше свободы для управления объектами, которые могут быть переданы в модальное окно. Я просто ленив и скоро обновлю этот ответ, чтобы использовать блоки :)
Сид,
1
@sid спасибо братан, это работает для меня, но вам немного нужно изменить. как много вещей изменилось. пожалуйста, отредактируйте его
ChenSmile
40

Я мог бы быть здесь неуместным, но я начинаю предпочитать блочный синтаксис очень подробному подходу делегата / протокола. Если вы делаете vc2 из vc1, у vc2 есть свойство, которое вы можете установить из vc1, то есть блок!

@property (nonatomic, copy) void (^somethingHappenedInVC2)(NSString *response);

Затем, когда что-то происходит в vc2, о чем вы хотите сообщить vc1, просто выполните блок, который вы определили в vc1!

self.somethingHappenedInVC2(@"Hello!");

Это позволяет отправлять данные из vc2 обратно в vc1. Прямо как по волшебству. IMO, это намного проще / чище, чем протоколы. Блоки - это круто, и их нужно принимать как можно больше.

РЕДАКТИРОВАТЬ - Улучшенный пример

Допустим, у нас есть mainVC, над которым мы хотим временно представить модальныйVC, чтобы получить некоторые данные от пользователя. Чтобы представить этот модальныйVC из mainVC, нам нужно выделить / инициализировать его внутри mainVC. Довольно простые вещи. Когда мы создаем этот модальный объект VC, мы также можем установить для него свойство блока, которое позволяет нам легко взаимодействовать между обоими объектами vc. Итак, возьмем приведенный выше пример и поместим свойство follwing в файл .h modalVC:

 @property (nonatomic, copy) void (^somethingHappenedInModalVC)(NSString *response);  

Затем в нашем mainVC, после того, как мы разместили / инициализировали новый объект modalVC, вы устанавливаете свойство блока modalVC следующим образом:

ModalVC *modalVC = [[ModalVC alloc] init];
modalVC.somethingHappenedInModalVC = ^(NSString *response) {
     NSLog(@"Something was selected in the modalVC, and this is what it was:%@", response);
}

Итак, мы просто устанавливаем свойство блока и определяем, что происходит при выполнении этого блока.

Наконец, в нашем modalVC мы могли бы иметь tableViewController, поддерживаемый массивом строк dataSource. После выбора строки мы можем сделать что-то вроде этого:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
      NSString *selectedString = self.dataSource[indexPath.row];
      self.somethingHappenedInModalVC(selectedString);
 }

И, конечно же, каждый раз, когда мы выбираем строку в modalVC, мы собираемся получить вывод консоли из нашей строки NSLog обратно в mainVC. Надеюсь, это поможет!

ЛИЦЦА
источник
1
Должно ли это работать при использовании раскадровки? Сейчас у меня это не работает. Просто завершает работу с ошибкой lldb. Основной diff. Я вижу, что выделение vc теперь является экземпляром потока раскадровки. ИЗМЕНИТЬ И я представлял перед созданием блока. Исправлена.
malaki1974
2
Я согласен с вами :) Я опубликовал свой ответ довольно давно. Теперь я переключаюсь между использованием блоков / протоколов в зависимости от использования. Поскольку эта ветка все еще довольно активна по сей день, я должен в какой-то момент отредактировать свой ответ, чтобы включить блоки.
Сид
1
Этот ответ необходимо принять, поскольку он дает наиболее интуитивно понятное решение.
Сукита Удугамасурия
2
Из двух подходящих ответов это лучший!
kygcoleman 07
1
Это, безусловно, мой предпочтительный метод. Затем быстро это достигается закрытием. Намного лучше, чем делегаты и уведомления, потому что вам не нужно указывать протоколы или эти «уродливые» константы уведомлений. Если вы сделаете имя переменной в представленном vc, которая содержит закрытие, красивым, это может быть очень интуитивно понятный код, например. Vc.didCancel, vc.didFinish ... Вы можете установить их в файле prepareForSegue для виртуального канала, который его представляет (если вы используете сегменты).
HixField
4

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

theiOSЧувак
источник
Ссылка на самом деле слишком усложняет его, все, что вам нужно, это наблюдатель (первый контроллер представления) и отправить уведомление со второго. Вы можете назначить селекторы для уведомления и также получать информацию, отправленную обратно через уведомление.
theiOSDude 01
2

Определите протокол делегата во втором контроллере представления и сделайте первый делегатом второго.

cschwarz
источник