Cómo pasar prepareForSegue: un objeto

358

Tengo muchas anotaciones en una vista de mapa (con rightCalloutAccessorybotones). El botón realizará una transición de esto mapviewa a tableview. Quiero pasar tableviewun objeto diferente (que contiene datos) dependiendo de qué botón de llamada se hizo clic.

Por ejemplo: (totalmente inventado)

  • anotación1 (Austin) -> pasar datos obj 1 (relevante para Austin)
  • annotation2 (Dallas) -> pasar datos obj 2 (relevante para Dallas)
  • annotation3 (Houston) -> pasar datos obj 3 y así sucesivamente ... (se entiende la idea)

Puedo detectar en qué botón de llamada se hizo clic.

Estoy usando prepareForSegue: para pasar el obj de datos al destino ViewController. Como no puedo hacer que esta llamada tome un argumento adicional para el obj de datos que necesito, ¿cuáles son algunas formas elegantes de lograr el mismo efecto (obj de datos dinámicos)?

Cualquier consejo sería apreciado.

chizzle
fuente
posible duplicado de las variables Pass de un ViewController a otro en Swift
Paulw11

Respuestas:

674

Simplemente tome una referencia al controlador de vista de destino en el prepareForSegue:método y pase los objetos que necesite allí. Aquí hay un ejemplo ...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Make sure your segue name in storyboard is the same as this line
    if ([[segue identifier] isEqualToString:@"YOUR_SEGUE_NAME_HERE"])
    {
        // Get reference to the destination view controller
        YourViewController *vc = [segue destinationViewController];

        // Pass any objects to the view controller here, like...
        [vc setMyObjectHere:object];
    }
}

REVISIÓN: También puede usar el performSegueWithIdentifier:sender:método para activar la transición a una nueva vista basada en una selección o presionando un botón.

Por ejemplo, considere que tenía dos controladores de vista. El primero contiene tres botones y el segundo necesita saber cuál de esos botones ha sido presionado antes de la transición. Puede conectar los botones a un IBActionen su código que usa un performSegueWithIdentifier:método, como este ...

// When any of my buttons are pressed, push the next view
- (IBAction)buttonPressed:(id)sender
{
    [self performSegueWithIdentifier:@"MySegue" sender:sender];
}

// This will get called too before the view appears
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"MySegue"]) {

        // Get destination view
        SecondView *vc = [segue destinationViewController];

        // Get button tag number (or do whatever you need to do here, based on your object
        NSInteger tagIndex = [(UIButton *)sender tag];

        // Pass the information to your destination view
        [vc setSelectedButton:tagIndex];
    }
}

EDITAR: la aplicación de demostración que adjunté originalmente ahora tiene seis años, así que la eliminé para evitar confusiones.

Simón
fuente
Gracias pero quiero configurar [vc setMyObjectHere:object];esto dinámicamente. es decir, obj1 para button1, obj2 para button2 El problema es que no puedo pasar una discusión. ¿Hay alguna forma de evitar esto?
chizzle
2
He actualizado mi publicación con un ejemplo descargable de lo que estoy hablando.
Simon
¡eso funciono! Gracias un montón. Como nota al margen, prepareForSegue: tiene un argumento UIControl que es una clase principal de UIButton (capaz de obtener la etiqueta): D
chizzle
¿Se llama a prepareForSegue incluso cuando solo está presionando a un controlador de navegación sin una secuencia de guión gráfico?
zakdances
Esta es una de las respuestas más completas que he visto. Buenos ejemplos de código, resuelve el problema, ofrece ejemplos descargables ... wow. ¡Estoy impresionado!
lewiguez
82

A veces es útil evitar crear una dependencia en tiempo de compilación entre dos controladores de vista. Así es como puede hacerlo sin preocuparse por el tipo de controlador de vista de destino:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController respondsToSelector:@selector(setMyData:)]) {
        [segue.destinationViewController performSelector:@selector(setMyData:) 
                                              withObject:myData];
    } 
}

Entonces, siempre que su controlador de vista de destino declare una propiedad pública, por ejemplo:

@property (nonatomic, strong) MyData *myData;

puede establecer esta propiedad en el controlador de vista anterior como lo describí anteriormente.

Macondo2Seattle
fuente
12
Esto es realmente una cuestión de opinión. (Todavía) no he tenido una situación en la que no quisiera controlar los controles de la vista 'estrictamente', aunque aprecio lo que estás diciendo y puede ser importante en las circunstancias correctas.
Simon
2
@ Simon: sí, simplemente eliges el enfoque que sea mejor para ti. En la aplicación en la que estoy trabajando ahora, por ejemplo, mi enfoque tiene mucho sentido ya que sigo agregando controladores de vista que necesitan el mismo objeto de datos. Poder conectarlos con solo una secuencia y saber que obtendrán los datos correctos es muy conveniente.
Macondo2Seattle
10
No es una cuestión de opinión, simplemente está mal :) La frase "La respuesta aceptada no es la mejor manera de hacer esto" está mal. Debería leer "En ciertos casos, debe hacer esto ..."
Fattie
2
Prefiero el método de Simon. Como me dirá los errores en tiempo de compilación. Por ejemplo, si me perdí la declaración de myData en el controlador de vista de destino, lo sabré de inmediato. Pero para su escenario, ¡su enfoque parece bueno!
Abdurrahman Mubeen Ali
14
Este enfoque crea una dependencia absoluta, ya que se requiere que el controlador de vista de destino tenga un setMyData:. Eso es una dependencia. El hecho de que utilice un selector para evitar el error de compilación debe tratarse como una debilidad de su enfoque, no como un beneficio. Es sorprendente para mí cuántos desarrolladores han perdido el concepto de que los errores de tiempo de compilación deberían preferirse a los errores de tiempo de ejecución.
Nate
21

