Passando dados entre os controladores de exibição


1372

Eu sou novo no iOS e Objective-C e em todo o paradigma MVC e estou preso ao seguinte:

Tenho uma visão que funciona como um formulário de entrada de dados e quero dar ao usuário a opção de selecionar vários produtos. Os produtos estão listados em outra visualização com UITableViewControllerae habilitei várias seleções.

Minha pergunta é: como transfiro os dados de uma visualização para outra? Estarei mantendo as seleções UITableViewem uma matriz, mas como eu as passo de volta para a visualização do formulário de entrada de dados anterior, para que ela possa ser salva junto com os outros dados no Core Data ao enviar o formulário?

Naveguei e vi algumas pessoas declararem uma matriz no delegado do aplicativo. Li algo sobre Singletons, mas não entendi o que é isso e li algo sobre a criação de um modelo de dados.

Qual seria a maneira correta de fazer isso e como eu faria isso?

Respostas:


1684

Esta pergunta parece ser muito popular aqui no stackoverflow, então pensei em tentar e dar uma resposta melhor para ajudar pessoas começando no mundo do iOS como eu.

Espero que esta resposta seja clara o suficiente para as pessoas entenderem e que não perdi nada.

Passando dados para a frente

Passando dados para um controlador de exibição de outro controlador de exibição. Você usaria esse método se desejasse passar um objeto / valor de um controlador de exibição para outro controlador de exibição que possa estar pressionando em uma pilha de navegação.

Neste exemplo, teremos ViewControllerAeViewControllerB

Para passar um BOOLvalor de ViewControllerApara ViewControllerB, faríamos o seguinte.

  1. em ViewControllerB.hcriar uma propriedade para oBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. em que ViewControllerAvocê precisa falar sobre ViewControllerBisso, use um

    #import "ViewControllerB.h"

    Então, onde você deseja carregar a visualização, por exemplo. didSelectRowAtIndexou alguns dos quais IBActionvocê precisa definir a propriedade ViewControllerBantes de enviá-la para a pilha de navegação.

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

    Isto irá definir isSomethingEnabledem ViewControllerBque BOOLvalor YES.

Passando dados para frente usando segmentos

Se você estiver usando Storyboards, provavelmente usará seques e precisará deste procedimento para passar os dados adiante. Isso é semelhante ao acima, mas em vez de passar os dados antes de você pressionar o controlador de exibição, use um método chamado

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

Então, para passar de BOOLfrom ViewControllerApara ViewControllerB, faríamos o seguinte:

  1. em ViewControllerB.hcriar uma propriedade para oBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. em que ViewControllerAvocê precisa falar sobre ViewControllerBisso, use um

    #import "ViewControllerB.h"
  3. Crie um segue de ViewControllerApara ViewControllerBno storyboard e dê um identificador, neste exemplo, chamaremos de"showDetailSegue"

  4. Em seguida, precisamos adicionar o método ao ViewControllerAque é chamado quando qualquer segue é executada, por isso, precisamos detectar qual segue foi chamado e, em seguida, fazer alguma coisa. Em nosso exemplo, verificaremos "showDetailSegue"e, se isso for executado, passaremos nosso BOOLvalor paraViewControllerB

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

    Se você tiver suas visualizações incorporadas em um controlador de navegação, precisará alterar ligeiramente o método acima para o seguinte

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

    Isto irá definir isSomethingEnabledem ViewControllerBque BOOLvalor YES.

Passando dados de volta

Para passar dados de volta ViewControllerBpara ViewControllerAvocê precisar usar Protocolos e Delegados ou Blocos , o último pode ser usado como um mecanismo pouco acoplado para retornos de chamada.

Para fazer isso, faremos ViewControllerAum delegado de ViewControllerB. Isso permite ViewControllerBenviar uma mensagem de volta para ViewControllerAnos permitir enviar dados de volta.

Para ViewControllerAser um delegado ViewControllerBdele deve estar em conformidade com ViewControllerBo protocolo que devemos especificar. Isso informa ViewControllerAquais métodos ele deve implementar.

  1. Em ViewControllerB.h, abaixo de #import, mas acima de @interfacevocê especificar o protocolo.

    @class ViewControllerB;
    
    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
    
  2. próximo ainda no ViewControllerB.hvocê precisa configurar uma delegatepropriedade e sintetizar emViewControllerB.m

    @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. Em ViewControllerBchamamos uma mensagem no delegatequando aparecer o controlador de vista.

    NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
    
  4. É para isso ViewControllerB. Agora ViewControllerA.h, diga ViewControllerApara importar ViewControllerBe estar em conformidade com seu protocolo.

    #import "ViewControllerB.h"
    
    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
    
  5. Em ViewControllerA.mimplementar o seguinte método do nosso protocolo

    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
        NSLog(@"This was returned from ViewControllerB %@",item);
    }
    
  6. Antes de enviar viewControllerBpara a pilha de navegação, precisamos informar ViewControllerBque ViewControllerAé seu representante, caso contrário, obteremos um erro.

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

Referências

  1. Usando a delegação para se comunicar com outros controladores de exibição no Guia de programação do controlador de exibição
  2. Delegar Padrão

NSNotification center É outra maneira de transmitir dados.

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

Passando dados de volta de uma classe para outra (Uma classe pode ser qualquer controlador, gerente de rede / sessão, subclasse UIView ou qualquer outra classe)

Blocos são funções anônimas.

Este exemplo passa dados do Controlador B para o Controlador A

definir um bloco

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

adicione manipulador de bloco (ouvinte) onde você precisa de um valor (por exemplo, você precisa de sua resposta de API no ControllerA ou precisa de dados do ContorllerB em A)

