Передача данных между контроллерами представления

1371

Я новичок в iOS и Objective-C и во всей парадигме MVC и застрял в следующем:

У меня есть представление, которое действует как форма ввода данных, и я хочу дать пользователю возможность выбрать несколько продуктов. Продукты перечислены в другом окне, UITableViewControllerи я включил несколько вариантов выбора.

У меня вопрос, как мне перенести данные из одного представления в другое? Я буду удерживать выборки UITableViewв массиве, но как мне потом передать это обратно в предыдущее представление формы ввода данных, чтобы его можно было сохранить вместе с другими данными в Core Data при отправке формы?

Я просмотрел и видел, как некоторые люди объявляют массив в делегате приложения. Я читаю кое-что о Singletons, но не понимаю, что это такое, и я читаю кое-что о создании модели данных.

Каков будет правильный способ выполнения этого и как я буду это делать?

Мэтт Прайс
источник

Ответы:

1683

Этот вопрос, кажется, очень популярен здесь на stackoverflow, поэтому я подумал, что постараюсь дать лучший ответ, чтобы помочь людям, начинающим в мире iOS, как я.

Я надеюсь, что этот ответ достаточно ясен для понимания людьми, и я ничего не пропустил.

Передача данных вперед

Передача данных в контроллер представления от другого контроллера представления. Вы должны использовать этот метод, если хотите передать объект / значение из одного контроллера представления в другой контроллер представления, который вы, возможно, помещаете в стек навигации.

Для этого примера мы будем иметь ViewControllerAиViewControllerB

Чтобы передать BOOLзначение из ViewControllerAв, ViewControllerBмы бы сделали следующее.

  1. в ViewControllerB.hсоздать свойство дляBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. в ViewControllerAвы должны сказать ему о ViewControllerBтак использовать

    #import "ViewControllerB.h"

    Тогда где вы хотите загрузить вид, например. didSelectRowAtIndexили некоторые из них, IBActionвам нужно установить свойство ViewControllerBперед тем, как поместить его в стек навигации.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.isSomethingEnabled = YES;
    [self pushViewController:viewControllerB animated:YES];
    

    Это позволит установить isSomethingEnabledв ViewControllerBк BOOLзначению YES.

Передача данных вперед с использованием сегментов

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

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

Таким образом, чтобы передать BOOLот ViewControllerAк ViewControllerBмы бы сделали следующее:

  1. в ViewControllerB.hсоздать свойство дляBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. в ViewControllerAвы должны сказать ему о ViewControllerBтак использовать

    #import "ViewControllerB.h"
  3. Создайте переход от ViewControllerAк ViewControllerBна раскадровке и дайте ему идентификатор, в этом примере мы назовем его"showDetailSegue"

  4. Затем нам нужно добавить метод к тому, ViewControllerAкоторый вызывается, когда выполняется любой переход, поэтому нам нужно определить, какой вызов вызывался, а затем что-то сделать. В нашем примере мы проверим, "showDetailSegue"и если это будет выполнено, мы передадим наше BOOLзначениеViewControllerB

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
            controller.isSomethingEnabled = YES;
        }
    }
    

    Если ваши представления встроены в контроллер навигации, вам нужно немного изменить описанный выше метод на следующий

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
            ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
            controller.isSomethingEnabled = YES;
        }
    }
    

    Это позволит установить isSomethingEnabledв ViewControllerBк BOOLзначению YES.

Передача данных назад

Для того, чтобы передать данные обратно из ViewControllerBк ViewControllerAВам необходимо использовать протоколы и делегат или блоки , последние может быть использован в качестве слабосвязанного механизма обратных вызовов.

Для этого мы сделаем ViewControllerAделегата ViewControllerB. Это позволяет ViewControllerBотправить сообщение обратно, чтобы ViewControllerAмы могли отправить данные обратно.

Чтобы ViewControllerAбыть его делегатом, ViewControllerBон должен соответствовать ViewControllerBпротоколу, который мы должны указать. Это говорит, ViewControllerAкакие методы он должен реализовать.

  1. В ViewControllerB.h, ниже #import, но выше @interfaceвы указываете протокол.

    @class ViewControllerB;
    
    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
    
  2. следующий еще в ViewControllerB.hвам нужно настроить delegateсвойство и синтезировать вViewControllerB.m

    @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. В ViewControllerBмы вызываем сообщение о том, delegateкогда мы выскакиваем контроллер представления.

    NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
    
  4. Вот и все ViewControllerB. Теперь ViewControllerA.h, скажи, ViewControllerAчтобы импортировать ViewControllerBи соответствовать его протоколу.

    #import "ViewControllerB.h"
    
    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
    
  5. В ViewControllerA.mреализации следующий метод из нашего протокола

    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
        NSLog(@"This was returned from ViewControllerB %@",item);
    }
    
  6. Прежде чем viewControllerBперейти к стеку навигации, мы должны сказать, ViewControllerBчто ViewControllerAэто его делегат, иначе мы получим ошибку.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.delegate = self
    [[self navigationController] pushViewController:viewControllerB animated:YES];
    

Ссылки

  1. Использование делегирования для связи с другими контроллерами представления в Руководстве по программированию контроллера представления
  2. Шаблон делегата

NSNotification Center Это еще один способ передачи данных.

// add observer in controller(s) where you want to receive data
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeepLinking:) name:@"handleDeepLinking" object:nil];

-(void) handleDeepLinking:(NSNotification *) notification {
    id someObject = notification.object // some custom object that was passed with notification fire.
}

// post notification
id someObject;
[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

Передача данных обратно из одного класса в другой (классом может быть любой контроллер, менеджер сети / сеанса, подкласс UIView или любой другой класс)

Блоки являются анонимными функциями.

В этом примере данные передаются с контроллера B на контроллер A

определить блок

@property void(^selectedVoucherBlock)(NSString *); // in ContollerA.h

Добавьте обработчик блока (слушатель), где вам нужно значение (например, вам нужен ответ API в ControllerA или вам нужны данные ContorllerB на A)

// in ContollerA.m

- (void)viewDidLoad {
    [super viewDidLoad];
    __unsafe_unretained typeof(self) weakSelf = self;
    self.selectedVoucherBlock = ^(NSString *voucher) {
        weakSelf->someLabel.text = voucher;
    };
}

Перейти к контроллеру B

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ControllerB *vc = [storyboard instantiateViewControllerWithIdentifier:@"ControllerB"];
vc.sourceVC = self;
    [self.navigationController pushViewController:vc animated:NO];

пожарный блок

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: 
(NSIndexPath *)indexPath {
    NSString *voucher = vouchersArray[indexPath.row];
    if (sourceVC.selectVoucherBlock) {
        sourceVC.selectVoucherBlock(voucher);
    }
    [self.navigationController popToViewController:sourceVC animated:YES];
}

Еще один рабочий пример для блоков

Мэтт Прайс
источник
24
Мы также должны поставить @class ViewControllerB;выше определение @protocol? Без этого я получаю ошибку «Ожидаемый тип» на ViewControllerB в строке: - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; в @protocolобъявлении
alan-p
4
Это прекрасно работает. Как говорит Алан-р, не забудьте написать @class ViewControllerB; выше протокола, в противном случае вы получите ошибку «Ожидается тип».
Эндрю Дэвис
6
вам не нужны делегаты для передачи назад, просто используйте размотку.
Малхал
4
Когда я ставлю "viewControllerB.delegate = self;" в ViewControllerB я получаю ошибку. Присваивая 'id <ViewControllerBDelegate>' из несовместимого типа 'ViewControllerB * const __strong', я не уверен, что делаю неправильно. Кто-нибудь может помочь? Плюс мне пришлось изменить: initWithNib -> initWithNibName.
uplearnedu.com
4
если вы используете, NavigationControllerвы должны использовать [self.navigationController pushViewController:viewController animated:YES];вместо этого[self pushViewController:viewControllerB animated:YES];
Назир
192

стриж

Существует множество объяснений здесь и вокруг StackOverflow, но если вы новичок, просто пытающийся получить что-то базовое для работы, попробуйте посмотреть это руководство на YouTube (именно это помогло мне наконец понять, как это сделать).

Передача данных на следующий View Controller

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

введите описание изображения здесь

Создайте макет раскадровки в Интерфейсном Разработчике. Чтобы начать, просто Controlнажмите на кнопку и перетащите на контроллер второго вида.

Контроллер первого вида

Код для первого контроллера представления

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // get a reference to the second view controller
        let secondViewController = segue.destination as! SecondViewController

        // set a variable in the second view controller with the String to pass
        secondViewController.receivedString = textField.text!
    }

}

