Передача даних між контролерами перегляду


1372

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

Я маю думку, яка виступає формою для введення даних, і я хочу надати користувачеві можливість вибору декількох продуктів. Продукти перераховані на іншому вікні, UITableViewControllerі я включив декілька варіантів.

Моє запитання: як я можу перенести дані з одного перегляду в інший? Я буду проводити виділення UITableViewв масиві, але як мені потім повернути його до попереднього перегляду форми введення даних, щоб його можна було зберегти разом з іншими даними до Основних даних після подання форми?

Я займався серфінгом і бачив, як деякі люди оголошують масив у делегата додатка. Я читав щось про 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перш ніж натиснути на nav стек.

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

    Це дозволить встановити isSomethingEnabledв ViewControllerBдо BOOLзначенням YES.

Передача даних вперед за допомогою Segues

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

-(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];
Nazir

192

Швидкий

Тут є багато тонн пояснень і навколо StackOverflow, але якщо ви новачок просто намагаєтесь зробити щось основне для роботи, спробуйте переглянути цей підручник YouTube (саме це допомогло мені нарешті зрозуміти, як це зробити).

Передача даних в наступний контролер перегляду

Далі наведено приклад на основі відео. Ідея полягає в тому, щоб передати рядок з текстового поля в Контролері першого перегляду до мітки в Контролері другого перегляду.

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

Створіть макет розкладу в Інтерфейсі. Щоб зробити segue, просто 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.
  • Встановіть перший і другий контролери подання на відповідні файли Swift в IB.

Повернення даних назад до попереднього контролера перегляду

Для передачі даних назад від другого контролера перегляду до першого контролера перегляду ви використовуєте протокол та делегат . Це відео - це дуже чітка хода цього процесу:

Далі наведено приклад на основі відео (з кількома модифікаціями).

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

Створіть макет розкладу в Інтерфейсі. Знову ж таки, щоб зробити segue, просто 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.

Це воно. Запустивши додаток, ви повинні мати можливість відправляти дані назад з другого контролера перегляду в перший.


З огляду на деякі останні оновлення Swift, це все ще поширена модель для впровадження?
piofusco

4
Більшість усіх оновлень Swift, які я бачив, були відносно незначними синтаксичними змінами, а не змінами в передачі даних між контролерами перегляду. Якщо я дізнаюся про будь-які великі зміни, як це, я оновлю свою відповідь.
Сурагч

2
offtopic - iOS має такий некрасивий спосіб передавати параметри новим контролерам перегляду, неймовірний - вам потрібно встановлювати параметри не в місці, коли ви здійснюєте виклик, а в якомусь іншому. Android має кращий підхід у цьому відношенні - коли ви починаєте діяльність, ви можете передавати будь-які дані (ну, майже) через її початковий Намір. Легко. Не потрібно робити кадри чи щось таке. Передача зворотних значень абоненту теж важлива річ, не потрібно делегувати. Звичайно, можна використовувати і некрасиві підходи, ніяких проблем там немає))
Міксаз

1
@Himanshu, спочатку знайдіть посилання на другий контролер подання. Потім оновіть загальнодоступну змінну, яку вона містить.
Сурагч

8
@Honey. Я думаю, що слово "делегат" є заплутаним. Дозвольте мені використовувати слово «робітник». "Робітник" (перший контролер подання) робить все, що "начальник" (другий контролер подання) наказує йому робити. "Начальник" не знає, хто буде його "працівником"; це може бути хто завгодно. Тож у першому контролері подання (клас "робітник") сказано, що я буду вашим "працівником". Ти скажи мені, що написати на етикетці, і я це зроблю за тебе. Таким чином, secondViewController.delegate = selfозначає «я згоден бути працівником начальства». Дивіться цю відповідь для іншого прикладу та більше пояснень.
Сурагч

136

M в MVC призначений для "Модель", а в парадигмі MVC роль модельних класів полягає в управлінні даними програми. Модель є протилежною погляду - представлення вміє відображати дані, але нічого не знає, що робити з даними, тоді як модель знає все про те, як працювати з даними, але нічого про те, як їх відображати. Моделі можуть бути складними, але вони не повинні бути - модель для вашої програми може бути простою, як масив рядків або словників.

