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: Виклики подій у кількох контролерах перегляду з потоку в додатку

РЕДАГУВАТИ:

Якщо ви хочете передати кілька аргументів, код перед відхиленням виглядає так:

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
}

Відповідно до Посібника з програмування View Controller від Apple для iOS, другийViewController слід відхилити у представленому контролері перегляду, а не в представленому.
Майкл

Здається, ви не встановили делегат UITableView. Не могли б ви опублікувати це як запитання з кодом, який ви маєте, і обвести назад? Можливо, я зможу вам допомогти.
Сід

1
@Michael Документація говорить, що виклик dismiss on self перенаправляє виклик на контролер подання представлення. Крім того, дзвінок на self є чистішим, оскільки таким чином вам не потрібно турбуватися про перемикання між presentingViewController та 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, це набагато простіше / чистіше, ніж протоколи. Блоки чудові і їх потрібно охоплювати якомога більше.

EDIT - покращений приклад

Скажімо, у нас є mainVC, який ми хочемо представити modalVC поверх тимчасово, щоб отримати якийсь вхід від користувача. Для того, щоб представити цей modalVC від mainVC, нам потрібно виділити / ініціювати його всередині mainVC. Досить базові речі. Ну, коли ми робимо цей об'єкт modalVC, ми також можемо встановити для нього властивість блоку, що дозволяє нам легко спілкуватися між обома об'єктами 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. Основна різниця Я бачу, що виділення vc тепер є інстанційним потоком розкадровки. EDIT І я представляв до створення блоку. Виправлено.
malaki1974

2
Я згоден з вами :) Я відповів на це досить давно. Тепер я перемикаюся між використанням блоків / протоколів залежно від використання. Побачивши, що ця тема все ще досить активна донині, я повинен, колись, відредагувати свою відповідь, включивши блоки.
Сід

1
Цю відповідь потрібно прийняти, оскільки це забезпечує найбільш інтуїтивне рішення.
Сукіта Удугамасоорія

2
З двох відповідних відповідей це найкраща!
kygcoleman

1
Це, безумовно, мій найкращий метод. Тоді це швидко робиться із закриттям. Набагато краще, ніж делегатів та сповіщень, тому що вам не потрібно вказувати протоколи або ці "потворні" константи сповіщень. Якщо ви зробите ім'я змінної у представленому віртуальному програмному забезпеченні, яке добре закриває, це може бути дуже інтуїтивний код, наприклад. Vc.didCancel, vc.didFinish ... Ви можете встановити їх у prepaForSegue для vc, який його представляє (якщо ви використовуєте segues).
HixField

4

хм, шукайте центр сповіщень і передайте інформацію в сповіщення. ось яблука беруться за це - я сприймаю цей підхід особисто, якщо у когось немає інших пропозицій


Посилання насправді надмірно ускладнює це, потрібно лише спостерігач (перший контролер перегляду) і надіслати повідомлення від другого. Ви можете призначити селектори повідомленню, а також отримати інформацію, надіслану назад через сповіщення.
theiOSDude

2

Визначте протокол делегата у другому контролері подання та зробіть перший протоколом делегата другого.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.