Контроллер второго вида

И код для второго контроллера представления

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    // This variable will hold the data being passed from the First View Controller
    var receivedString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Used the text from the First View Controller to set the label
        label.text = receivedString
    }

}

Не забывай

  • Подключите розетки для UITextFieldи UILabel.
  • Установите первый и второй View Controllers для соответствующих файлов Swift в IB.

Передача данных обратно в предыдущий View Controller

Чтобы передать данные из второго контроллера представления в первый контроллер представления, вы используете протокол и делегат . Это видео представляет собой очень четкое описание этого процесса:

Ниже приведен пример, основанный на видео (с некоторыми изменениями).

введите описание изображения здесь

Создайте макет раскадровки в Интерфейсном Разработчике. Опять же, чтобы перейти к следующему этапу, вы просто Controlперетаскиваете кнопку на контроллер второго вида. Установите идентификатор segue в showSecondViewController. Кроме того, не забудьте подключить розетки и действия, используя имена в следующем коде.

Контроллер первого вида

Код для первого контроллера представления

import UIKit

class FirstViewController: UIViewController, DataEnteredDelegate {

    @IBOutlet weak var label: UILabel!

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showSecondViewController" {
            let secondViewController = segue.destination as! SecondViewController
            secondViewController.delegate = self
        }
    }

    func userDidEnterInformation(info: String) {
        label.text = info
    }
}

Обратите внимание на использование нашего пользовательского DataEnteredDelegateпротокола.

Контроллер второго вида и протокол

Код для второго контроллера вида

import UIKit

// protocol used for sending data back
protocol DataEnteredDelegate: AnyObject {
    func userDidEnterInformation(info: String)
}

class SecondViewController: UIViewController {

    // making this a weak variable so that it won't create a strong reference cycle
    weak var delegate: DataEnteredDelegate? = nil

    @IBOutlet weak var textField: UITextField!

    @IBAction func sendTextBackButton(sender: AnyObject) {

        // call this method on whichever class implements our delegate protocol
        delegate?.userDidEnterInformation(info: textField.text!)

        // go back to the previous view controller
        _ = self.navigationController?.popViewController(animated: true)
    }
}

Обратите внимание, что за protocolпределами класса View Controller.

Вот и все. Запустив приложение сейчас, вы сможете отправить данные обратно со второго контроллера представления на первый.

Suragch
источник
Учитывая некоторые из последних обновлений Swift, это все еще распространенный шаблон для реализации?
Piofusco
4
Большинство всех обновлений Swift, которые я видел, были относительно небольшими синтаксическими изменениями, а не изменениями в том, как данные передаются между контроллерами представления. Если я узнаю о каких-либо серьезных изменениях, подобных этому, я обновлю свой ответ.
Сурагч
2
offtopic - у iOS такой уродливый способ передавать параметры новым контроллерам представлений, невероятно - вы должны устанавливать параметры не там, где вы делаете вызов, а в каком-то другом. У Android есть лучший подход в этом отношении - когда вы запускаете Activity, вы можете передавать любые данные (ну почти) через начальный Intent. Легко. Не надо кастовать или еще что Передача возвращаемых значений обратно вызывающей стороне также важна, нет необходимости делегировать. Конечно, можно использовать и уродливые подходы, там нет проблем))
Mixaz
1
@Himanshu, сначала получите ссылку на второй контроллер вида. Затем обновите открытую переменную, которую она содержит.
Сурагч
8
@Мед. Я думаю, что слово «делегат» сбивает с толку. Позвольте мне использовать слово «работник». «Работник» (контроллер первого вида) делает то, что ему говорит «босс» (контроллер второго вида). «Босс» не знает, кем будет его «работник»; это может быть кто угодно. Так что в первом контроллере представления (класс «работник») написано, что я буду вашим «работником». Вы говорите мне, что написать на этикетке, и я сделаю это для вас. Таким образом, secondViewController.delegate = selfозначает «Я согласен быть начальником». Посмотрите этот ответ для другого примера и большего объяснения.
Сураг
136

M в MVC - это «Модель», а в парадигме MVC роль классов моделей заключается в управлении данными программы. Модель является противоположностью представления - представление знает, как отображать данные, но ничего не знает о том, что делать с данными, в то время как модель знает все о том, как работать с данными, но ничего о том, как их отображать. Модели могут быть сложными, но это не обязательно - модель для вашего приложения может быть такой же простой, как массив строк или словарей.

Роль контроллера - посредник между представлением и моделью. Следовательно, им нужна ссылка на один или несколько объектов вида и один или несколько объектов модели. Допустим, ваша модель представляет собой массив словарей, каждый из которых представляет одну строку в вашей таблице. Корневое представление для вашего приложения отображает эту таблицу, и оно может отвечать за загрузку массива из файла. Когда пользователь решает добавить новую строку в таблицу, он нажимает какую-то кнопку, и ваш контроллер создает новый (изменяемый) словарь и добавляет его в массив. Чтобы заполнить строку, контроллер создает контроллер подробного представления и дает ему новый словарь. Контроллер подробного представления заполняет словарь и возвращает. Словарь уже является частью модели, поэтому больше ничего не должно произойти.

Калеб
источник
95

Существуют различные способы получения данных в другой класс в iOS. Например -

  1. Прямая инициализация после выделения другого класса.
  2. Делегирование - для передачи данных обратно
  3. Уведомление - для передачи данных нескольким классам одновременно
  4. Сохранение в NSUserDefaults- для доступа к нему позже
  5. Синглтон классы
  6. Базы данных и другие механизмы хранения, такие как plist и т. Д.

Но для простого сценария передачи значения другому классу, распределение которого выполняется в текущем классе, наиболее распространенным и предпочтительным методом будет прямая установка значений после распределения. Это делается следующим образом:-

Мы можем понять это, используя два контроллера - Controller1 и Controller2

