Pasar datos entre controladores de vista

1371

Soy nuevo en iOS y Objective-C y en todo el paradigma MVC y estoy atascado con lo siguiente:

Tengo una vista que actúa como un formulario de entrada de datos y quiero darle al usuario la opción de seleccionar múltiples productos. Los productos se enumeran en otra vista con UITableViewControlleray he habilitado varias selecciones.

Mi pregunta es, ¿cómo transfiero los datos de una vista a otra? Retendré las selecciones UITableViewen una matriz, pero ¿cómo paso eso a la vista del formulario de entrada de datos anterior para que pueda guardarse junto con los otros datos en Core Data al enviar el formulario?

He navegado y he visto a algunas personas declarar una matriz en el delegado de la aplicación. Leí algo sobre Singletons pero no entiendo qué son y leí algo sobre la creación de un modelo de datos.

¿Cuál sería la forma correcta de realizar esto y cómo lo haría?

Matt Price
fuente

Respuestas:

1683

Esta pregunta parece ser muy popular aquí en stackoverflow, así que pensé en intentar dar una mejor respuesta para ayudar a las personas que se inician en el mundo de iOS como yo.

Espero que esta respuesta sea lo suficientemente clara para que la gente la entienda y que no me haya perdido nada.

Transmitir datos hacia adelante

Transferencia de datos a un controlador de vista desde otro controlador de vista. Usaría este método si quisiera pasar un objeto / valor de un controlador de vista a otro controlador de vista que podría estar empujando a una pila de navegación.

Para este ejemplo, tendremos ViewControllerAyViewControllerB

Para pasar un BOOLvalor de ViewControllerAa ViewControllerBharíamos lo siguiente.

  1. en ViewControllerB.hcrear una propiedad para elBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. en lo ViewControllerAque necesita decirlo, ViewControllerBasí que use un

    #import "ViewControllerB.h"

    Luego, donde desea cargar la vista, por ejemplo. didSelectRowAtIndexo algunos en los IBActionque necesita establecer la propiedad ViewControllerBantes de insertarla en la pila de navegación.

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

    Esto establecerá isSomethingEnableden ViewControllerBal BOOLvalor YES.

Transmitir datos hacia adelante usando Segues

Si está utilizando Storyboards, lo más probable es que esté utilizando segues y necesitará este procedimiento para pasar los datos hacia adelante. Esto es similar a lo anterior, pero en lugar de pasar los datos antes de presionar el controlador de vista, utiliza un método llamado

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

Entonces, para pasar BOOLde ViewControllerAa ViewControllerB, haríamos lo siguiente:

  1. en ViewControllerB.hcrear una propiedad para elBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. en lo ViewControllerAque necesita decirlo, ViewControllerBasí que use un

    #import "ViewControllerB.h"
  3. Cree un segue de ViewControllerAa ViewControllerBen el guión gráfico y asígnele un identificador, en este ejemplo lo llamaremos"showDetailSegue"

  4. Luego, necesitamos agregar el método al ViewControllerAque se llama cuando se realiza cualquier segue, debido a esto necesitamos detectar qué segue se llamó y luego hacer algo. En nuestro ejemplo, verificaremos "showDetailSegue"y, si se realiza, pasaremos nuestro BOOLvalor aViewControllerB

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

    Si tiene sus vistas incrustadas en un controlador de navegación, debe cambiar el método anterior ligeramente a lo siguiente

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

    Esto establecerá isSomethingEnableden ViewControllerBal BOOLvalor YES.

Pasar datos de vuelta

Para pasar los datos de regreso ViewControllerBa ViewControllerA, debe usar Protocolos y Delegados o Bloques , este último puede usarse como un mecanismo débilmente acoplado para devoluciones de llamadas.

Para hacer esto haremos ViewControllerAun delegado de ViewControllerB. Esto permite ViewControllerBenviar un mensaje de vuelta para ViewControllerAque podamos enviar datos de vuelta.

Para ViewControllerAser delegado ViewControllerBdebe cumplir con ViewControllerBel protocolo que tenemos que especificar. Esto le dice ViewControllerAqué métodos debe implementar.

  1. En ViewControllerB.h, debajo de #import, pero arriba @interface, especifique el protocolo.

    @class ViewControllerB;
    
    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
  2. siguiente todavía en el ViewControllerB.hque necesita configurar una delegatepropiedad y sintetizar enViewControllerB.m

    @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. En ViewControllerBllamamos un mensaje en el delegatecuando hacemos estallar el controlador de vista.

    NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
  4. Eso es todo ViewControllerB. Ahora adentro ViewControllerA.h, diga ViewControllerAque importe ViewControllerBy cumpla con su protocolo.

    #import "ViewControllerB.h"
    
    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
  5. En ViewControllerA.mimplementar el siguiente método de nuestro protocolo

    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
        NSLog(@"This was returned from ViewControllerB %@",item);
    }
  6. Antes de pasar viewControllerBa la pila de navegación, debemos decir ViewControllerBque ViewControllerAes su delegado; de lo contrario, obtendremos un error.

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

Referencias

  1. Uso de la delegación para comunicarse con otros controladores de vista en la Guía de programación del controlador de vista
  2. Patrón delegado

Centro de NSNotification Es otra forma de pasar datos.

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

Transferencia de datos de una clase a otra (una clase puede ser cualquier controlador, administrador de red / sesión, subclase UIView o cualquier otra clase)

Los bloques son funciones anónimas.

Este ejemplo pasa datos del controlador B al controlador A

definir un bloque

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

agregue el controlador de bloque (escucha) donde necesita un valor (por ejemplo, necesita su respuesta API en ControllerA o necesita datos de ContorllerB en A)

// in ContollerA.m

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