// in ContollerA.m

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

Vá para o controlador B

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

bloco de fogo

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

Outro exemplo de trabalho para blocos


24
Também precisamos colocar uma @class ViewControllerB;definição acima da @protocol? Sem ele eu recebo um erro "Tipo esperado" na ViewControllerB na linha: - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; dentro da @protocoldeclaração
alan-p

4
Isso funciona muito bem. Como alan-p diz, não esqueça de escrever @class ViewControllerB; acima do protocolo, caso contrário, você receberá o erro "Esperava um tipo".
Andrew Davis

6
você não precisa de delegados para retornar, basta relaxar.
malhal 27/09/13

4
Quando eu coloco "viewControllerB.delegate = self;" no ViewControllerB, estou recebendo um erro. Atribuindo a 'id <ViewControllerBDelegate>' do tipo incompatível 'ViewControllerB * const __strong', não tenho certeza do que estou fazendo de errado. Alguém pode ajudar? Além disso, eu tive que mudar: initWithNib -> initWithNibName.
Uplearnedu.com 28/03

4
se você estiver usando NavigationControllervocê tem que usar [self.navigationController pushViewController:viewController animated:YES];em vez[self pushViewController:viewControllerB animated:YES];
Nazir

192

Rápido

Há toneladas e muitas explicações aqui e ao redor do StackOverflow, mas se você é iniciante, está apenas tentando obter algo básico para o trabalho, tente assistir a este tutorial do YouTube (foi o que me ajudou a finalmente entender como fazê-lo).

Passando dados para o próximo View Controller

A seguir, é apresentado um exemplo baseado no vídeo. A idéia é passar uma sequência do campo de texto no First View Controller para o rótulo no Second View Controller.

insira a descrição da imagem aqui

Crie o layout do storyboard no Interface Builder. Para fazer as etapas, basta Controlclicar no botão e arrastar para o Second View Controller.

First View Controller

O código para o First View Controller é

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

}

Second View Controller

E o código para o 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
    }

}

Não esqueça

  • Ligue as tomadas para UITextFielde UILabel.
  • Defina o primeiro e o segundo controladores de exibição nos arquivos Swift apropriados no IB.

Passando dados de volta para o View Controller anterior

Para passar dados do segundo controlador de exibição para o primeiro controlador de exibição, use um protocolo e um delegado . Este vídeo é uma visão muito clara desse processo:

A seguir, é apresentado um exemplo baseado no vídeo (com algumas modificações).

insira a descrição da imagem aqui

Crie o layout do storyboard no Interface Builder. Novamente, para fazer o seguinte, basta Controlarrastar do botão para o Second View Controller. Defina o identificador segue para showSecondViewController. Além disso, não esqueça de conectar as saídas e ações usando os nomes no código a seguir.

First View Controller

O código para o First View Controller é

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

Observe o uso do nosso DataEnteredDelegateprotocolo personalizado .

Controlador e protocolo Second View

O código para o segundo controlador de exibição é

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

Observe que protocolestá fora da classe View Controller.

É isso aí. Ao executar o aplicativo agora, você poderá enviar dados do segundo controlador de exibição para o primeiro.


Dadas algumas das atualizações mais recentes do Swift, esse ainda é um padrão comum de implementar?
piofusco

4
A maioria das atualizações do Swift que eu vi foram mudanças sintáticas relativamente menores, não mudanças na maneira como os dados são transmitidos entre os controladores de exibição. Se eu souber de alguma mudança importante como essa, atualizarei minha resposta.
Suragch

2
offtopic - o iOS tem uma maneira tão feia de passar parâmetros para novos controladores de exibição, inacreditável - você precisa definir parâmetros não em um local quando estiver fazendo a chamada, mas em outro. O Android tem uma abordagem melhor a esse respeito - quando você inicia uma Atividade, pode passar quaisquer dados (quase, quase) por meio do Intent inicial. Fácil. Não há necessidade de lançar ou algo assim. Passar valores de retorno de volta para o chamador também é uma coisa essencial, sem necessidade de delegar. Claro que é possível usar abordagens feias também, não há problema nenhum))
Mixaz

1
@ Himanshu, primeiro obtenha uma referência ao segundo controlador de exibição. Atualize a variável pública que ela contém.
Suragch 30/07/16

8
@Mel. Eu acho que a palavra "delegar" é confusa. Deixe-me usar a palavra "trabalhador". O "trabalhador" (controlador da primeira visão) faz o que o "chefe" (controlador da segunda visão) manda fazer. O "chefe" não sabe quem será seu "trabalhador"; poderia ser qualquer um. Portanto, no primeiro controlador de exibição (classe "trabalhador"), ele será o seu "trabalhador". Você me diz o que escrever no rótulo e eu farei isso por você. Assim, secondViewController.delegate = selfsignifica "Eu concordo em ser o trabalhador do chefe". Veja esta resposta para outro exemplo e mais explicações.
Suragch 26/09/16

136

OM no MVC é para "Model" e no paradigma MVC o papel das classes de modelo é gerenciar os dados de um programa. Um modelo é o oposto de uma visão - uma visão sabe como exibir dados, mas não sabe nada sobre o que fazer com dados, enquanto um modelo sabe tudo sobre como trabalhar com dados, mas nada sobre como exibi-los. Os modelos podem ser complicados, mas não precisam. O modelo do seu aplicativo pode ser tão simples quanto uma matriz de strings ou dicionários.