Предположим, в классе Controller1 вы хотите создать объект Controller2 и передать его с передачей значения String. Это можно сделать так:

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj passValue:@"String"];
    [self pushViewController:obj animated:YES];
}

В реализации класса Controller2 будет эта функция, так как

@interface Controller2  : NSObject

@property (nonatomic , strong) NSString* stringPassed;

@end

@implementation Controller2

@synthesize stringPassed = _stringPassed;

- (void) passValue:(NSString *)value {

    _stringPassed = value; //or self.stringPassed = value
}

@end

Вы также можете напрямую установить свойства класса Controller2 следующим образом:

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj setStringPassed:@"String"];  
    [self pushViewController:obj animated:YES];
}

Для передачи нескольких значений вы можете использовать несколько параметров, таких как: -

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passValue:@“String1 andValues:objArray withDate:date]; 

Или, если вам нужно передать более 3 параметров, связанных с общей функцией, вы можете сохранить значения в классе Model и передать этот modelObject следующему классу.

ModelClass *modelObject = [[ModelClass alloc] init]; 
modelObject.property1 = _property1;
modelObject.property2 = _property2;
modelObject.property3 = _property3;

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passmodel: modelObject];

Короче говоря, если вы хотите -

1) set the private variables of the second class initialise the values by calling a custom function and passing the values.
2) setProperties do it by directlyInitialising it using the setter method.
3) pass more that 3-4 values related to each other in some manner , then create a model class and set values to its object and pass the object using any of the above process.

Надеюсь это поможет

borncrazy
источник
84

После дальнейших исследований выяснилось, что протоколы и делегаты - верный / предпочтительный способ Apple сделать это.

Я закончил тем, что использовал этот пример

Обмен данными между контроллерами представления и другими объектами @ iPhone Dev SDK

Работал нормально и позволял мне передавать строку и массив вперед и назад между моими представлениями.

Спасибо за вашу помощь

Мэтт Прайс
источник
3
не используйте протоколы и делегаты, просто используйте размотку.
Малхал
1
@malhal Что если вы не используете раскадровки ??
Эван Р
Я тоже ненавижу бесполезные протоколы и делегатов. @malhal
DawnSong
@EvanR Вы можете создавать и выполнять сегменты в коде. Все то же самое.
DawnSong 12.12.15
1
По сути весь QA на этой странице "от старых дней до просмотра контейнеров". Вы бы никогда через миллион лет не потрудились с протоколами или делегатами. Каждая мелочь, которую вы делаете на любом экране, в любом случае является представлением контейнера, поэтому вопрос действительно больше не существует - у вас уже есть все ссылки «вверх и вниз» из всех представлений контейнера.
Толстяк
66

Я нахожу самый простой и самый элегантный вариант с прохождением блоков. Давайте назовем контроллер представления, который ожидает возвращенные данные, как «A» и возвращающий контроллер представления как «B». В этом примере мы хотим получить 2 значения: первое из Type1 и второе из Type2.

Предполагая, что мы используем Storyboard, первый контроллер устанавливает блок обратного вызова, например, во время подготовки к следующему этапу:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController isKindOfClass:[BViewController class]])
    {
        BViewController *viewController = segue.destinationViewController;

        viewController.callback = ^(Type1 *value1, Type2 *value2) {
            // optionally, close B
            //[self.navigationController popViewControllerAnimated:YES];

            // let's do some action after with returned values
            action1(value1);
            action2(value2);
        };

    }
}

и контроллер представления "B" должен объявить свойство обратного вызова, BViewController.h:

// it is important to use "copy"
@property (copy) void(^callback)(Type1 *value1, Type2 *value2);

Затем в файле реализации BViewController.m после того, как мы получили желаемые значения для возврата нашего обратного вызова, необходимо вызвать:

if (self.callback)
    self.callback(value1, value2);

Следует помнить одну вещь: использование блока часто требует управления сильными и слабыми ссылками, как описано здесь.

Лешек Зарна
источник
Почему бы не быть параметром для блока обратного вызова, а не отдельным свойством?
Тимучин
56

Во многих из приведенных ответов содержится некоторая полезная информация, но ни один из них не дает полного ответа на этот вопрос.

Вопрос касается передачи информации между контроллерами представления. В приведенном конкретном примере запрашивается передача информации между представлениями, но с учетом самооценки новизны iOS первоначальный плакат, скорее всего, подразумевал между viewControllers, а не между представлениями (без какого-либо участия ViewControllers). Кажется, что все ответы сосредоточены на двух контроллерах представления, но что, если приложение развивается, чтобы задействовать более двух контроллеров представления в обмене информацией?

Оригинальный постер также спрашивал о Singletons и использовании AppDelegate . На эти вопросы нужно ответить.

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

Сценарии применения

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

Сценарий первый: максимум два контроллера представления когда-либо должны обмениваться информацией. Смотрите диаграмму один.

схема исходной задачи

В приложении есть два контроллера вида. Существует ViewControllerA (форма ввода данных) и View Controller B (список продуктов). Элементы, выбранные в списке продуктов, должны соответствовать элементам, отображаемым в текстовом поле в форме ввода данных. В этом сценарии ViewControllerA и ViewControllerB должны взаимодействовать напрямую друг с другом, а не с другими контроллерами представления.

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

схема применения домашнего инвентаря

В приложении есть четыре контроллера вида. Это приложение на основе вкладок для управления домашним инвентарем. Три контроллера представления представляют по-разному фильтрованные представления тех же данных:

  • ViewControllerA - Предметы роскоши
  • ViewControllerB - Не застрахованные предметы
  • ViewControllerC - Весь домашний инвентарь
  • ViewControllerD - Добавить форму нового элемента

Каждый раз, когда отдельный элемент создается или редактируется, он также должен синхронизироваться с другими контроллерами представления. Например, если мы добавляем лодку в ViewControllerD, но она еще не застрахована, тогда лодка должна появиться, когда пользователь перейдет к ViewControllerA (предметы роскоши), а также ViewControllerC (вся домашняя инвентаризация), но не когда пользователь перейдет к ViewControllerB (не застрахованные предметы). Нам нужно заботиться не только о добавлении новых элементов, но и об удалении элементов (которые могут быть разрешены с любого из четырех контроллеров представления) или редактировании существующих элементов (что можно разрешить из «Формы добавления нового элемента», с целью повторного использования того же самого). для редактирования).

Поскольку все контроллеры представления должны совместно использовать одни и те же данные, все четыре контроллера представления должны оставаться синхронизированными, и, следовательно, должна быть какая-то связь со всеми другими контроллерами представления, всякий раз, когда какой-либо один контроллер представления изменяет базовые данные. Должно быть совершенно очевидно, что мы не хотим, чтобы каждый контроллер представления взаимодействовал напрямую друг с другом в этом сценарии. В случае, если это не очевидно, рассмотрим, было ли у нас 20 различных контроллеров представления (а не только 4). Насколько сложно и подвержено ошибкам уведомлять каждый из 19 контроллеров представления каждый раз, когда один контроллер представления вносит изменения?

Решения: делегаты, шаблон наблюдателя и синглтоны