Ir al controlador B

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

bloque de fuego

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

Otro ejemplo de trabajo para bloques

Matt Price
fuente
24
¿También tenemos que poner una @class ViewControllerB;definición superior a @protocol? Sin él, aparece el error "Tipo esperado" en ViewControllerB en la línea: - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; dentro de la @protocoldeclaración
alan-p
44
Esto funciona muy bien. Como dice alan-p, no olvides escribir @class ViewControllerB; arriba del protocolo, de lo contrario, recibirá el error "Se esperaba un tipo".
Andrew Davis
66
no necesita delegados para pasar de regreso, solo use unwind.
malhal
44
Cuando pongo "viewControllerB.delegate = self;" en ViewControllerB obtengo un error. Asignando a 'id <ViewControllerBDelegate>' del tipo incompatible 'ViewControllerB * const __strong', no estoy seguro de lo que estoy haciendo mal. ¿Alguien puede ayudar? Además, tuve que cambiar: initWithNib -> initWithNibName.
uplearnedu.com
44
si está usando NavigationController, debe usar [self.navigationController pushViewController:viewController animated:YES];en su lugar[self pushViewController:viewControllerB animated:YES];
Nazir
192

Rápido

Hay toneladas y toneladas de explicaciones aquí y alrededor de StackOverflow, pero si eres un principiante que solo intenta obtener algo básico para trabajar, intenta ver este tutorial de YouTube (es lo que finalmente me ayudó a entender cómo hacerlo).

Transferencia de datos al siguiente controlador de vista

El siguiente es un ejemplo basado en el video. La idea es pasar una cadena desde el campo de texto en el Primer controlador de vista a la etiqueta en el Segundo controlador de vista.

ingrese la descripción de la imagen aquí

Cree el diseño del guión gráfico en el Creador de interfaces. Para realizar la segue, simplemente Controlhaga clic en el botón y arrastre hacia el Segundo controlador de vista.

Primer controlador de vista

El código para el First View Controller es

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

}

Segundo controlador de vista

Y el código para el Segundo controlador de vista es

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
    }

}

No olvides

  • Conecte las salidas para el UITextFieldy el UILabel.
  • Establezca el primer y el segundo controlador de vista en los archivos Swift apropiados en IB.

Devolver datos al controlador de vista anterior

Para pasar datos desde el segundo controlador de vista al primer controlador de vista, utilice un protocolo y un delegado . Este video es un paseo muy claro sobre ese proceso:

El siguiente es un ejemplo basado en el video (con algunas modificaciones).

ingrese la descripción de la imagen aquí

Cree el diseño del guión gráfico en el Creador de interfaces. Nuevamente, para realizar la segue, simplemente Controlarrastre desde el botón al Segundo controlador de vista. Establezca el identificador de segue en showSecondViewController. Además, no olvide conectar las salidas y acciones utilizando los nombres en el siguiente código.

Primer controlador de vista

El código para el First View Controller es

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

Tenga en cuenta el uso de nuestro DataEnteredDelegateprotocolo personalizado .

Segundo controlador de vista y protocolo

El código para el segundo controlador de vista es

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

Tenga en cuenta que protocolestá fuera de la clase Controlador de vista.

Eso es. Al ejecutar la aplicación ahora, debería poder enviar datos desde el segundo controlador de vista al primero.

Suragch
fuente
Dadas algunas de las últimas actualizaciones de Swift, ¿sigue siendo un patrón común para implementar?
piofusco
44
La mayoría de las actualizaciones de Swift que he visto han sido cambios sintácticos relativamente menores, no cambios en la forma en que se pasan los datos entre los controladores de vista. Si me entero de algún cambio importante como ese, actualizaré mi respuesta.
Suragch
2
offtopic: iOS tiene una forma tan fea de pasar parámetros a los nuevos controladores de vista, increíble: debe establecer los parámetros no en un lugar cuando realiza la llamada, sino en otro. Android tiene un mejor enfoque en este sentido: cuando inicia una actividad, puede pasar cualquier información (bueno, casi) a través de su intención inicial. Fácil. No hay necesidad de lanzar o algo. También es esencial pasar los valores de retorno a la persona que llama, no es necesario delegar. Por supuesto, también es posible usar enfoques feos, no hay problema))
Mixaz
1
@Himanshu, primero obtenga una referencia al segundo controlador de vista. Luego actualice la variable pública que contiene.
Suragch
8
@Miel. Creo que la palabra "delegado" es confusa. Déjame usar la palabra "trabajador". El "trabajador" (primer controlador de vista) hace lo que el "jefe" (segundo controlador de vista) le dice que haga. El "jefe" no sabe quién será su "trabajador"; Podría ser cualquiera. Entonces, en el primer controlador de vista (clase "trabajador"), dice, seré tu "trabajador". Me dices qué escribir en la etiqueta y lo haré por ti. Por lo tanto, secondViewController.delegate = selfsignifica "Acepto ser el trabajador del jefe". Vea esta respuesta para otro ejemplo y más explicación.
Suragch
136

La M en MVC es para "Modelo" y en el paradigma MVC el rol de las clases de modelo es administrar los datos de un programa. Un modelo es lo opuesto a una vista: una vista sabe cómo mostrar datos, pero no sabe nada sobre qué hacer con los datos, mientras que un modelo sabe todo sobre cómo trabajar con datos, pero nada sobre cómo mostrarlos. Los modelos pueden ser complicados, pero no tienen por qué serlo: el modelo para su aplicación puede ser tan simple como una serie de cadenas o diccionarios.