O papel de um controlador é mediar entre vista e modelo. Portanto, eles precisam de uma referência para um ou mais objetos de exibição e um ou mais objetos de modelo. Digamos que seu modelo é uma matriz de dicionários, com cada dicionário representando uma linha na sua tabela. A visualização raiz do seu aplicativo exibe essa tabela e pode ser responsável por carregar a matriz de um arquivo. Quando o usuário decide adicionar uma nova linha à tabela, ele toca em algum botão e seu controlador cria um novo dicionário (mutável) e o adiciona à matriz. Para preencher a linha, o controlador cria um controlador de exibição de detalhes e fornece o novo dicionário. O controlador de exibição de detalhes preenche o dicionário e retorna. O dicionário já faz parte do modelo, então nada mais precisa acontecer.


95

Existem várias maneiras pelas quais os dados podem ser recebidos para uma classe diferente no iOS. Por exemplo -

  1. Inicialização direta após a alocação de outra classe.
  2. Delegação - para retornar dados
  3. Notificação - para transmitir dados para várias classes ao mesmo tempo
  4. Salvando NSUserDefaults- para acessá-lo mais tarde
  5. Classes Singleton
  6. Bancos de dados e outros mecanismos de armazenamento, como plist, etc.

Mas, para o cenário simples de passar um valor para uma classe diferente cuja alocação é feita na classe atual, o método mais comum e preferido seria a configuração direta de valores após a alocação. Isto se faz do seguinte modo:-

Podemos entender isso usando dois controladores - Controller1 e Controller2

Suponha que na classe Controller1 você deseje criar o objeto Controller2 e enviá-lo com um valor String sendo passado. Isso pode ser feito da seguinte maneira: -

- (void)pushToController2 {

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

Na implementação da classe Controller2, haverá esta função 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

Você também pode definir diretamente as propriedades da classe Controller2 da maneira semelhante a esta:

- (void)pushToController2 {

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

Para passar vários valores, você pode usar vários parâmetros como: -

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

Ou, se você precisar passar mais de 3 parâmetros relacionados a um recurso comum, poderá armazenar os valores em uma classe Model e passar esse modelObject para a próxima classe

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

Então, resumindo, se você quiser -

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 isto ajude


84

Após mais pesquisas, parecia que Protocolos e Delegados é a maneira correta / a Apple preferia fazer isso.

Acabei usando este exemplo

Compartilhando dados entre os controladores de exibição e outros objetos no iPhone Dev SDK

Funcionou bem e me permitiu passar uma string e uma matriz para frente e para trás entre meus pontos de vista.

Obrigado por toda sua ajuda


3
não use protocolos e delegados, apenas descontraia.
malhal

1
@malhal E se você não usar storyboards?
Evan R

Eu também odeio protocolos inúteis e delegados. @malhal
DawnSong

@EvanR Você pode criar e executar segues no código. É tudo a mesma coisa.
DawnSong

1
Essencialmente, o controle de qualidade inteiro nesta página é "dos dias anteriores às visualizações do contêiner". Você nunca se preocuparia em um milhão de anos com protocolos ou delegados agora. Tudo o que você faz em qualquer tela é uma exibição de contêiner de qualquer maneira; portanto, a pergunta realmente não existe mais - você já tem todas as referências "para cima e para baixo" de todas as visualizações de contêiner.
Fattie

66

Acho a versão mais simples e elegante com blocos que passam. Vamos nomear o controlador de exibição que aguarda os dados retornados como "A" e retornar o controlador de exibição como "B". Neste exemplo, queremos obter 2 valores: primeiro do tipo 1 e segundo do tipo 2.

Supondo que usamos o Storyboard, o primeiro controlador define o bloco de retorno de chamada, por exemplo, durante a preparação do 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);
        };

    }
}

e o controlador de exibição "B" deve declarar a propriedade de retorno de chamada, BViewController.h:

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

Do que no arquivo de implementação BViewController.m, depois que desejamos que os valores retornem, nosso retorno de chamada deve ser chamado:

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

Uma coisa a lembrar é que o uso de bloco geralmente precisa gerenciar referências fortes e fracas, como explicado aqui


Por que o valor não é um parâmetro para bloquear o retorno de chamada em vez de ser uma propriedade separada?
Timuçin

56

Há algumas informações boas em muitas das respostas dadas, mas nenhuma aborda a questão completamente.

A pergunta é sobre a passagem de informações entre os controladores de exibição. O exemplo específico dado pergunta sobre a passagem de informações entre visualizações, mas, dada a novidade auto-declarada para o iOS, o pôster original provavelmente significava entre viewControllers, não entre visualizações (sem nenhum envolvimento dos ViewControllers). Parece que todas as respostas se concentram em dois controladores de exibição, mas e se o aplicativo evoluir para envolver mais de dois controladores de exibição na troca de informações?

O pôster original também perguntou sobre Singletons e o uso do AppDelegate . Essas perguntas precisam ser respondidas.

Para ajudar qualquer pessoa que esteja olhando para essa pergunta, que deseja uma resposta completa, tentarei fornecê-la.

Cenários de aplicação

Em vez de ter uma discussão abstrata altamente hipotética, ajuda a ter aplicações concretas em mente. Para ajudar a definir uma situação de controlador de duas visualizações e uma situação de controlador de mais de duas visualizações, vou definir dois cenários de aplicação concretos.

Cenário 1: no máximo dois controladores de exibição precisam compartilhar informações. Veja o diagrama um.

diagrama do problema original

Existem dois controladores de exibição no aplicativo. Há um ViewControllerA (formulário de entrada de dados) e o View Controller B (lista de produtos). Os itens selecionados na lista de produtos devem corresponder aos itens exibidos na caixa de texto no formulário de entrada de dados. Nesse cenário, ViewControllerA e ViewControllerB devem se comunicar diretamente entre si e com nenhum outro controlador de exibição.