В первом сценарии у нас есть несколько жизнеспособных решений, так как другие ответы дали

  • перетекает
  • делегаты
  • установка свойств на контроллеры вида напрямую
  • NSUserDefaults (на самом деле плохой выбор)

Во втором сценарии у нас есть другие жизнеспособные решения:

  • Шаблон наблюдателя
  • Одиночки

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

+ (HouseholdInventoryManager*) sharedManager; {
    static dispatch_once_t onceQueue;
    static HouseholdInventoryManager* _sharedInstance;

    // dispatch_once is guaranteed to only be executed once in the
    // lifetime of the application
    dispatch_once(&onceQueue, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

Теперь, когда мы понимаем, что такое синглтон, давайте обсудим, как синглтон вписывается в схему наблюдателя. Шаблон наблюдателя используется для того, чтобы один объект реагировал на изменения другого объекта. Во втором сценарии у нас есть четыре разных контроллера представления, которые все хотят знать об изменениях в базовых данных. «Базовые данные» должны принадлежать одному экземпляру, одиночке. «Знать об изменениях» достигается путем наблюдения изменений, внесенных в синглтон.

У приложения домашней инвентаризации будет один экземпляр класса, который предназначен для управления списком предметов инвентаря. Менеджер будет управлять коллекцией предметов домашнего обихода. Ниже приведено определение класса для менеджера данных:

#import <Foundation/Foundation.h>

@class JGCHouseholdInventoryItem;

@interface HouseholdInventoryManager : NSObject
/*!
 The global singleton for accessing application data
 */
+ (HouseholdInventoryManager*) sharedManager;


- (NSArray *) entireHouseholdInventory;
- (NSArray *) luxuryItems;
- (NSArray *) nonInsuredItems;

- (void) addHouseholdItemToHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) editHouseholdItemInHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) deleteHoueholdItemFromHomeInventory:(JGCHouseholdInventoryItem*)item;
@end

Когда коллекция предметов домашнего инвентаря изменяется, контроллеры представления должны быть осведомлены об этом изменении. Приведенное выше определение класса не дает понять, как это будет происходить. Нам нужно следовать схеме наблюдателя. Контроллеры представления должны формально соблюдать sharedManager. Есть два способа наблюдать за другим объектом:

  • Key-Value-Observing (KVO)
  • NSNotificationCenter.

Во втором сценарии у нас нет ни одного свойства HouseholdInventoryManager, которое можно было бы наблюдать с помощью KVO. Поскольку у нас нет единственного свойства, которое легко наблюдать, шаблон наблюдателя в этом случае должен быть реализован с использованием NSNotificationCenter. Каждый из четырех контроллеров представления подписывается на уведомления, а sharedManager отправляет уведомления в центр уведомлений, когда это необходимо. Менеджеру инвентаря не нужно ничего знать о контроллерах представления или экземплярах каких-либо других классов, которые могут быть заинтересованы в знании, когда изменяется коллекция предметов инвентаря; NSNotificationCenter заботится об этих деталях реализации. Контроллеры представления просто подписываются на уведомления, а менеджер данных просто публикует уведомления.

Многие начинающие программисты пользуются тем, что в жизни приложения всегда есть ровно один делегат приложения, который доступен во всем мире. Начинающие программисты используют этот факт для помещения объектов и функций в appDelegate для удобства доступа из любой точки приложения. Тот факт, что AppDelegate является синглтоном, не означает, что он должен заменить все остальные синглтоны. Это плохая практика, поскольку она ложится слишком большим бременем на один класс, нарушая хорошие объектно-ориентированные практики. Каждый класс должен иметь четкую роль, которую легко объяснить, часто просто по названию класса.

Каждый раз, когда ваш Application Delegate начинает раздуваться, начинайте удалять функциональность в одиночку. Например, основной стек данных не следует оставлять в AppDelegate, вместо этого его следует поместить в его собственный класс, класс coreDataManager.

Ссылки

Джейсон Кросс
источник
41

В OP не упоминались контроллеры представлений, но в ответах так много ответов, что я хотел бы рассказать о том, что некоторые из новых функций LLVM позволяют упростить эту задачу при передаче данных из одного контроллера представления в другой, а затем получить некоторые результаты обратно.

Сегменты раскадровки, блоки ARC и LLVM делают это проще, чем когда-либо для меня. Некоторые ответы вышеупомянутых раскадровок и сегментов уже были, но все еще полагались на делегирование. Определение делегатов, безусловно, работает, но некоторым людям может быть легче передавать указатели или блоки кода.

С UINavigators и segues, есть простые способы передачи информации на подчиненный контроллер и получения информации обратно. ARC упрощает передачу указателей на объекты, производные от NSObjects, поэтому, если вы хотите, чтобы вспомогательный контроллер добавил / изменил / изменил некоторые данные для вас, передайте ему указатель на изменяемый экземпляр. Блоки облегчают прохождение действий, поэтому, если вы хотите, чтобы подчиненный контроллер вызывал действие на контроллере более высокого уровня, передайте ему блок. Вы определяете блок для принятия любого количества аргументов, которые имеют смысл для вас. Вы также можете спроектировать API для использования нескольких блоков, если это подходит лучше.

Вот два тривиальных примера клея Segue. Первый простой показывает один параметр, переданный для ввода, второй для вывода.

// Prepare the destination view controller by passing it the input we want it to work on
// and the results we will look at when the user has navigated back to this controller's view.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results
     // by virtue of both controllers having a pointer to the same object.
     andResults:self.resultsFromNextController];
}

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

// Prepare the destination view controller by passing it the input we want it to work on
// and the callback when it has done its work.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results.
     resultsBlock:^(id results) {
         // This callback could be as involved as you like.
         // It can use Grand Central Dispatch to have work done on another thread for example.
        [self setResultsFromNextController:results];
    }];
}
WeakPointer
источник
41

Передача данных из ViewController 2 (назначение) в viewController 1 (Source) является более интересной вещью. Предполагая, что вы используете storyBoard, это все, что я узнал:

  • делегат
  • уведомление
  • Пользователь по умолчанию
  • одиночка

Это уже обсуждалось здесь.

Я обнаружил, что есть больше способов:

-Использование блокировки обратных вызовов:

использовать его в prepareForSegueметоде в VC1

NextViewController *destinationVC = (NextViewController *) segue.destinationViewController;
[destinationVC setDidFinishUsingBlockCallback:^(NextViewController *destinationVC)
{
    self.blockLabel.text = destination.blockTextField.text;
}];

-Использование раскадровки Unwind (Выход)

Реализуйте метод с аргументом UIStoryboardSegue в VC 1, как этот:

-(IBAction)UnWindDone:(UIStoryboardSegue *)segue { }

В storyBoard подключите кнопку «Возврат» к зеленой кнопке «Выход» (раскрутить) видеомагнитофона. Теперь у вас есть переход, который «возвращается», поэтому вы можете использовать свойство destinationViewController в prepareForSegue в VC2 и изменить любое свойство VC1, прежде чем оно вернется.

  • Еще один вариант использования раскадровки Undwind (Exit) - вы можете использовать метод, который вы написали в VC1

    -(IBAction)UnWindDone:(UIStoryboardSegue *)segue {
        NextViewController *nextViewController = segue.sourceViewController;
        self.unwindLabel.text = nextViewController.unwindPropertyPass;
    } 

    А в prepareForSegue из VC1 вы можете изменить любое свойство, которым хотите поделиться.