En Swift 4.2 haría algo así:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let yourVC = segue.destination as? YourViewController {
        yourVC.yourData = self.someData
    }
}
Remy Cilia
fuente
¿Necesita llamar a: `super.prepare (for: segue, sender: sender)`?
Andrew K
16

Tengo una clase de remitente , como esta

@class MyEntry;

@interface MySenderEntry : NSObject
@property (strong, nonatomic) MyEntry *entry;
@end

@implementation MySenderEntry
@end

Yo uso esta clase de remitente para pasar objetos aprepareForSeque:sender:

-(void)didSelectItemAtIndexPath:(NSIndexPath*)indexPath
{
    MySenderEntry *sender = [MySenderEntry new];
    sender.entry = [_entries objectAtIndex:indexPath.row];
    [self performSegueWithIdentifier:SEGUE_IDENTIFIER_SHOW_ENTRY sender:sender];
}

-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_SHOW_ENTRY]) {
        NSAssert([sender isKindOfClass:[MySenderEntry class]], @"MySenderEntry");
        MySenderEntry *senderEntry = (MySenderEntry*)sender;
        MyEntry *entry = senderEntry.entry;
        NSParameterAssert(entry);

        [segue destinationViewController].delegate = self;
        [segue destinationViewController].entry = entry;
        return;
    }

    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_HISTORY]) {
        // ...
        return;
    }

    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_FAVORITE]) {
        // ...
        return;
    }
}
neoneye
fuente
lo estamos usando prepareForSegue o lo estamos implementando y se supone que prepareForSegue se llama a sí mismo, es decir, no necesitamos hacer algo como[self prepareForSegue]
Honey
15

Encontré esta pregunta cuando intentaba aprender cómo pasar datos de un controlador de vista a otro. Sin embargo, necesito algo visual para ayudarme a aprender, por lo que esta respuesta es un complemento de las otras que ya están aquí. Es un poco más general que la pregunta original, pero se puede adaptar al trabajo.

Este ejemplo básico funciona así:

ingrese la descripción de la imagen aquí

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.

Primer controlador de vista

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

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

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

}

Segundo controlador de vista

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
    }

}

Recuerda

  • Realice la segue haciendo controlclic en el botón y arrastrándolo al Segundo controlador de vista.
  • Conecte las salidas para el UITextFieldy el UILabel.
  • Establezca el primer y el segundo controlador de vista en los archivos Swift apropiados en IB.

Fuente

Cómo enviar datos a través de segue (swift) (tutorial de YouTube)

Ver también

Ver controladores: pasar datos hacia adelante y pasar datos hacia atrás (respuesta más completa)

Suragch
fuente
5

Para Swift use esto,

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var segueID = segue.identifier

    if(segueID! == "yourSegueName"){

        var yourVC:YourViewController = segue.destinationViewController as YourViewController

        yourVC.objectOnYourVC = setObjectValueHere!

    }
}
Mohammad Zaid Pathan
fuente
4

Implementé una biblioteca con una categoría en UIViewController que simplifica esta operación. Básicamente, establece los parámetros que desea pasar en un NSDictionary asociado al elemento de la IU que está realizando la segue. Funciona también con segues manuales.

Por ejemplo, puedes hacer

[self performSegueWithIdentifier:@"yourIdentifier" parameters:@{@"customParam1":customValue1, @"customValue2":customValue2}];

para un segue manual o cree un botón con un segue y use

[button setSegueParameters:@{@"customParam1":customValue1, @"customValue2":customValue2}];

Si el controlador de vista de destino no es compatible con la codificación de valores clave para una clave, no sucede nada. También funciona con valores-clave (útil para desenrollar segues). Compruébalo aquí https://github.com/stefanomondino/SMQuickSegue

Stefano Mondino
fuente
2

Mi solución es similar.

// In destination class: 
var AddressString:String = String()

// In segue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if (segue.identifier == "seguetobiddetailpagefromleadbidder")
    {
        let secondViewController = segue.destinationViewController as! BidDetailPage
        secondViewController.AddressString = pr.address as String
    }
}
AG
fuente
1

Solo usa esta función.

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let index = CategorytableView.indexPathForSelectedRow
    let indexNumber = index?.row
    let VC = segue.destination as! DestinationViewController
   VC.value = self.data

}
Parth Barot
fuente
0

Utilicé esta solución para poder mantener la invocación del segue y la comunicación de datos dentro de la misma función:

private var segueCompletion : ((UIStoryboardSegue, Any?) -> Void)?

func performSegue(withIdentifier identifier: String, sender: Any?, completion: @escaping (UIStoryboardSegue, Any?) -> Void) {
    self.segueCompletion = completion;
    self.performSegue(withIdentifier: identifier, sender: sender);
    self.segueCompletion = nil
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    self.segueCompletion?(segue, sender)
}

Un caso de uso sería algo como:

func showData(id : Int){
    someService.loadSomeData(id: id) {
        data in
        self.performSegue(withIdentifier: "showData", sender: self) {
            storyboard, sender in
            let dataView = storyboard.destination as! DataView
            dataView.data = data
        }
    }
}

Esto parece funcionar para mí, sin embargo, no estoy 100% seguro de que las funciones de realizar y preparar siempre se ejecuten en el mismo hilo.

dannrob
fuente