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

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

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

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

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

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

30 ответов


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

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

Передача Данных Вперед

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

для этого примера у нас будет ViewControllerA и ViewControllerB

передать BOOL значение ViewControllerA to 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 to BOOL стоимостью YES.

передача данных вперед с помощью Segues

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

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

чтобы использовать BOOL С ViewControllerA to ViewControllerB мы бы сделали следующее:

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

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

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

  4. Далее нам нужно добавить метод ViewControllerA это вызывается, когда выполняется любой сегмент, из-за этого нам нужно определить, какой сегмент был вызван, а затем что-то сделать. В нашем примере мы проверим для "showDetailSegue" и если это выполняется, мы пройдем наш!--16--> значение 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 to BOOL стоимостью YES.

Передача Данных Обратно

для передачи данных из ViewControllerB to 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. In 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];
    

ссылки


Свифт

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

передача данных вперед к следующему-представление-контроллер

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

enter image description here

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

Первый Вид-Контроллер

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

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.

передача данных на предыдущий контроллер вида

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

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

enter image description here

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

Первый Вид-Контроллер

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

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

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

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

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

import UIKit

// protocol used for sending data back
protocol DataEnteredDelegate: class {
    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 находится вне класса контроллера вида.

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


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

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


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

  1. прямая инициализация после выделения другого класса.
  2. делегирование-для передачи данных назад
  3. уведомление - для передачи данных в несколько классов в одно время
  4. экономия NSUserDefaults - для доступа к ним позднее
  5. Singleton-классы
  6. базы данных и другие механизмы хранения как plist, etc.

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

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

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

- (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 параметра которые связаны с общей особенностью вы можете сохраните значения в классе модели и передайте этот 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.

надеюсь, что это помогает


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

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

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

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

Спасибо за помощь


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

предполагая, что мы используем раскадровку, первый контроллер устанавливает блок обратного вызова, например, во время подготовки 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);

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


во многих ответах есть хорошая информация, но ни один из них не решает вопрос полностью.

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

оригинальный плакат также спросил о синглтоны и использование AppDelegate. На эти вопросы необходимо ответить.

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

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

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

diagram of original problem

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

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

home inventory application diagram

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

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

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

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

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

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

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

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

  • Шаблон Observer
  • синглтоны

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

+ (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;
}

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

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

#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. Есть два способа наблюдать другой объект:--3-->

  • ключ-значение-наблюдение (кво)
  • NSNotificationCenter.

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

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

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

ссылки


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

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

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

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


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

  • делегат
  • уведомления
  • настройки пользователя
  • Синглтон

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

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

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

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

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

- С помощью раскадровки раскрутите (выход)

реализовать метод с аргументом UIStoryboardSegue в VC 1, например:

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

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

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

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

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

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

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

:) Ура.


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

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

С UINavigators и связок, существуют простые способы передачи информации на обслугу контроллер и получать информацию обратно. 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];
    }];
}

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

FirstViewController.h

@property (nonatomic, retain) NSString *str;

SecondViewController.h

@property (nonatomic, retain) NSString *str1;

FirstViewController.м

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

Я искал это решение в течение длительного времени, Наконец я нашел ее. Прежде всего объявите все объекты в вашем 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 должен иметь функцию setter с ним.

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

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

позаботьтесь о орфографических ошибках.


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

добавьте новый файл в файл проекта (Objective-C Protocol) - > New, теперь назовите его 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

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


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

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

Теперь в viewControllerB.h

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

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

в viewControllerB.м

#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.м

#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. создайте экземпляр первого контроллера вида во втором контроллере вида и сделайте его свойство @property (nonatomic,assign).

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

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

Надеюсь, Это Поможет.


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

например:

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

StrFirstValue = @"first";

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

1>Нам нужно создать объект string в 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 ];

В настоящее время я участвую в решении этой проблемы с открытым исходным кодом через проект под названием 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 и передать данные, вы создаете новое намерение и добавляете данные в свой словарь (savedInstanceState). Затем просто установите текущее намерение фабрики:

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

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


в моем случае я использовал одноэлементный класс, который может работать как глобальный объект, позволяющий получать доступ к данным почти отовсюду в приложении. Первое, что нужно сделать, это построить класс singleton. Пожалуйста, обратитесь к странице" Как должна выглядеть моя цель-c singleton? " И то, что я сделал, чтобы сделать объект глобально доступным, было просто импортировать его в appName_Prefix.pch который предназначен для применения инструкции import в каждом классе. Чтобы получить доступ к этому объекту и использовать, я просто реализовал метод класса для возвращает общий экземпляр, содержащий собственные переменные


создайте свойство на next view controller .h и определить геттер и сеттер.

добавить property В NextVC.h на nextVC

@property (strong, nonatomic) NSString *indexNumber;

добавить

@synthesize indexNumber; в NextVC.м

и последний

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

[email protected]"123";

[self.navigationController vc animated:YES];

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

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

Настройка Раскадровки

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

  1. в Отправитель
  2. Переходить
  3. Приемник

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


Very simple view layout.  Note : No navigation controller


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


The Sender


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


The Receiver


наконец, настройка для переходить.


The Segue Identifier


Контроллеры Вида

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

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

//
//  ViewControllerSender.swift
//  PassDataBetweenViews
//
//  Created by Chris Cantley on 8/25/15.
//  Copyright (c) 2015 Chris Cantley. All rights reserved.
//

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

    }

}

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

//
//  ViewControllerReceiver.swift
//  PassDataBetweenViews
//
//  Created by Chris Cantley on 8/25/15.
//  Copyright (c) 2015 Chris Cantley. All rights reserved.
//

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, и у вас нет ваших страниц под навигационным контроллером.

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

Ghost Busters is a classic folks.


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.м

@synthesize newsHeadlineStr;

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

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


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

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

 @property (nonatomic, strong) NSString *str1;

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

 @property (nonatomic, strong) NSString *str2;

синтезировать str2 в ViewControllerTwo.м

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

сделайте это в ViewControlerOne.м

 - (void)viewDidLoad
 {
   [super viewDidLoad];

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

 }

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

-(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.м

- (void)viewDidLoad
{
 [super viewDidLoad];
  NSLog(@"%@",str2);
}

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

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

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


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

я писал в блоге об этом некоторое время назад: Общий Код Модели. Вот краткое резюме:

Общие сведения

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

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

поскольку подготовка к segue является наиболее распространенным здесь примером:

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

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

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

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

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

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


    если вы хотите отправить данные от одного к другому 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.м

    #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.м

    #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
    

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


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

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


    Я видел много людей, усложняющих это с помощью 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 строки кода внутри метода, и вы сделали.


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

    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
        if ([segue.identifier isEqualToString:@"myIdentifer]) {
            NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
            myViewController *destViewController = segue.destinationViewController;
            destViewController.name = [object objectAtIndex:indexPath.row];
        }
    }
    

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

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

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

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

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

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

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