В обоих вариантах раскрутки вы можете установить свойство тега кнопки и проверить его в prepareForSegue.

Надеюсь, я добавил что-то к обсуждению.

:) ура.

Yevgeni
источник
40

Существует несколько способов обмена данными.

  1. Вы всегда можете поделиться данными, используя NSUserDefaults. Установите значение, которым вы хотите поделиться в отношении ключа по вашему выбору, и получите значение, NSUserDefaultсвязанное с этим ключом, в следующем контроллере представления.

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
  2. Вы можете просто создать недвижимость в viewcontrollerA. Создайте объект viewcontrollerAв viewcontrollerBи назначьте желаемое значение этому свойству.

  3. Вы также можете создавать собственные делегаты для этого.

Анубрата Сантра
источник
30
Типичная цель NSUserDefaults состоит в том, чтобы хранить пользовательские настройки, которые сохраняются между выполнениями приложения, так что все, что хранится здесь, останется здесь, если явно не удалено. Это действительно плохая идея использовать эту функцию для передачи информации между контроллерами представления (или любыми другими объектами) в приложении.
Хосе Гонсалес
30

Если вы хотите передать данные с одного контроллера на другой, попробуйте этот код

FirstViewController.h

@property (nonatomic, retain) NSString *str;

SecondViewController.h

@property (nonatomic, retain) NSString *str1;

FirstViewController.m

- (void)viewDidLoad
   {
     // message for the second SecondViewController
     self.str = @"text message";

     [super viewDidLoad];
   }

-(IBAction)ButtonClicked
 {
   SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
   secondViewController.str1 = str;
  [self.navigationController pushViewController:secondViewController animated:YES];
 }
user2998756
источник
29

Это очень старый ответ, и это анти-шаблон, пожалуйста, используйте делегатов. Не используйте этот подход!

1. Создайте экземпляр первого View Controller во втором View Controller и установите его свойство @property (nonatomic,assign).

2. Назначьте SecondviewControllerэкземпляр этого контроллера представления.

2. Когда вы закончите операцию выбора, скопируйте массив в первый View Controller. Когда вы выгрузите SecondView, FirstView будет содержать данные массива.

Надеюсь это поможет.

kaar3k
источник
2
Я не верю, что это правильный путь, поскольку он создает очень жесткую связь между контроллерами представления. Не совсем придерживаюсь MVC.
Мэтт Прайс
1
Если вы хотите строго следовать MVC, использовать NSNotificationCenter методы могут быть вызваны из ViewControllerA в ViewControllerB, проверить это может помочь U
kaar3k
28

Я долго искал это решение, Atlast нашел его. Прежде всего объявите все объекты в вашем файле SecondViewController.h как

@interface SecondViewController: UIviewController 
{
    NSMutableArray *myAray;
    CustomObject *object;
}

Теперь в вашем файле реализации выделите память для таких объектов, как это

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     if (self) 
     {
         // Custom initialization
         myAray=[[NSMutableArray alloc] init];
         object=[[CustomObject alloc] init];
     }
     return self;
}

Теперь вы выделили память Arrayи объект. Теперь вы можете заполнить эту память, прежде чем нажать этуViewController

Перейдите к вашему SecondViewController.h и напишите два метода

-(void)setMyArray:(NSArray *)_myArray;
-(void)setMyObject:(CustomObject *)_myObject;

в файле реализации вы можете реализовать функцию

-(void)setMyArray:(NSArray *)_myArray
{
     [myArra addObjectsFromArray:_myArray];
}
-(void)setMyObject:(CustomObject *)_myObject
{
     [object setCustomObject:_myObject];
}

ожидая, что вы CustomObjectдолжны иметь функцию установки с ним.

Теперь ваша основная работа выполнена. перейти к месту, где вы хотите нажатьSecondViewController и выполните следующие действия

SecondViewController *secondView= [[SecondViewController alloc] initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]] ;
[secondView setMyArray:ArrayToPass];
[secondView setMyObject:objectToPass];
[self.navigationController pushViewController:secondView animated:YES ];

Берегите орфографические ошибки.

AsifHabib
источник
24

Это не способ сделать это, вы должны использовать делегаты, я предполагаю, что у нас есть два контроллера представления ViewController1 и ViewController2, и этот элемент проверки находится в первом, и когда его состояние изменяется, вы хотите сделать что-то в ViewController2, чтобы Чтобы добиться этого надлежащим образом, вы должны сделать следующее:

Добавьте новый файл в ваш проект (протокол Objective-C) Файл -> Новый, теперь назовите его ViewController1Delegate или как хотите, и напишите их между директивами @interface и @end

@optional

- (void)checkStateDidChange:(BOOL)checked;

Теперь перейдите к ViewController2.h и добавьте

#import "ViewController1Delegate.h"

затем измените его определение на

@interface ViewController2: UIViewController<ViewController1Delegate>

Теперь перейдите к ViewController2.m и внутри реализации добавьте:

- (void)checkStateDidChange:(BOOL)checked {
     if (checked) {
           // Do whatever you want here
           NSLog(@"Checked");
     }
     else {
           // Also do whatever you want here
           NSLog(@"Not checked");
     }
}

Теперь перейдите к ViewController1.h и добавьте следующее свойство:

@property (weak, nonatomic) id<ViewController1Delegate> delegate; 

Теперь, если вы создаете ViewController1 внутри ViewController2 после некоторого события, то вы должны сделать это следующим образом, используя файлы NIB:

ViewController1* controller = [[NSBundle mainBundle] loadNibNamed:@"ViewController1" owner:self options:nil][0];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];

Теперь все готово, когда вы обнаруживаете событие проверки, измененное в ViewController1, все что вам нужно сделать, это ниже

[delegate checkStateDidChange:checked]; // You pass here YES or NO based on the check state of your control

Пожалуйста, скажите мне, если есть что-то, что не ясно, если я не правильно понял ваш вопрос.

Бода Тальо
источник
23

Если вы хотите отправить данные из одного в другой viewController, вот способ:

Скажем, у нас есть viewControllers: viewControllerA и viewControllerB

Теперь в viewControllerB.h

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

- (id)initWithArray:(NSArray)a andString:(NSString)s;

В viewControllerB.m

#import "viewControllerB.h"

@implementation viewControllerB

- (id)initWithArray:(NSArray)a andString:(NSString)s {

   array = [[NSArray alloc] init];
   array = a;

   string = [[NSString alloc] init];
   string = s;

}

В viewControllerA.m

#import "viewControllerA.h"
#import "viewControllerB.h"

@implementation viewControllerA

- (void)someMethod {

  someArray = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];
  someString = [NSString stringWithFormat:@"Hahahahaha"];

  viewControllerB *vc = [[viewControllerB alloc] initWithArray:someArray andString:someString];

  [self.navigationController pushViewController:vc animated:YES];
  [vc release];

}

Так вот как вы можете передавать данные из viewControllerA в viewControllerB без установки какого-либо делегата. ;)