Cenário dois : mais de dois controladores de exibição precisam compartilhar as mesmas informações. Veja o diagrama dois.

diagrama de aplicação do inventário doméstico

Existem quatro controladores de exibição no aplicativo. É um aplicativo baseado em guias para gerenciar o inventário doméstico. Três controladores de exibição apresentam visualizações filtradas de maneira diferente dos mesmos dados:

  • ViewControllerA - Itens de luxo
  • ViewControllerB - Itens Não Segurados
  • ViewControllerC - Inventário doméstico inteiro
  • ViewControllerD - Formulário para adicionar novo item

Sempre que um item individual é criado ou editado, ele também deve ser sincronizado com os outros controladores de exibição. Por exemplo, se adicionarmos um barco no ViewControllerD, mas ele ainda não estiver seguro, o barco deverá aparecer quando o usuário acessar o ViewControllerA (Itens de luxo) e também o ViewControllerC (inventário doméstico inteiro), mas não quando o usuário acessar o ViewControllerB (itens não segurados). Precisamos nos preocupar não apenas com a adição de novos itens, mas também com a exclusão de itens (que podem ser permitidos em qualquer um dos quatro controladores de exibição) ou com a edição de itens existentes (que podem ser permitidos no "Formulário para adicionar novo item", redirecionando o mesmo para edição).

Como todos os controladores de exibição precisam compartilhar os mesmos dados, todos os quatro controladores de exibição precisam permanecer sincronizados e, portanto, deve haver algum tipo de comunicação com todos os outros controladores de exibição, sempre que um único controlador de exibição altera os dados subjacentes. Deveria ser bastante óbvio que não queremos que cada controlador de visualização se comunique diretamente entre si neste cenário. Caso isso não seja óbvio, considere se tivéssemos 20 controladores de exibição diferentes (em vez de apenas 4). Quão difícil e propenso a erros seria notificar cada um dos outros 19 controladores de visualização sempre que um controlador de visualização fazia uma alteração?

As Soluções: Delegados e o Padrão do Observador, e Singletons

No cenário um, temos várias soluções viáveis, pois outras respostas deram

  • segues
  • delegados
  • definindo propriedades nos controladores de exibição diretamente
  • NSUserDefaults (na verdade, uma má escolha)

No cenário dois, temos outras soluções viáveis:

  • Padrão do observador
  • Singletons

Um singleton é uma instância de uma classe, sendo a única instância existente durante sua vida útil. Um singleton recebe esse nome pelo fato de ser a única instância. Normalmente, os desenvolvedores que usam singletons têm métodos de classe especiais para acessá-los.

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

Agora que entendemos o que é um singleton, vamos discutir como um singleton se encaixa no padrão de observador. O padrão observador é usado para um objeto responder a alterações de outro objeto. No segundo cenário, temos quatro controladores de exibição diferentes, que todos desejam saber sobre alterações nos dados subjacentes. Os "dados subjacentes" devem pertencer a uma única instância, um singleton. O "conhecimento sobre mudanças" é realizado observando as alterações feitas no singleton.

O aplicativo de inventário doméstico teria uma única instância de uma classe projetada para gerenciar uma lista de itens de inventário. O gerente gerenciava uma coleção de itens domésticos. A seguir está uma definição de classe para o gerenciador de dados:

#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

Quando a coleção de itens do inventário doméstico é alterada, os controladores de exibição precisam estar cientes dessa alteração. A definição de classe acima não torna óbvio como isso acontecerá. Precisamos seguir o padrão do observador. Os controladores de exibição devem observar formalmente o sharedManager. Existem duas maneiras de observar outro objeto:

  • Observação de valor-chave (KVO)
  • NSNotificationCenter.

No cenário dois, não temos uma única propriedade do HouseholdInventoryManager que pode ser observada usando o KVO. Como não temos uma propriedade única que seja facilmente observável, o padrão do observador, nesse caso, deve ser implementado usando o NSNotificationCenter. Cada um dos quatro controladores de exibição assinaria notificações, e o sharedManager enviaria notificações ao centro de notificações quando apropriado. O gerente de inventário não precisa saber nada sobre os controladores de exibição ou instâncias de outras classes que possam estar interessadas em saber quando a coleção de itens de inventário é alterada; o NSNotificationCenter cuida desses detalhes de implementação. Os Controladores de exibição simplesmente assinam notificações e o gerenciador de dados simplesmente publica notificações.

Muitos programadores iniciantes aproveitam o fato de que sempre há exatamente um Delegado de Aplicativo durante a vida útil do aplicativo, que é acessível globalmente. Os programadores iniciantes usam esse fato para inserir objetos e funcionalidades no appDelegate como uma conveniência para acessar de qualquer outro lugar no aplicativo. Só porque o AppDelegate é um singleton não significa que ele deve substituir todos os outros singletons. Essa é uma prática ruim, pois coloca muita carga em uma classe, quebrando boas práticas orientadas a objetos. Cada classe deve ter um papel claro que seja facilmente explicado, geralmente apenas pelo nome da classe.

Sempre que o seu Application Delegate começar a ficar inchado, comece a remover a funcionalidade em singletons. Por exemplo, a Core Data Stack não deve ser deixada no AppDelegate, mas deve ser colocada em sua própria classe, a coreDataManager.

Referências


41

O OP não mencionou os controladores de exibição, mas muitas das respostas o fazem, e eu queria compartilhar com o que alguns dos novos recursos do LLVM permitem tornar isso mais fácil quando se deseja passar dados de um controlador de exibição para outro e, em seguida, obtendo alguns resultados de volta.