Роль контролера полягає в посередництві між видом і моделлю. Тому їм потрібна посилання на один або кілька об'єктів перегляду та один або кілька об'єктів моделі. Скажімо, що ваша модель являє собою масив словників, кожен словник представляє один рядок у вашій таблиці. У кореневому представленні для вашої програми відображається ця таблиця, і вона може відповідати за завантаження масиву з файлу. Коли користувач вирішить додати новий рядок до таблиці, він натискає якусь кнопку, і ваш контролер створює новий (змінний) словник і додає його до масиву. Для того, щоб заповнити рядок, контролер створює контролер перегляду деталей та надає йому новий словник. Контролер подання деталей заповнює словник і повертається. Словник вже є частиною моделі, тому нічого іншого не потрібно робити.


95

Існують різні способи передачі даних в інший клас в iOS. Наприклад -

  1. Пряма ініціалізація після виділення іншого класу.
  2. Делегація - для передачі даних назад
  3. Повідомлення - для трансляції даних у кілька класів одночасно
  4. Збереження в NSUserDefaults - для отримання доступу до нього пізніше
  5. Однокласні заняття
  6. Бази даних та інші механізми зберігання даних, такі як пліст тощо.

Але для простого сценарію передачі значення іншому класу, розподіл якого виконується в поточному класі, найбільш поширеним і бажаним методом буде пряме встановлення значень після розподілу. Робиться це так:

Ми можемо зрозуміти це за допомогою двох контролерів - 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 і передати цю модельObject наступному класу

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.

Сподіваюсь, це допомагає


84

Після проведення додаткових досліджень здавалося, що протоколи та делегати - це правильний спосіб / Apple, який бажає зробити це.

Я закінчив, використовуючи цей приклад

Обмін даними між контролерами перегляду та іншими об'єктами @ iPhone Dev SDK

Добре працював і дозволив мені пропускати рядок і масив вперед і назад між своїми поглядами.

Дякуємо за всю вашу допомогу


3
не використовуйте протоколи та делегати, просто використовуйте розмотування.
мальхал

1
@malhal Що робити, якщо ви не використовуєте мовленнєві дошки ??
Еван Р

Я ненавиджу марні протоколи і делегатів теж. @malhal
DawnSong

@EvanR Ви можете створювати та виконувати Seges в коді. Це все одно.
DawnSong

1
По суті, вся якість на цій сторінці "від старих часів до перегляду контейнерів". Ви ніколи за мільйон років не зациклювалися на протоколах чи делегатах зараз. Кожна дрібниця, яку ви робите на будь-якому екрані, так чи інакше - це перегляд контейнера, тому питання справді більше не існує - у вас уже є всі посилання "вгору і вниз" з усіх представлень контейнерів.
Fattie

66

Я знаходжу найпростішу та найелегантнішу версію з прохідними блоками. Назвемо контролер перегляду, який чекає повернених даних як "А", а контролер повернення перегляду - "В". У цьому прикладі ми хочемо отримати 2 значення: перше з Type1 і друге з Type2.

Припускаючи, що ми використовуємо Storyboard, перший контролер встановлює блок зворотних викликів, наприклад, під час підготовки Segue:

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

Варто пам’ятати, що за допомогою блоку часто потрібно керувати потужними та __слабими посиланнями, як пояснено тут


Чому б значення не було параметром для блоку зворотних викликів, а не окремим властивістю?
Timuçin

56

У багатьох наданих відповідях є хороша інформація, але жодна не відповідає на це питання повністю.

Питання задає питання про передачу інформації між контролерами перегляду. Наведений конкретний приклад запитує про передачу інформації між переглядами, але з огляду на самовизначену новизну iOS, оригінальний плакат, ймовірно, мав на увазі між viewControllers, а не між представленнями (без участі ViewControllers). Здається, що всі відповіді зосереджені на двох контролерах перегляду, але що робити, якщо додаток еволюціонує, потрібно залучати до обміну інформацією більше двох контролерів перегляду?

Оригінальний плакат також запитав про Singletons та використання AppDelegate . На ці запитання потрібно відповісти.

Щоб допомогти іншим, хто дивиться на це питання, хто хоче повну відповідь, я намагаюся його надати.

Сценарії застосування

Замість того, щоб вести високогіпотетичну, абстрактну дискусію, це допомагає мати на увазі конкретні програми. Щоб допомогти визначити ситуацію з двома переглядачами-контролером та ситуацією-контролером більше, ніж два, я збираюся визначити два конкретні сценарії застосування.

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

схема вихідної проблеми

У додатку є два контролери перегляду. Є ViewControllerA (форма введення даних) та контролер перегляду B (Список продуктів). Елементи, вибрані у списку продуктів, повинні відповідати предметам, відображеним у текстовому полі у формі введення даних. У цьому сценарії ViewControllerA та ViewControllerB повинні безпосередньо спілкуватися між собою та не мати інших контролерів перегляду.

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