Анируддх Джоши
источник
1
Я попытался использовать код ur в моем проекте, но не смог получить значения в viewcontrollerB. Можете ли вы сказать мне, в чем может быть проблема?
X-Coder
1
@Ajitthala Можете ли вы вставить свой код в новый вопрос? Я постараюсь решить вашу проблему. :)
Aniruddh Joshi
1
это неправильно, чтобы не использовать методы init, а просто сделать что-то вроде vcB.string = @ "asdf" из viewcontroller A?
khanh.tran.vinh
1
@ khanh.tran.vinh Зависит от того, используете ли вы ARC или нет.
Анируддх Джоши
21

Я знаю, что это непростая тема, но для тех, кто хочет ответить на этот вопрос с уклоном SWIFT и хочет получить простой пример, здесь мой метод перехода для передачи данных, если вы используете переход, чтобы обойти.

Это похоже на выше, но без кнопок, ярлыков и тому подобное. Просто передавая данные из одного представления в другое.

Настройте раскадровку

Есть три части.

  1. Отправитель
  2. Segue
  3. Получатель

Это очень простой макет представления с переходом между ними.


Очень простой вид компоновки.  Примечание: нет навигационного контроллера


Вот настройки для отправителя


Отправитель


Вот настройка для приемника.


Получатель


Наконец, установка для перехода.


Идентификатор Segue


Контроллеры представления

Мы придерживаемся этого простого, поэтому никаких кнопок, а не действий, мы просто перемещаем данные от отправителя к получателю при загрузке приложения, а затем выводим переданное значение на консоль.

Эта страница принимает изначально загруженное значение и передает его.

import UIKit


class ViewControllerSender: UIViewController {

    // THE STUFF - put some info into a variable
    let favoriteMovie = "Ghost Busters"

    override func viewDidAppear(animated: Bool) {
        // PASS IDENTIFIER - go to the recieving view controller.
        self.performSegueWithIdentifier("goToReciever", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        //GET REFERENCE - ...to the receiver view.
        var viewControllerReceiver = segue.destinationViewController as? ViewControllerReceiver

        //PASS STUFF - pass the variable along to the target.
        viewControllerReceiver!.yourFavMovie = self.favoriteMovie

    }

}

Эта страница просто отправляет значение переменной в консоль при загрузке. К этому моменту наш любимый фильм должен быть в этой переменной.

import UIKit

class ViewControllerReceiver: UIViewController {

    //Basic empty variable waiting for you to pass in your fantastic favorite movie.
    var yourFavMovie = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        //And now we can view it in the console.
        println("The Movie is \(self.yourFavMovie)")

    }   
}

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

После запуска он должен автоматически переключиться на представление получателя и передать значение от отправителя получателю, отображая значение в консоли.

Ghost Busters - это классические люди.

Кристофер Уэйд Кэнтли
источник
19

В моем случае я использовал одноэлементный класс, который может работать как глобальный объект, предоставляя доступ к данным практически из любой точки приложения. Первое, что нужно сделать - создать класс синглтона. Пожалуйста, обратитесь к странице « Как должен выглядеть мой синглтон Objective-C? » И что я сделал для того, чтобы сделать объект глобально доступным, просто импортировал его appName_Prefix.pch, чтобы применить оператор импорта во всех классах. Чтобы получить доступ к этому объекту и использовать его, я просто реализовал метод класса для возврата общего экземпляра, который содержит свои собственные переменные

petershine
источник
Это правильный ответ. Просто используйте синглтон в качестве «модели». Обратите внимание, что, как говорит Калеб, «модель для вашего приложения может быть такой же простой, как массив строк» . Важно отметить, что делать синглтон в Swift действительно тривиально . (Настолько просто, что здесь даже не стоит упоминать - просто Google.) Для новых программистов стоит понимать, что создание одиночки раньше было настоящей болью в заднице . Тем не менее, синглтоны играют ключевую роль в программировании на iOS - все, что делает Apple, является синглтоном. Вот почему Apple наконец-то сделала тривиальным (в Swift) правильное создание синглетонов.
Толстяк
1
Обратите внимание, однако, что в эти дни (2016+) «все является контейнерным представлением в iOS». Каждую вещь, которую вы делаете на экране, вы делаете маленьким контейнером. Довольно просто получить ссылки «вверх и вниз» цепочек представлений контейнера (хотя Apple сделает это проще в будущем), и вы все равно делаете это почти для каждого представления контейнера. Итак, если вы все равно это сделали - у вас есть ответ; нет необходимости в синглтоне. Введение в представление контейнера ... stackoverflow.com/a/23403979/294884
Толстяк
19

Swift 5

Well Ответ Мэтты Прайсы прекрасно подходит для передачи данных , но я собираюсь переписать его, в последней версию Swift , потому что я считаю , новые программисты считают бросить вызов из - за новый синтаксис и методы / рамку, так как оригинальный пост в Objective-C.

Существует несколько вариантов передачи данных между контроллерами представления.

  1. Использование навигационного контроллера Push
  2. Используя Segue
  3. Использование делегата
  4. Использование Notification Observer
  5. Используя Блок

Я собираюсь переписать его логику в Swift с последней iOS Framework


Передача данных через контроллер навигации Push : из ViewControllerA в ViewControllerB

Шаг 1. Объявите переменную в ViewControllerB

var isSomethingEnabled = false

Шаг 2. Печать переменной в методе ViewControllerB 'ViewDidLoad

override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through segue, navigation push
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }

Шаг 3. В ViewControllerA передать данные во время проталкивания через контроллер навигации

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
        viewControllerB.isSomethingEnabled = true
        if let navigator = navigationController {
            navigator.pushViewController(viewControllerB, animated: true)
        }
    }

Итак, вот полный код для:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:Passing Data through Navigation PushViewController
    @IBAction func goToViewControllerB(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.isSomethingEnabled = true
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    //MARK:  - Variable for Passing Data through Navigation push   
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through navigation push
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }
}

Передача данных через Segue : из ViewControllerA в ViewControllerB

Шаг 1. Создайте Segue из ViewControllerA в ViewControllerB и задайте Identifier = showDetailSegue в раскадровке, как показано ниже

введите описание изображения здесь

Шаг 2. В ViewControllerB объявляем жизнеспособное имя isSomethingEnabled и напечатайте ее значение.

Шаг 3. В ViewControllerA передайте значение isSomethingEnabled при передаче Segue

Итак, вот полный код для:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:  - - Passing Data through Segue  - - 
    @IBAction func goToViewControllerBUsingSegue(_ sender: Any) {
        performSegue(withIdentifier: "showDetailSegue", sender: nil)
    }

    //Segue Delegate Method
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "showDetailSegue") {
            let controller = segue.destination as? ViewControllerB
            controller?.isSomethingEnabled = true//passing data
        }
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through segue
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }
}

Передача данных через делегат : из ViewControllerB в ViewControllerA

Шаг 1. Объявите протокол ViewControllerBDelegate в файле ViewControllerB, но вне класса

protocol ViewControllerBDelegate: NSObjectProtocol {

    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

Шаг 2. Объявление экземпляра делегированной переменной в ViewControllerB

var delegate: ViewControllerBDelegate?

Шаг 3. Отправка данных для делегата внутри метода viewDidLoad ViewControllerB

delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")

Шаг 4. Подтвердите ViewControllerBDelegate в ViewControllerA

class ViewControllerA: UIViewController, ViewControllerBDelegate  {
// to do
}

Шаг 5. Подтвердите, что вы реализуете делегат в ViewControllerA

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.delegate = self//confirming delegate
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }

Шаг 6. Реализация метода делегата для получения данных в ViewControllerA

func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
        print("Value from ViewControllerB's Delegate", item!)
    }

Итак, вот полный код для:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController, ViewControllerBDelegate  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //Delegate method
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
        print("Value from ViewControllerB's Delegate", item!)
    }

    @IBAction func goToViewControllerForDelegate(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.delegate = self
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

ViewControllerB

import UIKit

//Protocol decleare
protocol ViewControllerBDelegate: NSObjectProtocol {
    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

class ViewControllerB: UIViewController {
    var delegate: ViewControllerBDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        //MARK:  - - - -  Set Data for Passing Data through Delegate  - - - - - -
        delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")
    }
}

Передача данных через Notification Observer : из ViewControllerB в ViewControllerA

Шаг 1. Установка и публикация данных в наблюдателе уведомлений в ViewControllerB

let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)

Шаг 2. Добавьте наблюдателя уведомлений в ViewControllerA

NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)

Шаг 3. Получите значение данных уведомления в ViewControllerA

@objc func methodOfReceivedNotification(notification: Notification) {
        print("Value of notification : ", notification.object ?? "")
    }

Итак, вот полный код для:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController{

    override func viewDidLoad() {
        super.viewDidLoad()

        // add observer in controller(s) where you want to receive data
        NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)
    }

    //MARK: Method for receiving Data through Post Notification 
    @objc func methodOfReceivedNotification(notification: Notification) {
        print("Value of notification : ", notification.object ?? "")
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //MARK:Set data for Passing Data through Post Notification
        let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)
    }
}

Передача данных через блок : из ViewControllerB в ViewControllerA

Шаг 1. Объявление блока в ViewControllerB

var authorizationCompletionBlock: ((Bool) -> ())? = {_ in}

Шаг 2. Установите данные в блоке в ViewControllerB

if authorizationCompletionBlock != nil
        {
            authorizationCompletionBlock!(true)
        }

Шаг 3. Получить данные блока в ViewControllerA

//Receiver Block
                controller!.authorizationCompletionBlock = { isGranted in
                    print("Data received from Block is :", isGranted)
                }

Итак, вот полный код для:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:Method for receiving Data through Block
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if (segue.identifier == "showDetailSegue") {
                let controller = segue.destination as? ViewControllerB
                controller?.isSomethingEnabled = true

                //Receiver Block
                controller!.authorizationCompletionBlock = { isGranted in
                    print("Data received from Block is :", isGranted)
                }
            }
        }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    //MARK:Variable for Passing Data through Block
    var authorizationCompletionBlock:((Bool)->())? = {_ in}

    override func viewDidLoad() {
        super.viewDidLoad()

        //MARK:Set data for Passing Data through Block
        if authorizationCompletionBlock != nil
        {
            authorizationCompletionBlock!(true)
        }
    }
}

Вы можете найти полный образец приложения на моем GitHub. Пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы по этому вопросу.

swiftBoy
источник
18

Передача данных между FirstViewController в SecondViewController, как показано ниже

Например:

FirstViewController Строковое значение как

StrFirstValue = @"first";

так что мы можем передать это значение во второй класс, используя шаг ниже

1> Нам нужно создать строковый объект в файле SecondViewController.h

NSString *strValue;

2> Необходимо объявить свойство, как показано ниже в декларации в файле .h

@property (strong, nonatomic)  NSString *strSecondValue;

3> Нужно синтезировать это значение в файле FirstViewController.m ниже объявления заголовка

@synthesize strValue;

и в FirstViewController.h:

@property (strong, nonatomic)  NSString *strValue;

4> В FirstViewController, из какого метода мы переходим ко второму виду, пожалуйста, напишите ниже код в этом методе.

SecondViewController *secondView= [[SecondViewController alloc]     
initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]];

[secondView setStrSecondValue:StrFirstValue];

[self.navigationController pushViewController:secondView animated:YES ];
Крис Алан
источник
Находясь в SecondViewController, как вы передаете данные обратно в FirstViewController?
Бруно
18

В настоящее время я участвую в решении этой проблемы с открытым исходным кодом через проект MCViewFactory, который можно найти здесь:

https://github.com/YetiHQ/manticore-iosviewfactory

Идея состоит в том, чтобы подражать парадигме намерений Android, используя глобальную фабрику для управления видом, который вы просматриваете, и используя «намерения» для переключения и передачи данных между представлениями. Вся документация находится на странице github, но вот некоторые основные моменты:

Вы настраиваете все свои представления в файлах .XIB и регистрируете их в делегате приложения при инициализации фабрики.

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

// the following two lines are optional. 
[factory registerView:@"YourSectionViewController"]; 

Теперь, в вашем VC, в любое время, когда вы хотите перейти к новому VC и передать данные, вы создаете новое намерение и добавляете данные в его словарь (saveInstanceState). Затем просто установите текущее намерение фабрики:

MCIntent* intent = [MCIntent intentWithSectionName:@"YourSectionViewController"];
[intent setAnimationStyle:UIViewAnimationOptionTransitionFlipFromLeft];
[[intent savedInstanceState] setObject:@"someValue" forKey:@"yourKey"];
[[intent savedInstanceState] setObject:@"anotherValue" forKey:@"anotherKey"];
// ...
[[MCViewModel sharedModel] setCurrentSection:intent];

Все ваши представления, которые соответствуют этому, должны быть подклассами MCViewController, которые позволяют вам переопределить новый метод onResume:, позволяющий вам получить доступ к данным, которые вы передали.

-(void)onResume:(MCIntent *)intent {
    NSObject* someValue = [intent.savedInstanceState objectForKey:@"yourKey"];
    NSObject* anotherValue = [intent.savedInstanceState objectForKey:@"anotherKey"];

    // ...

    // ensure the following line is called, especially for MCSectionViewController
    [super onResume:intent];
}

Надеюсь, что некоторые из вас найдут это решение полезным / интересным.


источник
Тогда все объекты контроллера могут получить / установить все зарегистрированные словари в любых областях? Понизить это.
Итачи
15

Создайте свойство на next view controller .hи определите getter и setter.

Добавьте это propertyв NextVC.h на nextVC

@property (strong, nonatomic) NSString *indexNumber;

Добавить

@synthesize indexNumber; в NextVC.m

И последнее

NextVC *vc=[[NextVC alloc]init];

vc.indexNumber=@"123";

[self.navigationController vc animated:YES];
Вивек Ядав
источник
11

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

Я написал пост в блоге об этом некоторое время назад: совместное использование кода модели . Вот краткое резюме:

Общие данные

Одним из подходов является разделение указателей на объекты модели между контроллерами представления.

  • Итерация грубой силы на контроллерах представления (в Navigation или Tab Bar Controller) для установки данных
  • Установите данные в prepareForSegue (если раскадровки) или init (если программно)

Так как подготовка к переходу является наиболее распространенным, вот пример:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var next = segue.destinationViewController as NextViewController
    next.dataSource = dataSource
}

Независимый доступ

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