El papel de un controlador es mediar entre la vista y el modelo. Por lo tanto, necesitan una referencia a uno o más objetos de vista y uno o más objetos de modelo. Digamos que su modelo es una matriz de diccionarios, y cada diccionario representa una fila en su tabla. La vista raíz de su aplicación muestra esa tabla, y podría ser responsable de cargar la matriz desde un archivo. Cuando el usuario decide agregar una nueva fila a la tabla, toca un botón y su controlador crea un nuevo diccionario (mutable) y lo agrega a la matriz. Para completar la fila, el controlador crea un controlador de vista detallada y le da el nuevo diccionario. El controlador de vista detallada completa el diccionario y regresa. El diccionario ya forma parte del modelo, por lo que no es necesario que suceda nada más.

Caleb
fuente
95

Hay varias formas de recibir datos en una clase diferente en iOS. Por ejemplo -

  1. Inicialización directa después de la asignación de otra clase.
  2. Delegación: para devolver datos
  3. Notificación: para transmitir datos a múltiples clases a la vez
  4. Guardar en NSUserDefaults- para acceder más tarde
  5. Clases Singleton
  6. Bases de datos y otros mecanismos de almacenamiento como plist, etc.

Pero para el escenario simple de pasar un valor a una clase diferente cuya asignación se realiza en la clase actual, el método más común y preferido sería el establecimiento directo de valores después de la asignación. Esto se hace de la siguiente manera: -

Podemos entenderlo usando dos controladores: Controller1 y Controller2

Suponga que en la clase Controller1 desea crear el objeto Controller2 y empujarlo con un valor de cadena que se pasa. Esto se puede hacer así:

- (void)pushToController2 {

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

En la implementación de la clase Controller2 habrá esta función como-

@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

También puede establecer directamente las propiedades de la clase Controller2 de la misma manera que esto:

- (void)pushToController2 {

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

Para pasar múltiples valores puede usar los múltiples parámetros como: -

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

O si necesita pasar más de 3 parámetros relacionados con una característica común, puede almacenar los valores en una clase de Modelo y pasar ese objeto de modelo a la siguiente clase

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

En resumen, si quieres ...

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.

Espero que esto ayude

nacido loco
fuente
84

Después de más investigación, parecía que Protocolos y Delegados es la forma correcta / Apple prefiere hacer esto.

Terminé usando este ejemplo

Compartir datos entre controladores de vista y otros objetos @ iPhone Dev SDK

Funcionó bien y me permitió pasar una cadena y una matriz hacia adelante y hacia atrás entre mis puntos de vista.

Gracias por toda tu ayuda

Matt Price
fuente
3
no use protocolos y delegados, solo use desenrollar.
malhal
1
@malhal ¿Qué pasa si no usas guiones gráficos?
Evan R
También odio los protocolos inútiles y los delegados. @malhal
DawnSong
@EvanR Puede crear y realizar segues en el código. Todo es lo mismo.
DawnSong
1
Esencialmente, el control de calidad completo en esta página es "de los viejos tiempos antes de las vistas de contenedor". Nunca en un millón de años se molestaría con protocolos o delegados ahora. De todos modos, cada pequeña cosa que haces en cualquier pantalla es una vista de contenedor, por lo que la pregunta ya no existe: ya tienes todas las referencias "arriba y abajo" de todas las vistas de contenedor.
Fattie
66

Encuentro la versión más simple y elegante con bloques de paso. Nombraremos el controlador de vista que espera los datos devueltos como "A" y el controlador de vista devuelto como "B". En este ejemplo, queremos obtener 2 valores: primero de Tipo1 y segundo de Tipo2.

Suponiendo que usemos Storyboard, el primer controlador establece el bloqueo de devolución de llamada, por ejemplo, durante la preparación de 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);
        };

    }
}

y el controlador de vista "B" debe declarar la propiedad de devolución de llamada, BViewController.h:

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

Que en el archivo de implementación BViewController.m después de haber deseado los valores para devolver nuestra devolución de llamada debe llamarse:

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

Una cosa para recordar es que el uso del bloque a menudo necesita administrar referencias fuertes y __ débiles como se explica aquí

Leszek Zarna
fuente
¿Por qué el valor no es un parámetro para el bloque de devolución de llamada en lugar de ser una propiedad separada?
Timuçin
56

Hay buena información en muchas de las respuestas dadas, pero ninguna aborda la pregunta por completo.

La pregunta se refiere a pasar información entre los controladores de vista. El ejemplo específico que se ofrece pregunta sobre cómo pasar información entre vistas, pero dada la novedad autodeclarada a iOS, el póster original probablemente significó entre viewControllers, no entre vistas (sin ninguna participación de los ViewControllers). Parece que todas las respuestas se centran en dos controladores de vista, pero ¿qué sucede si la aplicación evoluciona para necesitar involucrar a más de dos controladores de vista en el intercambio de información?

El póster original también preguntó sobre Singletons y el uso de AppDelegate . Estas preguntas deben ser respondidas.

Para ayudar a cualquier otra persona que vea esta pregunta, que quiera una respuesta completa, intentaré proporcionarla.

Escenarios de aplicación

En lugar de tener una discusión abstracta muy hipotética, ayuda tener en mente aplicaciones concretas. Para ayudar a definir una situación de controlador de dos vistas y una situación de controlador de más de dos vistas, voy a definir dos escenarios de aplicación concretos.

Escenario uno: un máximo de dos controladores de vista necesitan compartir información. Ver diagrama uno.

diagrama del problema original

Hay dos controladores de vista en la aplicación. Hay un ViewControllerA (Formulario de entrada de datos) y View Controller B (Lista de productos). Los elementos seleccionados en la lista de productos deben coincidir con los elementos que se muestran en el cuadro de texto en el formulario de entrada de datos. En este escenario, ViewControllerA y ViewControllerB deben comunicarse directamente entre sí y con ningún otro controlador de vista.