Os seguimentos do storyboard, os blocos ARC e LLVM tornam isso mais fácil do que nunca para mim. Algumas respostas acima mencionadas storyboards e segues já, mas ainda dependiam de delegação. A definição de delegados certamente funciona, mas algumas pessoas podem achar mais fácil passar ponteiros ou blocos de código.

Com UINavigators e segues, existem maneiras fáceis de passar informações ao controlador subserviente e recuperar as informações. O ARC simplifica a passagem de ponteiros para itens derivados de NSObjects; portanto, se você deseja que o controlador subserviente adicione / altere / modifique alguns dados, passe-o como ponteiro para uma instância mutável. Os blocos facilitam as ações de aprovação; portanto, se você deseja que o controlador subserviente invoque uma ação no seu controlador de nível superior, passe-o um bloco. Você define o bloco para aceitar qualquer número de argumentos que faça sentido para você. Você também pode projetar a API para usar vários blocos, se isso for melhor.

Aqui estão dois exemplos triviais da cola segue. O primeiro é direto, mostrando um parâmetro passado para entrada, o segundo para saída.

// 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 exemplo mostra a passagem de um bloco de retorno de chamada para o segundo argumento. Eu gosto de usar blocos porque mantém os detalhes relevantes próximos na fonte - a fonte de nível 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];
    }];
}

41

Retornar dados do ViewController 2 (destino) para o viewController 1 (Origem) é a coisa mais interessante. Supondo que você use o storyBoard, essas são todas as maneiras que descobri:

  • Delegar
  • Notificação
  • Padrões do usuário
  • Singleton

Já foram discutidos aqui.

Eu descobri que existem mais maneiras:

-Usando retornos de chamada em bloco:

use-o no prepareForSeguemétodo no VC1

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

-Usando storyboards Descontrair (Sair)

Implemente um método com um argumento UIStoryboardSegue no VC 1, como este:

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

No storyBoard, ligue o botão "return" ao botão verde Exit (Unwind) do vc. Agora você tem uma sequência que "retorna" para que você possa usar a propriedade destinationViewController no prepareForSegue do VC2 e alterar qualquer propriedade do VC1 antes de voltar.

  • Outra opção do uso de storyboards Undwind (Exit) - você pode usar o método que escreveu no VC1

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

    E no prepareForSegue do VC1, você pode alterar qualquer propriedade que deseja compartilhar.

Nas duas opções de desenrolamento, você pode definir a propriedade tag do botão e verificá-la no prepareForSegue.

Espero ter acrescentado algo à discussão.

:) Felicidades.


40

Existem vários métodos para compartilhar dados.

  1. Você sempre pode compartilhar dados usando NSUserDefaults. Defina o valor que deseja compartilhar com relação a uma chave de sua escolha e obtenha o valor NSUserDefaultassociado a essa chave no próximo controlador de exibição.

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
  2. Você pode apenas criar uma propriedade no viewcontrollerA. Crie um objeto de viewcontrollerAin viewcontrollerBe atribua o valor desejado a essa propriedade.

  3. Você também pode criar delegados personalizados para isso.


30
O objetivo típico do NSUserDefaults é armazenar as preferências do usuário que persistem entre as execuções de aplicativos, para que qualquer coisa armazenada aqui permaneça aqui, a menos que seja explicitamente removida. É uma péssima idéia usá-lo para passar informações entre os controladores de exibição (ou quaisquer outros objetos) em um aplicativo.
José González

30

Se você deseja passar dados de um controlador para outro, tente 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];
 }

29

Esta é uma resposta muito antiga e isso é antipadrão, use delegados. Não use essa abordagem !!

1. Crie a instância do primeiro View Controller no segundo View Controller e faça sua propriedade @property (nonatomic,assign).

2. Atribua a SecondviewControllerinstância deste controlador de exibição.

2. Ao concluir a operação de seleção, copie a matriz para o primeiro View Controller. Quando você descarrega o SecondView, o FirstView retém os dados da matriz.

Espero que isto ajude.


2
Não acredito que este seja o caminho correto a seguir, pois cria um link muito estriado entre os controladores de exibição. Não é realmente fiel ao MVC.
Matt Price

1
Se você quiser seguir rigorosamente MVC, use NSNotificationCenter um método pode ser chamado de ViewControllerA para ViewControllerB, verifique esta pode ajudar u
kaar3k

28

Eu estava pesquisando essa solução há muito tempo, encontrei-a pela Atlast. Primeiro, declare todos os objetos no seu arquivo SecondViewController.h como

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

Agora, no seu arquivo de implementação, aloque a memória para 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;
}

Agora você alocou a memória Arraye o objeto. agora você pode preencher essa memória antes de pressionar esteViewController

Vá para o SecondViewController.he escreva dois métodos

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

no arquivo de implementação, você pode implementar a função

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

esperando que você CustomObjectprecise ter uma função setter com ele.

agora seu trabalho básico está feito. vá para o local em que deseja empurrar SecondViewControllere faça o seguinte

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

Tome cuidado com erros de ortografia.


24

Esta não é a maneira de fazer isso, você deve usar delegados, presumo que tenhamos dois controladores de exibição, ViewController1 e ViewController2, e essa coisa de verificação está no primeiro e, quando seu estado muda, você deseja fazer algo no ViewController2, para Para conseguir isso da maneira correta, faça o seguinte:

Adicione um novo arquivo ao seu projeto (Protocolo Objective-C) Arquivo -> Novo, agora chame-o de ViewController1Delegate ou o que você quiser e escreva-o entre as diretivas @interface e @end

