Como passar prepareForSegue: um objeto

358

Eu tenho muitas anotações em um mapview (com rightCalloutAccessorybotões). O botão executará uma sequência disso mapviewpara a tableview. Quero passar tableviewum objeto diferente (que contém dados), dependendo de qual botão da chamada foi clicado.

Por exemplo: (totalmente inventado)

  • anotação1 (Austin) -> passar dados obj 1 (relevante para Austin)
  • anotação2 (Dallas) -> passar dados obj 2 (relevante para Dallas)
  • annotation3 (Houston) -> passa dados obj 3 e assim por diante ... (você entendeu)

Consigo detectar em qual botão da frase de destaque foi clicado.

Estou usando prepareForSegue: para passar o objeto de dados para o destino ViewController. Como não posso fazer esta chamada, use um argumento extra para o objeto de dados que eu exijo, quais são algumas maneiras elegantes de obter o mesmo efeito (objeto de dados dinâmicos)?

Qualquer dica seria apreciada.

chiar
fonte

Respostas:

674

Simplesmente pegue uma referência ao controlador de exibição de destino no prepareForSegue:método e passe todos os objetos que você precisar para lá. Aqui está um exemplo ...

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

REVISÃO: Você também pode usar o performSegueWithIdentifier:sender:método para ativar a transição para uma nova visualização com base em uma seleção ou pressionamento de botão.

Por exemplo, considere que eu tinha dois controladores de exibição. O primeiro contém três botões e o segundo precisa saber qual desses botões foi pressionado antes da transição. Você pode conectar os botões a um IBActioncódigo que usa o 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];
    }
}

EDIT: O aplicativo de demonstração que anexei originalmente agora tem seis anos, então o removi para evitar qualquer confusão.

Simon
fonte
Obrigado, mas eu quero definir [vc setMyObjectHere:object];isso dinamicamente. ou seja, obj1 para o botão1, obj2 para o botão2 O problema é que não consigo passar um argumento. Existe uma maneira de contornar isso?
Chizzle
2
Atualizei minha postagem com um exemplo para download do que estou falando.
23611 Simon
isso funcionou! Muito obrigado. Como uma nota lateral prepareForSegue: tem um argumento UIControl que é uma classe pai de UIButton (portanto, capaz de obter a tag): D
chizzle
O prepareForSegue é chamado mesmo quando você apenas passa para um controlador de navegação sem um storyboard?
zakdances
Essa é uma das respostas mais completas que eu já vi. Boas amostras de código, resolve o problema, oferece amostra para download ... uau. Estou impressionado!
lewiguez
82

Às vezes, é útil evitar criar uma dependência em tempo de compilação entre dois controladores de exibição. Veja como você pode fazer isso sem se preocupar com o tipo de controlador de exibição de destino:

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

Portanto, desde que o seu controlador de exibição de destino declare uma propriedade pública, por exemplo:

@property (nonatomic, strong) MyData *myData;

você pode definir essa propriedade no controlador de exibição anterior, como descrevi acima.

Macondo2Seattle
fonte
12
Isso é realmente uma questão de opinião. Ainda não tive uma situação em que não queria controlar os controladores de exibição 'com força', embora aprecie o que você está dizendo e possa ser importante nas circunstâncias certas.
21712 Simon
2
@ Simon: sim, você acabou de escolher a melhor abordagem para você. No aplicativo em que estou trabalhando agora, por exemplo, minha abordagem faz muito sentido, pois continuo adicionando controladores de exibição que precisam do mesmo objeto de dados. Ser capaz de conectá-los com apenas uma sequência e saber que eles obterão os dados certos é super conveniente.
Macondo2Seattle
10
Não é uma questão de opinião, é apenas errado :) A frase "A resposta aceita não é a melhor maneira de fazer isso" está errada. Deveria ler "Em certos casos, você precisa fazer isso ..."
Fattie
2
Eu prefiro o método de Simon. Como ele vai me dizer os erros em tempo de compilação. Por exemplo, se eu perdi a declaração de myData no controlador de exibição de destino, saberei imediatamente. Mas para o seu cenário, sua abordagem parece boa!
Abdurrahman Mubeen Ali 10/09/14
14
Essa abordagem absolutamente cria uma dependência, pois você está exigindo que o controlador de exibição de destino tenha um setMyData:. Isso é uma dependência. O fato de você usar um seletor para evitar o erro de compilação deve ser tratado como um ponto fraco da sua abordagem, não como um benefício. É chocante para mim quantos desenvolvedores perderam o conceito de que erros de tempo de compilação devem ser preferidos a erros de tempo de execução.
Nate
21

No Swift 4.2, eu faria algo assim:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let yourVC = segue.destination as? YourViewController {
        yourVC.yourData = self.someData
    }
}
Remy Cilia
fonte
Você precisa chamar: `super.prepare (para: segue, sender: sender)`?
Andrew K
16

Eu tenho uma classe de remetente , assim

@class MyEntry;

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

@implementation MySenderEntry
@end

Eu uso essa classe de remetente para passar objetos paraprepareForSeque: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
fonte
estamos usando prepareForSegue ou estamos implementando -lo e a suposição é prepareForSegue é chamado em si, ou seja, nós não precisamos fazer algo como[self prepareForSegue]
Mel
15

Me deparei com essa pergunta quando estava tentando aprender como passar dados de um View Controller para outro. Eu preciso de algo visual para me ajudar a aprender, então essa resposta é um complemento para as outras que já estão aqui. É um pouco mais geral do que a pergunta original, mas pode ser adaptada para funcionar.

Este exemplo básico funciona assim:

insira a descrição da imagem aqui

A idéia é passar uma sequência do campo de texto no First View Controller para o rótulo no Second View Controller.

First View Controller

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

}

Second View Controller

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
    }

}

Lembrar de

  • Faça as etapas controlclicando no botão e arrastando-o para o Second View Controller.
  • Conecte as tomadas para UITextFielde UILabel.
  • Defina o primeiro e o segundo controladores de exibição nos arquivos Swift apropriados no IB.

Fonte

Como enviar dados através de segue (swift) (tutorial do YouTube)

Veja também

Controladores de exibição: transmitindo dados para frente e retornando dados (resposta completa)

Suragch
fonte
5

Para Swift, use isso,

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
fonte
4

Eu implementei uma biblioteca com uma categoria no UIViewController que simplifica essa operação. Basicamente, você define os parâmetros que deseja passar em um NSDictionary associado ao item de interface do usuário que está executando as seguintes etapas. Também funciona com segues manuais.

Por exemplo, você pode fazer

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

para um segue manual ou crie um botão com um segue e use

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

Se o controlador da visualização de destino não for compatível com a codificação de valor-chave de uma chave, nada acontecerá. Também funciona com valores-chave (útil para descontrair segues). Confira aqui https://github.com/stefanomondino/SMQuickSegue

Stefano Mondino
fonte
2

Minha solução é semelhante.

// 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
fonte
1

Basta usar esta função.

 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
fonte
0

Usei essa solução para manter a invocação dos segue e a comunicação de dados na mesma função:

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

Um caso de uso seria 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
        }
    }
}

Isso parece funcionar para mim, no entanto, não tenho 100% de certeza de que as funções de execução e preparação sejam sempre executadas no mesmo encadeamento.

dannrob
fonte