Escenario dos : más de dos controladores de vista necesitan compartir la misma información. Ver diagrama dos.

diagrama de solicitud de inventario de la casa

Hay cuatro controladores de vista en la aplicación. Es una aplicación basada en pestañas para administrar el inventario de viviendas. Tres controladores de vista presentan vistas filtradas de manera diferente de los mismos datos:

  • ViewControllerA - Artículos de lujo
  • ViewControllerB - Artículos no asegurados
  • ViewControllerC - Inventario completo de la casa
  • ViewControllerD - Agregar nuevo formulario de artículo

Cada vez que se crea o edita un elemento individual, también debe sincronizarse con los otros controladores de vista. Por ejemplo, si agregamos un bote en ViewControllerD, pero aún no está asegurado, entonces el bote debe aparecer cuando el usuario vaya a ViewControllerA (Artículos de lujo), y también ViewControllerC (Inventario de toda la casa), pero no cuando el usuario vaya a ViewControllerB (Artículos no asegurados). Debemos preocuparnos no solo de agregar nuevos elementos, sino también de eliminar elementos (que se pueden permitir desde cualquiera de los cuatro controladores de vista), o editar elementos existentes (que se pueden permitir desde el "Agregar nuevo formulario de elementos", reutilizando el mismo para editar).

Dado que todos los controladores de vista necesitan compartir los mismos datos, los cuatro controladores de vista deben permanecer sincronizados y, por lo tanto, debe haber algún tipo de comunicación con todos los demás controladores de vista, siempre que un solo controlador de vista cambie los datos subyacentes. Debería ser bastante obvio que no queremos que cada controlador de vista se comunique directamente entre sí en este escenario. En caso de que no sea obvio, considere si teníamos 20 controladores de vista diferentes (en lugar de solo 4). ¿Qué tan difícil y propenso a errores sería notificar a cada uno de los otros 19 controladores de vista cada vez que un controlador de vista realiza un cambio?

Las Soluciones: Delegados y el Patrón de Observador, y Singletons

En el escenario uno, tenemos varias soluciones viables, ya que otras respuestas han dado

  • segues
  • delegados
  • establecer propiedades en los controladores de vista directamente
  • NSUserDefaults (en realidad, una mala elección)

En el escenario dos, tenemos otras soluciones viables:

  • Patrón de observador
  • Singletons

Un singleton es una instancia de una clase, siendo esa instancia la única instancia que existe durante su vida útil. Un singleton recibe su nombre del hecho de que es la instancia única. Normalmente, los desarrolladores que usan singletons tienen métodos de clase especiales para acceder a ellos.

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

Ahora que entendemos qué es un singleton, analicemos cómo se ajusta un singleton en el patrón de observación. El patrón de observador se usa para que un objeto responda a los cambios de otro objeto. En el segundo escenario, tenemos cuatro controladores de vista diferentes, que todos quieren saber acerca de los cambios en los datos subyacentes. Los "datos subyacentes" deben pertenecer a una sola instancia, un singleton. El "saber acerca de los cambios" se logra al observar los cambios realizados en el singleton.

La aplicación de inventario de inicio tendría una única instancia de una clase que está diseñada para administrar una lista de artículos de inventario. El gerente administraría una colección de artículos para el hogar. La siguiente es una definición de clase para el administrador de datos:

#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

Cuando la colección de artículos de inventario de la casa cambia, los controladores de vista deben ser conscientes de este cambio. La definición de clase anterior no hace obvio cómo sucederá esto. Necesitamos seguir el patrón del observador. Los controladores de vista deben observar formalmente el sharedManager. Hay dos formas de observar otro objeto:

  • Observación de valor clave (KVO)
  • NSNotificationCenter.

En el escenario dos, no tenemos una sola propiedad del HouseholdInventoryManager que pueda observarse usando KVO. Debido a que no tenemos una sola propiedad que sea fácilmente observable, el patrón de observador, en este caso, debe implementarse usando NSNotificationCenter. Cada uno de los cuatro controladores de vista se suscribirá a las notificaciones, y sharedManager enviará notificaciones al centro de notificaciones cuando sea apropiado. El administrador de inventario no necesita saber nada sobre los controladores de vista o las instancias de otras clases que puedan estar interesados ​​en saber cuándo cambia la colección de artículos de inventario; NSNotificationCenter se encarga de estos detalles de implementación. Los controladores de vista simplemente se suscriben a las notificaciones, y el administrador de datos simplemente publica las notificaciones.

Muchos programadores principiantes aprovechan el hecho de que siempre hay exactamente un Delegado de aplicaciones en la vida útil de la aplicación, que es accesible a nivel mundial. Los programadores principiantes utilizan este hecho para introducir objetos y funcionalidades en la aplicación Delegar como una conveniencia para acceder desde cualquier otro lugar de la aplicación. El hecho de que AppDelegate sea un singleton no significa que deba reemplazar a todos los demás. Esta es una práctica pobre ya que pone demasiada carga en una clase, rompiendo buenas prácticas orientadas a objetos. Cada clase debe tener un papel claro que se explica fácilmente, a menudo solo por el nombre de la clase.

Cada vez que su Delegado de aplicaciones comience a hincharse, comience a eliminar la funcionalidad en singletons. Por ejemplo, el Core Data Stack no debe dejarse en AppDelegate, sino que debe colocarse en su propia clase, una clase coreDataManager.

Referencias

Jason Cross
fuente
41

El OP no mencionó los controladores de vista, pero muchas de las respuestas sí, que quería intervenir con lo que algunas de las nuevas características de LLVM permiten hacer esto más fácil cuando se quiere pasar datos de un controlador de vista a otro y luego obteniendo algunos resultados de vuelta.