@optional

- (void)checkStateDidChange:(BOOL)checked;

Agora vá para ViewController2.he adicione

#import "ViewController1Delegate.h"

altere sua definição para

@interface ViewController2: UIViewController<ViewController1Delegate>

Agora vá para ViewController2.m e, dentro da implementação, adicione:

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

Agora vá para ViewController1.he adicione a seguinte propriedade:

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

Agora, se você estiver criando o ViewController1 dentro do ViewController2 após algum evento, faça o seguinte usando arquivos NIB:

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

Agora você está pronto, sempre que detectar o evento de verificação alterado no ViewController1, tudo o que você precisa fazer é o abaixo

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

Diga-me se há algo que não esteja claro se eu não entendi sua pergunta corretamente.


23

Se você deseja enviar dados de um para outro viewController, aqui está uma maneira de fazê-lo:

Digamos que temos viewControllers: viewControllerA e viewControllerB

Agora no viewControllerB.h

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

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

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

}

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

}

Portanto, é assim que você pode transmitir dados do viewControllerA para o viewControllerB sem definir nenhum representante. ;)


1
Tentei usar o código ur no meu projeto, mas não consigo obter os valores no viewcontrollerB. Você pode me dizer qual pode ser o problema?
O X-Coder

1
@Ajitthala Você pode colar seu código em uma nova pergunta? Vou tentar resolver o seu problema. :)
Aniruddh Joshi

1
é errado não usar métodos init e fazer algo como vcB.string = @ "asdf" do viewcontroller A?
Khanh.tran.vinh

1
@ khanh.tran.vinh Depende se você está usando o ARC ou não.
Aniruddh Joshi

21

Sei que esse é um assunto muito comentado, mas para quem procura responder a essa pergunta com uma inclinação SWIFT e quer um exemplo básico, aqui está meu método para passar dados, se você estiver usando um segue para se locomover.

É semelhante ao anterior, mas sem os botões, etiquetas e outros. Simplesmente passando dados de uma visualização para a seguinte.

Configurar o Storyboard

Existem três partes.

  1. O remetente
  2. O Segue
  3. O receptor

Este é um layout de visualização muito simples, com um segue entre eles.


Layout de exibição muito simples.  Nota: nenhum controlador de navegação


Aqui está a configuração para o remetente


O remetente


Aqui está a configuração para o receptor.


O receptor


Por fim, a configuração para o segue.


O Identificador de Segue


Os controladores de exibição

Estamos mantendo isso simples, para que não haja botões, nem ações; estamos simplesmente movendo dados do remetente para o receptor quando o aplicativo é carregado e, em seguida, emitindo o valor transmitido para o console.

Esta página pega o valor inicialmente carregado e o repassa.

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 apenas envia o valor da variável para o console quando ele é carregado. A essa altura, nosso filme favorito deve estar nessa variável.

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

    }   
}

É assim que você pode resolvê-lo se quiser usar um segue e não tiver suas páginas sob um controlador de navegação.

Uma vez executado, ele deve alternar para a visualização do receptor automaticamente e passar o valor do remetente para o receptor, exibindo o valor no console.

Ghost Busters é um povo clássico.


19

No meu caso, usei uma classe singleton que pode funcionar como um objeto global, permitindo o acesso aos dados de quase todos os lugares do aplicativo. A primeira coisa é construir uma classe singleton. Por favor, consulte a página “ Como deve ser meu singleton Objective-C? ” E o que eu fiz para tornar o objeto globalmente acessível foi simplesmente importá-lo appName_Prefix.pchpara a aplicação da declaração de importação em todas as classes. Para acessar e usar esse objeto, simplesmente implementei o método de classe para retornar a instância compartilhada, que contém suas próprias variáveis


Essa é a resposta correta. Basta usar um singleton como o "modelo". Observe que, como Caleb diz, "o modelo para seu aplicativo pode ser tão simples quanto uma matriz de strings" . É fundamental notar que fazer um singleton em Swift é realmente trivial . (Tão simples que nem vale a pena mencionar aqui - apenas o google.) Para novos programadores, vale a pena entender que fazer um singleton costumava ser uma verdadeira dor de cabeça . No entanto, os singletons são absolutamente essenciais para a programação do iOS - tudo o que a Apple faz é um singleton. É por isso que a Apple finalmente fez um teste (em Swift) para fazer singletons corretamente.
Fattie

1
Observe, no entanto, que hoje em dia (2016 ou mais) "tudo é uma exibição de contêiner no iOS". Cada coisa que você faz na tela faz uma pequena exibição do contêiner. É bastante trivial obter as cadeias de exibição de contêineres "para cima e para baixo" (embora a Apple facilite isso no futuro), e você faz isso para quase todas as visualizações de contêineres. Então, se você fez isso de qualquer maneira - você tem a resposta; não há necessidade de um singleton. Vista Container introdução ... stackoverflow.com/a/23403979/294884
Fattie

19

Swift 5

Bem, a resposta de Matt Price é perfeitamente adequada para a transmissão de dados, mas eu vou reescrevê-los, na versão mais recente do Swift, porque acredito que os novos programadores acham bastante desafiador devido à nova sintaxe e métodos / estruturas, como a postagem original está no Objective-C.

Existem várias opções para passar dados entre os controladores de exibição.

  1. Usando o Push do Controlador de Navegação
  2. Usando Segue
  3. Usando Delegado
  4. Usando o Notification Observer
  5. Usando bloco

Vou reescrever sua lógica no Swift com o último iOS Framework