Наиболее распространенный способ, которым я видел это, - одноэлементный экземпляр. Так что, если ваш объект-одиночка был, DataAccessвы могли бы сделать следующее в методе viewDidLoad UIViewController:

func viewDidLoad() {
    super.viewDidLoad()
    var data = dataAccess.requestData()
}

Есть дополнительные инструменты, которые также помогают передавать данные:

  • Наблюдение значения ключа
  • NSNotification
  • Основные данные
  • NSFetchedResultsController
  • Источник данных

Основные данные

Приятно, что в Core Data есть обратные отношения. Поэтому, если вы хотите просто дать NotesViewController объект notes, вы можете это сделать, потому что он будет иметь обратную связь с чем-то другим, таким как блокнот. Если вам нужны данные на ноутбуке в NotesViewController, вы можете вернуться к графу объектов, выполнив следующие действия:

let notebookName = note.notebook.name

Подробнее об этом читайте в моем блоге: разделение кода модели

Корей Хинтон
источник
10

NewsViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [tbl_View deselectRowAtIndexPath:indexPath animated:YES];
  News *newsObj = [newstitleArr objectAtIndex:indexPath.row];
  NewsDetailViewController *newsDetailView = [[NewsDetailViewController alloc] initWithNibName:@"NewsDetailViewController" bundle:nil];

  newsDetailView.newsHeadlineStr = newsObj.newsHeadline;

  [self.navigationController pushViewController:newsDetailView animated:YES];
}

NewsDetailViewController.h

@interface NewsDetailViewController : UIViewController
@property(nonatomic,retain) NSString *newsHeadlineStr;
@end

NewsDetailViewController.m

@synthesize newsHeadlineStr;
Мохсин Сабасара
источник
10

Делегирование - единственное решение для выполнения таких операций при использовании файлов .xib, однако все ответы, описанные выше, предназначены storyboardдля файлов .xibs, которые необходимо использовать для делегирования. это единственное решение, которое вы можете.

Другое решение - использовать шаблон класса singleton, инициализировать его один раз и использовать во всем приложении.

user2786888
источник
10

если вы хотите передать данные из ViewControlerOne во ViewController, попробуйте следующее.

сделать это в ViewControlerOne.h

 @property (nonatomic, strong) NSString *str1;

сделать это в ViewControllerTwo.h

 @property (nonatomic, strong) NSString *str2;

Синтезировать str2 в ViewControllerTwo.m

@interface ViewControllerTwo ()
@end
@implementation ViewControllerTwo
@synthesize str2;

сделать это в ViewControlerOne.m

 - (void)viewDidLoad
 {
   [super viewDidLoad];

  // Data or string you wants to pass in ViewControllerTwo..
  self.str1 = @"hello world";

 }

на кнопках событие нажатия сделать это ..

-(IBAction)ButtonClicked
{ //Navigation on buttons click event from ViewControlerOne to ViewControlerTwo with transferring data or string..
  ViewControllerTwo *objViewTwo=[self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerTwo"];
  obj.str2=str1;
  [self.navigationController pushViewController: objViewTwo animated:YES];
}

сделать это в ViewControllerTwo.m

- (void)viewDidLoad
{
 [super viewDidLoad];
  NSLog(@"%@",str2);
}
krushnsinh
источник
10

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

AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

Например

если вы объявите, NSArray object *arrayXYZто вы можете получить к нему доступ в любом контроллере представленияappDelegate.arrayXYZ

ak_tyagi
источник
Это метод выбора для хакатона
Хай Фенг Као
9

Если вы хотите отправить данные из одного в другой viewController, вот способ:

Скажем, у нас есть viewControllers: ViewController и NewViewController.

в ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
{
    IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;
}

@property (nonatomic,retain) IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;

-(IBAction)goToNextScreen:(id)sender;

@end

в ViewController.m

#import "ViewController.h"

#import "NewViewController.h"

@implementation ViewController
@synthesize mytext1,mytext2,mytext3,mytext4;

-(IBAction)goToNextScreen:(id)sender
{
    NSArray *arr = [NSArray arrayWithObjects:mytext1.text,mytext2.text,mytext3.text,mytext4.text, nil];


    NewViewController *newVc = [[NewViewController alloc] initWithNibName:@"NewViewController" bundle:nil];

    newVc.arrayList = arr;

    [self.navigationController pushViewController:newVc animated:YES];

}

В NewViewController.h

#import <UIKit/UIKit.h>

@interface NewViewController : UITableViewController
{
    NSArray *arrayList;

    NSString *name,*age,*dob,*mobile;

}

@property(nonatomic, retain)NSArray *arrayList;

@end

В NewViewController.m

#import "NewViewController.h"

#import "ViewController.h"

@implementation NewViewController
@synthesize arrayList;

#pragma mark - Table view data source

- (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 [arrayList count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];      
    }
    // Configure the cell...
    cell.textLabel.text = [arrayList objectAtIndex:indexPath.row];
    return cell;


}

@end

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

Sabs
источник
8

Мне нравится идея объектов Model и объектов Mock на основе NSProxy для фиксации или отбрасывания данных, если то, что выбирает пользователь, может быть отменено.

Данные легко передавать, так как это один объект или пара объектов, и, если у вас есть, скажем, контроллер UINavigationController, вы можете сохранить ссылку на модель внутри, и все контроллеры push-представления могут получить к ней доступ непосредственно из контроллера навигации.

Бен Синклер
источник
8

Я видел много людей, которые усложняли это, используя didSelectRowAtPathметод. Я использую Core Data в моем примере.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    //this solution is for using Core Data
    YourCDEntityName * value = (YourCDEntityName *)[[self fetchedResultsController] objectAtIndexPath: indexPath];

    YourSecondViewController * details = [self.storyboard instantiateViewControllerWithIdentifier:@"nameOfYourSecondVC"];//make sure in storyboards you give your second VC an identifier

    //Make sure you declare your value in the second view controller
    details.selectedValue = value;

    //Now that you have said to pass value all you need to do is change views
    [self.navigationController pushViewController: details animated:YES];

}

4 строки кода внутри метода, и все готово.

App Dev Guy
источник
6

Есть много ответов на эти вопросы, предлагающих много разных способов установить связь с контроллером представления, которые действительно будут работать, но я нигде не вижу упоминания, какой из них лучше всего использовать, а какой избегать.

На практике, на мой взгляд, рекомендуется всего несколько решений:

  • Для передачи данных вперед:
    • переопределить prepare(for:sender:)метод UIViewControllerпри использовании раскадровки и segues
    • передавать данные через инициализатор или через свойства при выполнении переходов контроллера представления через код
  • Для передачи данных в обратном направлении
    • обновить общее состояние приложения (которое можно передать вперед между контроллерами представления одним из указанных выше способов)
    • использовать делегирование
    • использовать раскрутить

Решения, которые я рекомендую НЕ использовать:

  • Ссылка на предыдущий контроллер напрямую вместо использования делегирования
  • Обмен данными через синглтон
  • Передача данных через приложение-делегат
  • Обмен данными через пользовательские настройки по умолчанию
  • Передача данных через уведомления

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

Для тех, кто заинтересован, я написал несколько статей, в которых более подробно рассматриваются эти вопросы и выделены различные недостатки:

Маттео Манфердини
источник