La secuencia de guiones gráficos, los bloques ARC y LLVM lo hacen más fácil que nunca para mí. Algunas respuestas mencionadas anteriormente ya incluyen guiones gráficos y segues, pero aún se basan en la delegación. Definir delegados ciertamente funciona, pero a algunas personas les puede resultar más fácil pasar punteros o bloques de código.

Con UINavigators y segues, hay formas fáciles de pasar información al controlador subordinado y recuperar la información. ARC simplifica el paso de punteros a elementos derivados de NSObjects, por lo que si desea que el controlador subordinado agregue / cambie / modifique algunos datos por usted, páselo como puntero a una instancia mutable. Los bloques facilitan la aprobación de acciones, por lo que si desea que el controlador subordinado invoque una acción en su controlador de nivel superior, páselo un bloque. Define el bloque para aceptar cualquier número de argumentos que tenga sentido para usted. También puede diseñar la API para usar múltiples bloques si eso se adapta mejor a las cosas.

Aquí hay dos ejemplos triviales del pegamento segue. El primero es sencillo y muestra un parámetro pasado para la entrada, el segundo para la salida.

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

Este segundo ejemplo muestra pasar un bloque de devolución de llamada para el segundo argumento. Me gusta usar bloques porque mantiene los detalles relevantes muy juntos en la fuente, la fuente de nivel superior.

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

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

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

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

Pasar datos desde ViewController 2 (destino) a viewController 1 (Fuente) es lo más interesante. Suponiendo que use storyBoard, esas son todas las formas en que me enteré:

  • Delegar
  • Notificación
  • Valores predeterminados del usuario
  • único

Esos ya se discutieron aquí.

Descubrí que hay más formas:

-Uso de devoluciones de llamada de bloque:

úselo en el prepareForSeguemétodo en el VC1

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

-Uso de guiones gráficos Unwind (Salir)

Implemente un método con un argumento UIStoryboardSegue en VC 1, como este:

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

En el storyBoard, enganche el botón "regresar" al botón verde Salir (Unwind) del vc. Ahora tiene un segmento que "retrocede" para que pueda usar la propiedad destinationViewController en prepareForSegue de VC2 y cambiar cualquier propiedad de VC1 antes de que regrese.

  • Otra opción de usar storyboards Undwind (Exit): puede usar el método que escribió en VC1

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

    Y en prepareForSegue de VC1 puede cambiar cualquier propiedad que desee compartir.

En ambas opciones de desenrollado, puede establecer la propiedad de etiqueta del botón y verificarla en prepareForSegue.

Espero haber agregado algo a la discusión.

:) Salud.

Yevgeni
fuente
40

Existen múltiples métodos para compartir datos.

  1. Siempre puedes compartir datos usando NSUserDefaults. Establezca el valor que desea compartir con respecto a una clave de su elección y obtenga el valor NSUserDefaultasociado a esa clave en el siguiente controlador de vista.

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
  2. Simplemente puede crear una propiedad en viewcontrollerA. Cree un objeto de viewcontrollerAin viewcontrollerBy asigne el valor deseado a esa propiedad.

  3. También puede crear delegados personalizados para esto.

Anubrata Santra
fuente
30
El propósito típico de NSUserDefaults es almacenar las preferencias del usuario que persisten entre las ejecuciones de la aplicación, por lo que todo lo almacenado aquí permanecerá aquí a menos que se elimine explícitamente. Es una muy mala idea usar esto para pasar información entre los controladores de vista (o cualquier otro objeto) en una aplicación.
José González
30

Si desea pasar datos de un controlador a otro, pruebe este código

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];
 }
usuario2998756
fuente
29

Esta es una respuesta muy antigua y es anti patrón, por favor use delegados. ¡No uses este enfoque!

1. Cree la instancia del primer controlador de vista en el segundo controlador de vista y haga su propiedad @property (nonatomic,assign).

2. Asigne la SecondviewControllerinstancia de este controlador de vista.

2. Cuando finalice la operación de selección, copie la matriz en el primer controlador de vista. Cuando descargue SecondView, FirstView contendrá los datos de la matriz.

Espero que esto ayude.

kaar3k
fuente
2
No creo que esta sea la forma correcta de hacerlo, ya que crea un vínculo muy complejo entre los controladores de vista. Realmente no se apega a MVC.
Matt Price
1
Si desea seguir estrictamente MVC, use NSNotificationCenter , se puede llamar a un método desde ViewControllerA a ViewControllerB, verifique que esto podría ser útil para usted
kaar3k
28

Estuve buscando esta solución por mucho tiempo, Atlast la encontré. En primer lugar, declare todos los objetos en su archivo SecondViewController.h como

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

Ahora en su archivo de implementación asigne la memoria para esos objetos como este

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

Ahora ha asignado la memoria para Arrayy el objeto. ahora puedes llenar ese recuerdo antes de presionar estoViewController

Vaya a su SecondViewController.h y escriba dos métodos

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

en el archivo de implementación puede implementar la función

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

esperando que CustomObjectdebe tener una función de establecimiento con él.

ahora tu trabajo básico está hecho. vaya al lugar donde desea presionar SecondViewControllery haga lo siguiente

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

Tenga cuidado con los errores ortográficos.

AsifHabib
fuente
24

Esta no es la forma de hacerlo, debe usar delegados, supongo que tenemos dos controladores de vista ViewController1 y ViewController2 y esta verificación está en la primera y cuando cambia su estado, desea hacer algo en ViewController2, para Para lograrlo de la manera adecuada, debe hacer lo siguiente:

Agregue un nuevo archivo a su proyecto (Objective-C Protocol) File -> New, ahora llámelo ViewController1Delegate o lo que quiera y escríbalos entre las directivas @interface y @end

@optional

- (void)checkStateDidChange:(BOOL)checked;

Ahora ve a ViewController2.h y agrega

#import "ViewController1Delegate.h"

luego cambie su definición a

@interface ViewController2: UIViewController<ViewController1Delegate>

Ahora vaya a ViewController2.m y dentro de la implementación agregue:

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

Ahora vaya a ViewController1.h y agregue la siguiente propiedad:

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

Ahora, si está creando ViewController1 dentro de ViewController2 después de algún evento, entonces debe hacerlo de esta manera utilizando archivos NIB:

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

Ahora está todo listo, siempre que detecte el evento de verificación cambiado en ViewController1, todo lo que tiene que hacer es lo siguiente

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

Por favor, dígame si hay algo que no esté claro si no entendí su pregunta correctamente.

Boda Taljo
fuente
23

Si desea enviar datos de uno a otro viewController, aquí hay una manera de hacerlo:

Digamos que tenemos viewControllers: viewControllerA y viewControllerB

Ahora en viewControllerB.h

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

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

En viewControllerB.m

#import "viewControllerB.h"

@implementation viewControllerB

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

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

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

}

En viewControllerA.m

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

@implementation viewControllerA

- (void)someMethod {

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

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

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

}

Así es como puede pasar datos desde viewControllerA a viewControllerB sin establecer ningún delegado. ;)

Aniruddh Joshi
fuente
1
Intenté usar el código ur en mi proyecto, pero no puedo obtener los valores en viewcontrollerB. ¿Me puede decir cuál podría ser el problema?
The X-Coder
1
@ Ajitthala ¿Puedes pegar tu código en una nueva pregunta? Intentaré resolver tu problema. :)
Aniruddh Joshi
1
¿Es incorrecto no usar métodos de inicio y hacer algo como vcB.string = @ "asdf" desde viewcontroller A?
khanh.tran.vinh
1
@ khanh.tran.vinh Depende de si está utilizando ARC o no.
Aniruddh Joshi
21

Sé que este es un tema difícil pero para aquellos que buscan responder a esta pregunta con una inclinación SWIFT y quieren un ejemplo básico, aquí está mi método de referencia para pasar datos si está utilizando un segue para moverse.

Es similar a lo anterior pero sin los botones, etiquetas y demás. Simplemente pasando datos de una vista a la siguiente.

Configurar el guión gráfico

Hay tres partes

  1. El remitente
  2. La segue
  3. El receptor

Este es un diseño de vista muy simple con una segue entre ellos.


Diseño de vista muy simple.  Nota: sin controlador de navegación


Aquí está la configuración para el remitente


El remitente


Aquí está la configuración para el receptor.


El receptor


Por último, la configuración para el segue.


El identificador de Segue


Los controladores de vista

Estamos manteniendo esto simple para que no haya botones, no acciones, simplemente estamos moviendo datos del remitente al receptor cuando se carga la aplicación y luego enviando el valor transmitido a la consola.

Esta página toma el valor cargado inicialmente y lo pasa.

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

    }

}

Esta página solo envía el valor de la variable a la consola cuando se carga. En este punto, nuestra película favorita debería estar en esa variable.

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)")

    }   
}

Así es como puede abordarlo si desea usar una segue y no tiene sus páginas bajo un controlador de navegación.

Una vez que se ejecuta, debe cambiar a la vista del receptor automáticamente y pasar el valor del remitente al receptor, mostrando el valor en la consola.

Ghost Busters es una gente clásica.

Christopher Wade Cantley
fuente
19

En mi caso, utilicé una clase singleton que puede funcionar como un objeto global que permite acceder a los datos desde casi cualquier lugar de la aplicación. Lo primero es construir una clase singleton. Por favor refiérase a la página, " ¿Qué le debo mi Objective-C Singleton parece? " Y lo que hice para que el objeto accesible a nivel mundial fue simplemente importar en appName_Prefix.pchque es para la aplicación de la declaración de importación de cada clase. Para acceder a este objeto y usarlo, simplemente implementé el método de clase para devolver la instancia compartida, que contiene sus propias variables

Petershine
fuente
Esta es la respuesta correcta. Simplemente use un singleton como "modelo". Tenga en cuenta que, como dice Caleb, "el modelo para su aplicación podría ser tan simple como una serie de cadenas" . Es fundamental tener en cuenta que hacer un singleton en Swift es realmente trivial . (Tan simple que ni siquiera vale la pena mencionarlo aquí, solo google.) Para los nuevos programadores, vale la pena entender que hacer un singleton solía ser un verdadero dolor de cabeza . Sin embargo, los singletons son absolutamente centrales para la programación de iOS: todo lo que Apple hace es un singleton. Es por eso que Apple finalmente lo hizo trtvial (en Swift) para hacer singletons correctamente.
Fattie
1
Sin embargo, tenga en cuenta que en estos días (2016+) "todo es una vista de contenedor en iOS". Cada cosa que haces en la pantalla haces una pequeña vista de contenedor. Es bastante trivial obtener las referencias "hacia arriba y hacia abajo" de las vistas de contenedor (aunque Apple lo hará más fácil en el futuro), y de todos modos lo haces para casi todas las vistas de contenedor. Entonces, si lo has hecho de todos modos, tienes la respuesta; No hay necesidad de un singleton. Introducción a la vista de contenedores ... stackoverflow.com/a/23403979/294884
Fattie
19

Swift 5

Bueno, la respuesta de Matt Price está perfectamente bien para pasar datos, pero voy a reescribirla en la última versión de Swift porque creo que los nuevos programadores encuentran que es bastante difícil debido a la nueva sintaxis y métodos / marcos, ya que la publicación original está en Objective-C.