Passando dados pelo Push do controlador de navegação : do ViewControllerA para o ViewControllerB

Etapa 1. Declarar variável no ViewControllerB

var isSomethingEnabled = false

Etapa 2. Variável de impressão no método ViewControllerB 'ViewDidLoad

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

Etapa 3. No ViewControllerA Pass Data enquanto pressiona através do Navigation Controller

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

Então, aqui está o 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)
    }
}

Passando dados pelo segmento : de ViewControllerA para ViewControllerB

Etapa 1. Crie um Segue de ViewControllerA para ViewControllerB e forneça Identifier = showDetailSegue no Storyboard, como mostrado abaixo

insira a descrição da imagem aqui

Etapa 2. No ViewControllerB Declare um viável chamado isSomethingEnabled e imprima seu valor.

Etapa 3. No ViewController, o valor de um passe isSomethingEnabled ao passar Segue

Então, aqui está o 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)
    }
}

Passando dados por meio de delegado : de ViewControllerB para ViewControllerA

Etapa 1. Declarar o protocolo ViewControllerBDelegate no arquivo ViewControllerB, mas fora da classe

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

Etapa 2. Declare a instância da variável Delegate em ViewControllerB

var delegate: ViewControllerBDelegate?

Etapa 3. Envie dados para delegar dentro do método viewDidLoad do ViewControllerB

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

Etapa 4. Confirme o ViewControllerBDelegate no ViewControllerA

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

Etapa 5. Confirme que você implementará o delegado no 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)
            }
        }

Etapa 6. Implemente o método delegado para receber dados no ViewControllerA

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

Então, aqui está o 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")
    }
}

Passagem de dados pelo Notification Observer : do ViewControllerB para o ViewControllerA

Etapa 1. Defina e publique dados no observador de notificações no ViewControllerB

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

Etapa 2. Adicione o Notification Observer no ViewControllerA

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

Etapa 3. Receba o valor dos dados de notificação no ViewControllerA

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

Então, aqui está o 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)
    }
}

Passando dados pelo bloco : do ViewControllerB para o ViewControllerA

Etapa 1. Declarar o bloco no ViewControllerB

var authorCompletionBlock: ((Bool) -> ())? = {_ in}

Etapa 2. Defina os dados no bloco no ViewControllerB

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

Etapa 3. Receba os dados do bloco no ViewControllerA

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

Então, aqui está o 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)
        }
    }
}

Você pode encontrar uma amostra completa do aplicativo no meu GitHub. Informe-me se tiver alguma dúvida sobre isso.


18

Passando dados entre o FirstViewController para o SecondViewController como abaixo

Por exemplo:

FirstViewController Valor da string como

StrFirstValue = @"first";

para que possamos passar esse valor na segunda classe usando o passo abaixo

1> Precisamos criar um objeto string no arquivo SecondViewController.h

NSString *strValue;

2> Precisa declarar a propriedade conforme abaixo abaixo na declaração no arquivo .h

@property (strong, nonatomic)  NSString *strSecondValue;

3> Precisa sintetizar esse valor no arquivo FirstViewController.m abaixo da declaração do cabeçalho

@synthesize strValue;

e no FirstViewController.h:

@property (strong, nonatomic)  NSString *strValue;

4> No FirstViewController, em qual método navegamos para a segunda visualização, escreva o código abaixo nesse método.

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

[secondView setStrSecondValue:StrFirstValue];

[self.navigationController pushViewController:secondView animated:YES ];

Depois de estar no SecondViewController, como você transmite dados de volta ao FirstViewController?
11555 bruno

18

Atualmente, estou contribuindo para uma solução de código aberto para esse problema através de um projeto chamado MCViewFactory, que pode ser encontrado aqui:

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

A idéia é imitar o paradigma de intenções do Android, usando uma fábrica global para gerenciar qual visualização você está visualizando e usando "intenções" para alternar e passar dados entre as exibições. Toda a documentação está na página do github, mas aqui estão alguns destaques:

Você configura todas as suas visualizações em arquivos .XIB e as registra no delegado do aplicativo enquanto inicializa a fábrica.

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

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

Agora, no seu VC, sempre que você desejar mover para um novo VC e transmitir dados, crie uma nova intenção e adicione dados ao seu dicionário (savedInstanceState). Em seguida, basta definir a intenção atual da 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 as suas visualizações que estão em conformidade com isso precisam ser subclasses do MCViewController, que permitem substituir o novo método onResume:, permitindo acessar os dados que você transmitiu.