Діаграма застосування домашнього інвентарю

У додатку є чотири контролери перегляду. Це додаток на основі вкладки для управління домашнім інвентарем. Три контролери подання представлені різними відфільтрованими видами одних і тих же даних:

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

Кожен раз, коли окремий елемент створюється чи редагується, він також повинен синхронізуватися з іншими контролерами перегляду. Наприклад, якщо ми додамо човен у ViewControllerD, але він ще не застрахований, він повинен з’явитися, коли користувач переходить до ViewControllerA (предметів розкоші), а також ViewControllerC (весь домашній інвентар), але не тоді, коли користувач переходить до ViewControllerB (Нестрахові товари). Нам потрібно потурбуватися не лише про додавання нових елементів, а й про видалення елементів (які можуть бути дозволені з будь-якого з чотирьох контролерів перегляду) або редагування існуючих елементів (що може бути дозволено з "Додати форму нового елемента", змінивши те саме для редагування).

Оскільки всім контролерам огляду потрібно обмінюватися одними і тими ж даними, всі чотири контролери перегляду повинні залишатися синхронізованими, і тому потрібно мати певний зв’язок з усіма іншими контролерами перегляду, коли кожен контролер одиничного виду змінює основні дані. Це повинно бути досить очевидним, що ми не хочемо, щоб кожен контролер перегляду в цьому сценарії безпосередньо спілкувався один з одним контролером перегляду. Якщо це не очевидно, подумайте, чи було у нас 20 різних контролерів перегляду (а не просто 4). Наскільки складно та схильне до помилок було б сповістити кожного з інших 19 контролерів перегляду кожного разу, коли один контролер перегляду здійснив зміну?

Рішення: Делегати і шаблон спостерігача, і одиночні

У першому сценарії у нас є кілька життєздатних рішень, як і інші відповіді

  • segues
  • делегати
  • встановлення властивостей безпосередньо на контролерах перегляду
  • 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

Коли колекція предметів домашнього інвентарю змінюється, контролери перегляду повинні бути поінформовані про цю зміну. Визначення класу вище не дає зрозуміти, як це відбуватиметься. Нам потрібно слідувати схемі спостерігачів. Контролери перегляду повинні офіційно дотримуватися спільного управління. Є два способи спостереження за іншим об'єктом:

  • Ключова цінність (KVO)
  • NSNotificationCenter.

У другому сценарії ми не маємо жодної властивості HouseholdInventoryManager, яку можна було б спостерігати за допомогою KVO. Оскільки у нас немає жодної властивості, яку легко помітити, модель спостерігача в цьому випадку повинна бути реалізована за допомогою NSNotificationCenter. Кожен з чотирьох контролерів перегляду підписується на сповіщення, а sharedManager надсилатиме сповіщення в центр сповіщень, коли це доречно. Менеджеру інвентаризації не потрібно нічого знати про контролери перегляду або про екземпляри будь-яких інших класів, які можуть бути зацікавлені знати, коли змінюється збір предметів інвентаризації; NSNotificationCenter опікується цими деталями впровадження. Контролери перегляду просто підписуються на сповіщення, а менеджер даних просто публікує сповіщення.

Багато програмістів-початківців користуються тим, що завжди є точно один делегат додатків за весь час роботи програми додатків, який доступний у всьому світі. Починаючі програмісти використовують цей факт для введення об’єктів та функціональних можливостей у appDelegate як зручність для доступу з будь-якого місця в додатку. Тільки тому, що AppDelegate є одинарним, це не означає, що він повинен замінити всі інші сингтони. Це погана практика, оскільки вона накладає занадто велике навантаження на один клас, порушуючи добрі об'єктно-орієнтовані практики. Кожен клас повинен мати чітку роль, яку легко пояснити, часто просто за назвою класу.

Щоразу, коли ваш Делегат додатків почне роздуватися, починайте видаляти функціональні можливості в одиночні кнопки. Наприклад, стек даних Core не повинен залишатися в AppDelegate, а замість цього повинен бути розміщений у своєму власному класі, як coreDataManager.

Список літератури


41

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

Розгортання мов, блоків ARC та LLVM роблять це простіше, ніж будь-коли, для мене. Деякі відповіді вище згаданих розкадрів та мотивів вже покладаються на делегування. Визначення делегатів, безумовно, працює, але деяким людям може бути легше передавати покажчики або кодові блоки.