Hay múltiples opciones para pasar datos entre los controladores de vista.

  1. Uso del controlador de navegación Push
  2. Usando Segue
  3. Usar delegado
  4. Usar el observador de notificaciones
  5. Usando bloque

Voy a reescribir su lógica en Swift con el último marco de iOS


Transferencia de datos a través del controlador de navegación Push : de ViewControllerA a ViewControllerB

Paso 1. Declarar variable en ViewControllerB

var isSomethingEnabled = false

Paso 2. Imprimir variable en el método ViewControllerB 'ViewDidLoad

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

Paso 3. En ViewController, pase los datos mientras empuja a través del controlador de navegación

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

Así que aquí está el código completo para:

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

Pasar datos a través de Segue : de ViewControllerA a ViewControllerB

Paso 1. Cree Segue de ViewControllerA a ViewControllerB y proporcione Identifier = showDetailSegue en Storyboard como se muestra a continuación

ingrese la descripción de la imagen aquí

Paso 2. En ViewControllerB Declare un viable llamado isSomethingEnabled e imprima su valor.

Paso 3. En ViewController, un pase es el valor de SomeEnabled al pasar Segue

Así que aquí está el código completo para:

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

Pasar datos a través del delegado : de ViewControllerB a ViewControllerA

Paso 1. Declare el protocolo ViewControllerBDelegate en el archivo ViewControllerB pero fuera de la clase

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

Paso 2. Declarar instancia de variable Delegate en ViewControllerB

var delegate: ViewControllerBDelegate?

Paso 3. Enviar datos para delegar dentro del método viewDidLoad de ViewControllerB

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

Paso 4. Confirme ViewControllerBDelegate en ViewControllerA

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

Paso 5. Confirme que implementará delegado en 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)
            }
        }

Paso 6. Implemente el método delegado para recibir datos en ViewControllerA

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

Así que aquí está el código completo para:

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

Paso de datos a través del observador de notificaciones : de ViewControllerB a ViewControllerA

Paso 1. Establecer y publicar datos en el Observador de notificaciones en ViewControllerB

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

Paso 2. Agregar el Observador de notificaciones en ViewControllerA

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

Paso 3. Recibir el valor de los datos de notificación en ViewControllerA

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

Así que aquí está el código completo para:

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

Paso de datos a través del bloque : de ViewControllerB a ViewControllerA

Paso 1. Declarar bloque en ViewControllerB

autorizacion varCompletionBlock: ((Bool) -> ())? = {_ en}

Paso 2. Establecer datos en bloque en ViewControllerB

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

Paso 3. Recibir datos de bloque en ViewControllerA

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

Así que aquí está el código completo para:

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

Puede encontrar una aplicación de muestra completa en mi GitHub . Avíseme si tiene alguna pregunta al respecto.

swiftBoy
fuente
18

Pasar datos entre FirstViewController a SecondViewController como se muestra a continuación

Por ejemplo:

Valor de cadena de FirstViewController como

StrFirstValue = @"first";

para que podamos pasar este valor en segunda clase usando el siguiente paso

1> Necesitamos crear un objeto de cadena en el archivo SecondViewController.h

NSString *strValue;

2> Necesita declarar la propiedad como se muestra a continuación en la declaración en el archivo .h

@property (strong, nonatomic)  NSString *strSecondValue;

3> Necesita sintetizar ese valor en el archivo FirstViewController.m debajo de la declaración del encabezado

@synthesize strValue;

y en FirstViewController.h:

@property (strong, nonatomic)  NSString *strValue;

4> En FirstViewController, desde qué método navegamos a la segunda vista, escriba el código siguiente en ese método.

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

[secondView setStrSecondValue:StrFirstValue];

[self.navigationController pushViewController:secondView animated:YES ];
Chris Alan
fuente
Después de estar en el SecondViewController, ¿cómo devuelve los datos al FirstViewController?
bruno
18

Actualmente estoy contribuyendo a una solución de código abierto para este problema a través de un proyecto llamado MCViewFactory, que se puede encontrar aquí:

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

La idea es imitar el paradigma de intención de Android, usando una fábrica global para administrar qué vista está mirando y usando "intentos" para cambiar y pasar datos entre vistas. Toda la documentación está en la página de Github, pero aquí hay algunos aspectos destacados:

Configura todas sus vistas en archivos .XIB y las registra en el delegado de la aplicación, mientras inicializa la fábrica.

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

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

Ahora, en su VC, cada vez que quiera pasar a un nuevo VC y pasar datos, crea una nueva intención y agrega datos a su diccionario (salvadoInstanceState). Luego, solo configure la intención actual de fábrica:

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

Todas sus vistas que se ajusten a esto deben ser subclases de MCViewController, lo que le permite anular el nuevo método onResume: que le permite acceder a los datos que ha pasado.

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

Espero que algunos de ustedes encuentren esta solución útil / interesante.


fuente
Entonces, ¿todos los objetos del controlador podrían obtener / configurar todos los diccionarios registrados en cualquier ámbito? Vota abajo esto.
Itachi
15

Cree la propiedad en siguiente view controller .hy defina getter y setter.

Agregue esto propertyen NextVC.h en nextVC

@property (strong, nonatomic) NSString *indexNumber;

Añadir

@synthesize indexNumber; en NextVC.m

Y última

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

vc.indexNumber=@"123";

[self.navigationController vc animated:YES];
Vivek Yadav
fuente
11

Hay muchas formas de hacer esto y es importante elegir la correcta. Probablemente una de las decisiones arquitectónicas más importantes radica en cómo se compartirá o accederá el código del modelo a través de la aplicación.