-(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 alguns de vocês achem esta solução útil / interessante.


Então todos os objetos do controlador poderiam obter / definir todos os dicionários registrados em qualquer escopo? Voto negativo.
Itachi

15

Crie a propriedade no próximo view controller .he defina getter e setter.

Adicione isso propertyno NextVC.h no nextVC

@property (strong, nonatomic) NSString *indexNumber;

Adicionar

@synthesize indexNumber; no NextVC.m

E por ultimo

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

vc.indexNumber=@"123";

[self.navigationController vc animated:YES];

11

Existem várias maneiras de fazer isso e é importante escolher a correta. Provavelmente, uma das maiores decisões de arquitetura reside em como o código do modelo será compartilhado ou acessado em todo o aplicativo.

Eu escrevi um post sobre isso há algum tempo: Compartilhando o Código do Modelo . Aqui está um breve resumo:

Dados compartilhados

Uma abordagem é compartilhar ponteiros para os objetos de modelo entre os controladores de exibição.

  • Iteração de força bruta nos controladores de exibição (em Navegador ou Controlador de barra de guias) para definir os dados
  • Defina os dados em prepareForSegue (se storyboards) ou init (se programático)

Como preparar para segue é o mais comum, aqui está um exemplo:

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

Acesso independente

Outra abordagem é lidar com uma tela cheia de dados de cada vez e, em vez de acoplar os controladores de exibição, acoplar cada controlador de exibição à única fonte de dados que eles podem acessar independentemente.

A maneira mais comum de fazer isso é uma instância singleton . Portanto, se o seu objeto singleton fosse, DataAccessvocê poderia fazer o seguinte no método viewDidLoad do UIViewController:

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

Existem ferramentas adicionais que também ajudam a transmitir dados:

  • Observação do valor-chave
  • NSNotification
  • Dados principais
  • NSFetchedResultsController
  • Fonte de dados

Dados principais

O bom do Core Data é que ele tem relacionamentos inversos. Portanto, se você quiser apenas fornecer ao NotesViewController o objeto notes, você poderá, porque ele terá um relacionamento inverso com algo mais como o notebook. Se você precisar de dados no bloco de notas no NotesViewController, poderá voltar ao gráfico de objetos fazendo o seguinte:

let notebookName = note.notebook.name

Leia mais sobre isso no meu blog: Compartilhando código de modelo


10

NewsViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [tbl_View deselectRowAtIndexPath:indexPath animated:YES];
  News *newsObj = [newstitleArr objectAtIndex:indexPath.row];
  NewsDetailViewController *newsDetailView = [[NewsDetailViewController alloc] initWithNibName:@"NewsDetailViewController" bundle:nil];

  newsDetailView.newsHeadlineStr = newsObj.newsHeadline;

  [self.navigationController pushViewController:newsDetailView animated:YES];
}

NewsDetailViewController.h

@interface NewsDetailViewController : UIViewController
@property(nonatomic,retain) NSString *newsHeadlineStr;
@end

NewsDetailViewController.m

@synthesize newsHeadlineStr;

10

A delegação é a única solução para executar essas operações quando você estiver usando arquivos .xib. No entanto, todas as respostas descritas acima são para storyboardarquivos .xibs que você precisa para usar a delegação. essa é a única solução que você pode.

Outra solução é usar o padrão de classe singleton para inicializá-lo uma vez e usá-lo em todo o aplicativo.


10

Se você deseja passar dados do ViewControlerOne para o ViewController, tente estes dois ..

faça isso em ViewControlerOne.h

 @property (nonatomic, strong) NSString *str1;

faça isso em ViewControllerTwo.h

 @property (nonatomic, strong) NSString *str2;

Sintetizar str2 em ViewControllerTwo.m

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

faça isso em ViewControlerOne.m

 - (void)viewDidLoad
 {
   [super viewDidLoad];

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

 }

nos botões, clique em evento, faça isso.

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

faça isso em ViewControllerTwo.m

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

10

Você pode salvar os dados no aplicativo delegado para acessá-los nos controladores de exibição do seu aplicativo. Tudo o que você precisa fazer é criar uma instância compartilhada do delegado do aplicativo

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

Por exemplo

se você declarar um NSArray object *arrayXYZ, poderá acessá-lo em qualquer controlador de exibiçãoappDelegate.arrayXYZ


É o método de escolha para hackathon
Hai Feng Kao

9

Se você deseja enviar dados de um para outro viewController, aqui está uma maneira de fazê-lo:

Digamos que temos viewControllers: ViewController e NewViewController.

em 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

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

}

Em NewViewController.h

#import <UIKit/UIKit.h>

@interface NewViewController : UITableViewController
{
    NSArray *arrayList;

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

}

@property(nonatomic, retain)NSArray *arrayList;

@end

Em 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

Desta forma, podemos passar os dados de um viewcontroller para outro view controller ...


8

Gosto da idéia dos objetos Modelo e Mock baseados no NSProxy para confirmar ou descartar dados, se o usuário selecionar puder ser cancelado.

É fácil transmitir dados, já que é um único objeto ou alguns objetos e, se você digitar o controlador UINavigationController, pode manter a referência do modelo dentro e todos os controladores de exibição por push podem acessá-lo diretamente do controlador de navegação.


8

Eu já vi muitas pessoas complicando isso usando o didSelectRowAtPathmétodo Estou usando o Core Data no meu exemplo.

- (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 linhas de código dentro do método e pronto.


6

Existem muitas respostas para essas perguntas, oferecendo muitas maneiras diferentes de executar a comunicação do controlador de exibição que de fato funcionaria, mas não vejo em nenhum lugar mencionado qual é realmente o melhor para usar e quais evitar.

Na prática, na minha opinião, apenas algumas soluções são recomendadas:

  • Para passar dados adiante:
    • substituir o prepare(for:sender:)método UIViewControllerao usar um storyboard e segues
    • passa dados através de um inicializador ou através de propriedades ao executar transições do controlador de exibição através do código
  • Para passar dados para trás
    • atualize o estado compartilhado do aplicativo (que você pode transmitir entre os controladores de exibição com um dos métodos acima)
    • usar delegação
    • use um desenrolar segue

Soluções que eu recomendo NÃO usar:

  • Referenciando diretamente o controlador anterior em vez de usar a delegação
  • Compartilhando dados através de um singleton
  • Passando dados pelo delegado do aplicativo
  • Compartilhando dados através dos padrões do usuário
  • Passando dados através de notificações

Essas soluções, embora funcionem a curto prazo, introduzem muitas dependências que distorcem a arquitetura do aplicativo e criam mais problemas posteriormente.

Para os interessados, escrevi alguns artigos que abordam esses pontos com mais profundidade e destacam as várias desvantagens:

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.