За допомогою UINavigators та segues існують прості способи передачі інформації до підпорядкованого контролеру та повернення інформації назад. ARC робить передачу покажчиків на речі, похідні від NSObjects, простими, тому якщо ви хочете, щоб підпорядкований контролер додав / змінив / змінив деякі дані для вас, передайте його вказівник на змінний екземпляр. Блоки спрощують проходження дій, тому якщо ви хочете, щоб підпорядкований контролер викликав дію на контролері вищого рівня, передайте йому блок. Ви визначаєте блок для прийняття будь-якої кількості аргументів, які мають для вас сенс. Ви також можете розробити API, щоб використовувати кілька блоків, якщо це краще підходить.

Ось два тривіальні приклади клейового матеріалу. Перший прямо показує один параметр, переданий для введення, другий для виведення.

// 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];
    }];
}

41

Передача даних назад з ViewController 2 (призначення) до viewController 1 (Джерело) - це цікавіше. Якщо припустити, що ви використовуєте storyBoard, це всі способи, які я дізнався:

  • Делегат
  • Повідомлення
  • За замовчуванням користувача
  • Сінглтон

Про це вже йшлося.

Я знайшов, що існує більше способів:

- Використання зворотних викликів блоків:

використовувати його в prepareForSegueметоді в VC1

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

-Використання розкладок (Вихід)

Реалізуйте метод з аргументом UIStoryboardSegue в VC 1, як цей:

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

У сюжетній дошці підключіть кнопку «повернення» до зеленої кнопки «Вихід» (відмотування) від вим. Тепер у вас з'явився вираз, який "повертається назад", тому ви можете використовувати властивість destineViewController в PripravForSegue VC2 і змінити будь-яке властивість VC1, перш ніж він повернеться назад.

  • Ще один варіант використання раскадровки Undwind (Exit) - ви можете використовувати метод, який ви написали в VC1

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

    І в PripravForSegue VC1 ви можете змінити будь-яку властивість, якою ви хочете поділитися.

В обох варіантах ви можете встановити властивість тегу кнопки та перевірити її у підготовці ForSegue.

Сподіваюся, я щось додав до дискусії.

:) Ура.


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];
 }

29

Це дуже стара відповідь, і це анти-шаблон, будь ласка, використовуйте делегатів. Не використовуйте цей підхід !!

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

2. Призначте SecondviewControllerпримірник цього контролера перегляду.

2. Закінчивши операцію з вибору, скопіюйте масив на перший контролер перегляду, Коли ви вивантажите SecondView, FirstView утримуватиме дані масиву.

Сподіваюся, що це допомагає.


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 ];

Слідкуйте за орфографічними помилками.


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

Зараз у переглядіControllerB.h

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

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

З точки зору КонтролерB.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;

}

З точки зоруControllerA.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
Я спробував використовувати ур-код у своєму проекті, але не в змозі отримати значення у viewcontrollerB. Чи можете ви сказати мені, що може бути проблемою?
X-Coder

1
@Ajitthala Чи можете ви вставити код у нове запитання? Я спробую вирішити ваше питання. :)
Анірудд Джоші

1
чи неправильно не використовувати методи init, а просто робити щось подібне до vcB.string = @ "asdf" з viewcontroller A?
khanh.tran.vinh

1
@ khanh.tran.vinh Залежить від того, використовуєте ви ARC чи ні.
Анірудд Джоші

21

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

Це схоже на вищезазначене, але без кнопок, міток тощо. Просто передання даних з одного перегляду в інший.

Налаштування розгортки

Є три частини.

  1. Відправник
  2. Segue
  3. Приймач

Це дуже простий макет виду з виступом між ними.


Дуже простий макет подання.  Примітка: Немає навігаційного контролера


Ось налаштування для відправника


Відправник


Ось налаштування для приймача.


Приймач


Нарешті, налаштування для segue.


Ідентифікатор 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)")

    }   
}

Ось як ви можете вирішити це, якщо ви хочете скористатися програмою Segue і не маєте своїх сторінок під навігаційним контролером.

Після запуску він повинен автоматично перейти до перегляду приймача і передати значення від відправника до приймача, відобразивши значення в консолі.

Привиди-привиди - класичні люди.


19

У моєму випадку я використовував однотонний клас, який може працювати як глобальний об'єкт, що дозволяє отримувати доступ до даних майже з будь-якого місця в додатку. По-перше, це побудувати клас одиночки. Будь ласка , зверніться до сторінки « Якою має бути моя Objective-C одноточечного виглядати? » А що я зробив , щоб зробити об'єкт глобально доступною просто імпортувати його в appName_Prefix.pchякій для застосування оператор імпорту в кожному класі. Для доступу до цього об’єкта та використання я просто реалізував метод класу для повернення спільного екземпляра, який містить власні змінні