Escribí una publicación de blog sobre esto hace un tiempo: Compartir el código del modelo . Aquí hay un breve resumen:

Datos compartidos

Un enfoque es compartir punteros a los objetos del modelo entre los controladores de vista.

  • La iteración de fuerza bruta en los controladores de vista (en Navegación o Controlador de barra de pestañas) para establecer los datos
  • Establecer datos en prepareForSegue (si son guiones gráficos) o init (si es programático)

Como prepararse para segue es lo más común, aquí hay un ejemplo:

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

Acceso independiente

Otro enfoque es manejar una pantalla llena de datos a la vez y, en lugar de acoplar los controladores de vista entre sí, acopla cada controlador de vista a una sola fuente de datos a la que pueden acceder de forma independiente.

La forma más común en que he visto esto es una instancia de singleton . Entonces, si su objeto singleton era DataAccess, podría hacer lo siguiente en el método viewDidLoad de UIViewController:

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

Existen herramientas adicionales que también ayudan a transmitir datos:

  • Observación de valores clave
  • NSNotification
  • Datos centrales
  • NSFetchedResultsController
  • Fuente de datos

Datos centrales

Lo bueno de Core Data es que tiene relaciones inversas. Entonces, si solo desea darle a NotesViewController el objeto de notas, puede hacerlo porque tendrá una relación inversa con algo más como el cuaderno. Si necesita datos en el bloc de notas en NotesViewController, puede volver al gráfico de objetos haciendo lo siguiente:

let notebookName = note.notebook.name

Lea más sobre esto en mi blog: Compartir el código del modelo

Korey Hinton
fuente
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;
Mohsin Sabasara
fuente
10

La delegación es la única solución para realizar tales operaciones cuando está utilizando archivos .xib, sin embargo, todas las respuestas descritas anteriormente son para storyboardarchivos .xibs que necesita usar delegación. Esa es la única solución que puedes.

Otra solución es usar el patrón de clase singleton, inicializarlo una vez y usarlo en toda su aplicación.

usuario2786888
fuente
10

si desea pasar datos de ViewControlerOne a ViewController, pruebe estos dos ...

hacer esto en ViewControlerOne.h

 @property (nonatomic, strong) NSString *str1;

hacer esto en ViewControllerTwo.h

 @property (nonatomic, strong) NSString *str2;

Sintetizar str2 en ViewControllerTwo.m

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

hacer esto en ViewControlerOne.m

 - (void)viewDidLoad
 {
   [super viewDidLoad];

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

 }

en el evento de clic de botones haz esto ...

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

hacer esto en ViewControllerTwo.m

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

Puede guardar datos en el delegado de aplicaciones para acceder a ellos a través de los controladores de vista en su aplicación. Todo lo que tiene que hacer es crear una instancia compartida de delegado de aplicaciones

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

Por ejemplo

si declaras un NSArray object *arrayXYZ, puedes acceder a él en cualquier controlador de vistaappDelegate.arrayXYZ

ak_tyagi
fuente
Es el método de elección para el hackathon
Hai Feng Kao
9

Si desea enviar datos de uno a otro viewController, aquí hay una manera de hacerlo:

Digamos que tenemos viewControllers: ViewController y NewViewController.

en 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

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

}

En NewViewController.h

#import <UIKit/UIKit.h>

@interface NewViewController : UITableViewController
{
    NSArray *arrayList;

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

}

@property(nonatomic, retain)NSArray *arrayList;

@end

En 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

De esta manera, podemos pasar los datos de un controlador de vista a otro controlador de vista ...

Sabs
fuente
8

Me gusta la idea de que los objetos Modelo y los objetos Simulados basados ​​en NSProxy confirmen o descarten datos si lo que el usuario selecciona puede cancelarse.

Es fácil pasar datos ya que es un objeto único o un par de objetos y si tiene, digamos, el controlador UINavigationController, puede mantener la referencia al modelo dentro y todos los controladores de vista empujados pueden acceder directamente desde el controlador de navegación.

Ben Sinclair
fuente
8

He visto a muchas personas complicar esto usando el didSelectRowAtPathmétodo. Estoy usando Core Data en mi ejemplo.

- (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 líneas de código dentro del método y ya está.

App Dev Guy
fuente
6

Hay muchas respuestas a estas preguntas que ofrecen muchas formas diferentes de realizar la comunicación del controlador de vista que realmente funcionarían, pero no veo en ninguna parte que se mencione cuáles son las mejores para usar y cuáles evitar.

En la práctica, en mi opinión, solo se recomiendan algunas soluciones:

  • Para pasar datos hacia adelante:
    • anular el prepare(for:sender:)método de UIViewControllercuando se usa un guión gráfico y segues
    • pasar datos a través de un inicializador o de propiedades al realizar transiciones del controlador de vista a través del código
  • Para pasar datos al revés
    • actualice el estado compartido de la aplicación (que puede pasar entre los controladores de vista con cualquiera de los métodos anteriores)
    • usar delegación
    • utilizar un desenrollar segue

Soluciones que recomiendo NO usar:

  • Hacer referencia al controlador anterior directamente en lugar de usar delegación
  • Compartir datos a través de un singleton
  • Pasar datos a través del delegado de la aplicación
  • Compartir datos a través de los valores predeterminados del usuario
  • Transmitir datos a través de notificaciones

Estas soluciones, aunque funcionan a corto plazo, introducen demasiadas dependencias que empañarán la arquitectura de la aplicación y crearán más problemas más adelante.

Para aquellos interesados, escribí algunos artículos que abordan estos puntos con mayor profundidad y destacan los diversos inconvenientes:

Matteo Manferdini
fuente