Це правильна відповідь. Просто використовуйте синглтон як "модель". Зауважте, як каже Калеб, "модель для вашої програми може бути такою ж простою, як масив рядків" . Важливо відзначити, що робити одиночку в Свіфті - це справді тривіально . (Настільки просто, що тут навіть не варто згадувати - просто гугл.) Для нових програмістів варто зрозуміти, що зробити синглтон було справжнім болем у дупі . Однак одиночні ключі є абсолютно важливими для програмування iOS - все, що робить Apple, є сингтом. Ось чому Apple нарешті зробила тртвіальним (у Swift) зробити синглтон належним чином.
Fattie

1
Однак зауважте, що в ці дні (2016+) "усе є переглядом контейнера в iOS". Кожна річ, яку ви робите на екрані, зробить невеликий перегляд контейнера. Досить тривіально отримувати посилання на ланцюжки переглядів контейнерів "вгору-вниз" (хоча Apple зробить це простіше у майбутньому), і ви це робите майже для кожного перегляду контейнера. Отже, якщо ви все-таки зробили це - у вас є відповідь; немає потреби в одиночному. Представлення контейнерів ... stackoverflow.com/a/23403979/294884
Fattie

19

Швидкий 5

Ну відповідь Метта Прайса ідеально підходить для передачі даних, але я збираюся їх переписати, в останній версії Swift, тому що я вважаю, що нові програмісти вважають, що це кидає виклик складним завдяки новому синтаксису та методам / фреймворкам, так як оригінальна публікація є в Objective-C.

Існує кілька варіантів передачі даних між контролерами перегляду.

  1. Використання навігаційного контролера Push
  2. Використання Segue
  3. Використання Делегата
  4. Використання спостережувача сповіщень
  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 оголосити життєздатність з назвоюSomethingEnabled та роздрукувати її значення.

Крок 3. У перегляді ViewController проходить значенняSomethingEnabled під час проходження 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")
    }
}

Передача даних через спостерігача сповіщень : від 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 АвторизаціяCompletionBlock: ((Bool) -> ())? = {_ в}

Крок 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. Будь ласка, повідомте мені, якщо у вас є якісь питання щодо цього.


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"]; 

Тепер у вашому ПК, коли ви хочете перейти до нового ПК та передавати дані, ви створюєте новий намір і додаєте дані до його словника (збереженийInstanceState). Потім просто встановіть поточний намір заводу:

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

Створіть властивість далі 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

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

Про це я недавно писав повідомлення в блозі: Спільний код моделі . Ось короткий підсумок:

Спільні дані

Один із підходів - це обмін покажчиками на об'єкти моделі між контролерами перегляду.

  • Здійснення грубої ітерації на контролерах перегляду (в Навігації або Контролері панелей вкладок) для встановлення даних
  • Встановіть дані у PrepaForSegue (якщо дошка розкадрів) або 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 об'єкт нотаток, який ви можете, тому що він матиме зворотне відношення до чогось іншого, наприклад ноутбука. Якщо вам потрібні дані про ноутбук у 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, для використання яких вам потрібно використовувати делегування. це єдине рішення, яке ти можеш.

Ще одне рішення - використовувати шаблон однокласного класу, ініціалізувати його один раз і використовувати його у всій програмі.


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);
}

10

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

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

Наприклад

якщо ви декларуєте NSArray object *arrayXYZто, ви можете отримати доступ до нього в будь-якому контролері перегляду за допомогоюappDelegate.arrayXYZ


Це вибір на вибір для хакатону
Хай Фенг Као

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

Таким чином, ми можемо передавати дані з одного контролера перегляду в інший контролер перегляду ...


8

Мені подобається ідея об'єктів Model та Mock об'єктів на основі NSProxy здійснювати чи відкидати дані, якщо те, що вибирає користувач, може бути скасовано.

Передавати дані легко, оскільки це єдиний об'єкт або пара об'єктів, і якщо у вас є скажімо контролер UINavigationController, ви можете зберігати посилання на модель всередині, і всі контролери, що висунуті, можуть отримати доступ до них безпосередньо з контролера навігації.


8

Я бачив багато людей над ускладненням цього didSelectRowAtPathметоду. Я використовую основні дані у своєму прикладі.

- (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 рядки коду всередині методу, і ви закінчили.


6

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

На практиці, на мою думку, рекомендується лише декілька рішень:

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

Рішення, які я рекомендую НЕ використовувати:

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

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

Для зацікавлених я написав кілька статей, які більш глибоко вирішують ці моменти та висвітлюють різні недоліки:

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