Passando i dati tra i controller di visualizzazione


1371

Sono nuovo su iOS e Objective-C e l'intero paradigma MVC e sono bloccato con quanto segue:

Ho una vista che funge da modulo di inserimento dati e voglio dare all'utente la possibilità di selezionare più prodotti. I prodotti sono elencati in un'altra vista con a UITableViewControllere ho abilitato più selezioni.

La mia domanda è: come trasferisco i dati da una vista all'altra? Terrò le selezioni su UITableViewin un array, ma come posso poi passarle alla vista precedente del modulo di immissione dei dati in modo che possa essere salvata insieme agli altri dati ai dati principali al momento dell'invio del modulo?

Ho navigato in giro e ho visto alcune persone dichiarare un array nel delegato dell'app. Ho letto qualcosa su Singletons ma non capisco cosa siano e ho letto qualcosa sulla creazione di un modello di dati.

Quale sarebbe il modo corretto di eseguire questo e come potrei farlo?

Risposte:


1683

Questa domanda sembra essere molto popolare qui su StackOverflow, quindi ho pensato di provare a dare una risposta migliore per aiutare le persone che iniziano nel mondo di iOS come me.

Spero che questa risposta sia abbastanza chiara da capire e che non ho perso nulla.

Inoltro dati in avanti

Passare i dati in avanti a un controller di visualizzazione da un altro controller di visualizzazione. Utilizzeresti questo metodo se desideri trasferire un oggetto / valore da un controller di visualizzazione a un altro controller di visualizzazione che potresti trasferire su uno stack di navigazione.

Per questo esempio, avremo ViewControllerAeViewControllerB

Per passare un BOOLvalore ViewControllerAa ViewControllerBfaremmo quanto segue.

  1. nel ViewControllerB.hcreare una proprietà per ilBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. in ViewControllerAè necessario dire di lui circa ViewControllerBin modo da utilizzare un

    #import "ViewControllerB.h"

    Quindi dove si desidera caricare la vista ad es. didSelectRowAtIndexo alcuni IBActiondevi impostare la proprietà ViewControllerBprima di spingerla nello stack di navigazione.

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

    Questo imposterà isSomethingEnabledin ViewControllerBa BOOLvalore YES.

Passare i dati in avanti usando Segues

Se stai utilizzando gli storyboard, molto probabilmente stai utilizzando segues e avrai bisogno di questa procedura per trasmettere i dati. Questo è simile al precedente ma invece di passare i dati prima di spingere il controller di visualizzazione, si utilizza un metodo chiamato

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

Quindi per passare un BOOLda ViewControllerAa ViewControllerBfaremmo quanto segue:

  1. nel ViewControllerB.hcreare una proprietà per ilBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. in ViewControllerAè necessario dire di lui circa ViewControllerBin modo da utilizzare un

    #import "ViewControllerB.h"
  3. Crea un seguito dallo ViewControllerAa ViewControllerBsullo storyboard e assegnagli un identificatore, in questo esempio lo chiameremo"showDetailSegue"

  4. Successivamente, dobbiamo aggiungere il metodo a ViewControllerAquello che viene chiamato quando viene eseguita una qualsiasi conseguenza, per questo motivo dobbiamo rilevare quale segue è stato chiamato e quindi fare qualcosa. Nel nostro esempio controlleremo "showDetailSegue"e, se eseguito, passeremo il nostro BOOLvalore aViewControllerB

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

    Se le visualizzazioni sono incorporate in un controller di navigazione, è necessario modificare leggermente il metodo sopra riportato di seguito

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

    Questo imposterà isSomethingEnabledin ViewControllerBa BOOLvalore YES.

Restituzione dei dati

Per passare di nuovo i dati da ViewControllerBa ViewControllerAè necessario utilizzare protocolli e Delegati o blocchi , quest'ultimo può essere utilizzato come un meccanismo loosely coupled per le richiamate.

Per fare questo faremo ViewControllerAun delegato di ViewControllerB. Ciò consente ViewControllerBdi inviare un messaggio per ViewControllerAconsentirci di inviare nuovamente i dati.

Per ViewControllerAessere un delegato di ViewControllerBesso deve essere conforme al ViewControllerBprotocollo che dobbiamo specificare. Questo dice ViewControllerAquali metodi deve implementare.

  1. In ViewControllerB.h, sotto il #import, ma sopra @interfacesi specifica il protocollo.

    @class ViewControllerB;
    
    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
  2. prossimo ancora nel ViewControllerB.hdevi impostare una delegateproprietà e sintetizzareViewControllerB.m

    @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. In ViewControllerBchiamiamo un messaggio sul delegatequando apriamo il controller di visualizzazione.

    NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
  4. Questo è tutto per ViewControllerB. Ora in ViewControllerA.h, dire ViewControllerAdi importare ViewControllerBe conformarsi al suo protocollo.

    #import "ViewControllerB.h"
    
    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
  5. Nel ViewControllerA.mrealizzare il seguente metodo dal nostro protocollo

    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
        NSLog(@"This was returned from ViewControllerB %@",item);
    }
  6. Prima viewControllerBdi passare allo stack di navigazione, dobbiamo dire ViewControllerBche ViewControllerAè il suo delegato, altrimenti avremo un errore.

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

Riferimenti

  1. Utilizzo della delega per comunicare con altri controller di visualizzazione nella Guida alla programmazione del controller di visualizzazione
  2. Modello delegato

NSNotification center È un altro modo per passare i dati.

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

Passaggio dei dati da una classe all'altra (una classe può essere qualsiasi controller, gestore di rete / sessione, sottoclasse UIView o qualsiasi altra classe)

I blocchi sono funzioni anonime.

Questo esempio passa i dati dal controller B al controller A

definire un blocco

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

aggiungi il gestore di blocchi (listener) dove hai bisogno di un valore (ad esempio hai bisogno della tua risposta API in ControllerA o hai bisogno di dati ContorllerB su A)

// in ContollerA.m

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

Vai al controller B

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

blocco di fuoco

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

Un altro esempio funzionante per i blocchi


24
Dobbiamo anche mettere un @class ViewControllerB;sopra la definizione @protocol? Senza di essa viene visualizzato l'errore "Tipo previsto" su ViewControllerB nella riga: - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; all'interno della @protocoldichiarazione
alan-p

4
Funziona benissimo. Come dice alan-p, non dimenticare di scrivere @class ViewControllerB; sopra il protocollo altrimenti riceverai l'errore "Previsto un tipo".
Andrew Davis,

6
non hai bisogno di delegati per tornare indietro, basta usare lo svolgimento.
Malhal,

4
Quando inserisco "viewControllerB.delegate = self;" in ViewControllerB visualizzo un errore. Assegnando a "id <ViewControllerBDelegate>" da un tipo incompatibile "ViewControllerB * const __strong", non sono sicuro di cosa sto facendo di sbagliato. Qualcuno può aiutare? Inoltre ho dovuto cambiare: initWithNib -> initWithNibName.
uplearnedu.com

4
se stai usando NavigationControllerdevi usare [self.navigationController pushViewController:viewController animated:YES];invece[self pushViewController:viewControllerB animated:YES];
Nazir

192

veloce

Ci sono un sacco di spiegazioni qui e intorno a StackOverflow, ma se sei un principiante solo cercando di ottenere qualcosa di base per lavorare, prova a guardare questo tutorial di YouTube (È ciò che mi ha aiutato a capire finalmente come farlo).

Passare i dati in avanti al View Controller successivo

Di seguito è riportato un esempio basato sul video. L'idea è di passare una stringa dal campo di testo nel controller della prima vista all'etichetta nel controller della seconda vista.

inserisci qui la descrizione dell'immagine

Crea il layout dello storyboard in Interface Builder. Per fare ciò, basta Controlfare clic sul pulsante e trascinare il mouse sul controller Second View.

First View Controller

Il codice per il 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!
    }

}

Controller di seconda vista

E il codice per il controller Second View è

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
    }

}

Non dimenticare

  • Collegare le prese per il UITextFielde il UILabel.
  • Impostare il primo e il secondo controller di visualizzazione sui file Swift appropriati in IB.

Passando i dati al View Controller precedente

Per trasferire i dati dal controller della seconda vista al controller della prima vista, utilizzare un protocollo e un delegato . Questo video è una passeggiata molto chiara di questo processo:

Di seguito è riportato un esempio basato sul video (con alcune modifiche).

inserisci qui la descrizione dell'immagine

Crea il layout dello storyboard in Interface Builder. Ancora una volta, per fare quanto segue, è sufficiente Controltrascinare dal pulsante al controller della seconda vista. Impostare l'identificatore segue su showSecondViewController. Inoltre, non dimenticare di collegare i punti vendita e le azioni utilizzando i nomi nel seguente codice.

First View Controller

Il codice per il 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
    }
}

Nota l'uso del nostro DataEnteredDelegateprotocollo personalizzato .

Controller e protocollo di seconda visualizzazione

Il codice per il controller della seconda vista è

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

Si noti che protocolè al di fuori della classe View Controller.

Questo è tutto. Eseguendo l'app ora dovresti essere in grado di inviare i dati dal controller della seconda vista al primo.


Dati alcuni degli ultimi aggiornamenti di Swift, questo è ancora un modello comune da implementare?
piofusco,

4
La maggior parte degli aggiornamenti di Swift che ho visto sono stati cambiamenti sintattici relativamente minori, non cambiamenti nel modo in cui i dati vengono passati tra i controller di visualizzazione. Se venissi a conoscenza di importanti cambiamenti del genere, aggiornerò la mia risposta.
Suragch,

2
offtopic - iOS ha un modo così brutto di passare parametri a nuovi controller di visualizzazione, incredibile - devi impostare i parametri non in un posto quando stai effettuando la chiamata, ma in un altro. Android ha un approccio migliore a questo proposito: quando inizi un'attività puoi passare qualsiasi dato (beh, quasi) tramite il suo intento iniziale. Facile. Non c'è bisogno di cast o qualcosa del genere. Anche il ritorno dei valori di ritorno al chiamante è una cosa essenziale, non è necessario delegare. Naturalmente è possibile usare anche approcci brutti, nessun problema lì))
Mixaz

1
@Himanshu, prima ottieni un riferimento al controller della seconda vista. Quindi aggiornare la variabile pubblica che contiene.
Suragch,

8
@Miele. Penso che la parola "delegato" sia confusa. Vorrei usare la parola "lavoratore". Il "lavoratore" (controller della prima vista) fa qualunque cosa il "capo" (controller della seconda vista) gli dica di fare. Il "capo" non sa chi sarà il suo "lavoratore"; potrebbe essere chiunque. Quindi nella prima vista controller (classe "worker"), dice, sarò il tuo "lavoratore". Dimmi cosa scrivere nell'etichetta e lo farò per te. Quindi, secondViewController.delegate = selfsignifica "Accetto di essere il lavoratore del capo". Vedi questa risposta per un altro esempio e ulteriori spiegazioni.
Suragch,

136

La M in MVC è per "Modello" e nel paradigma MVC il ruolo delle classi del modello è di gestire i dati di un programma. Un modello è l'opposto di una vista: una vista sa come visualizzare i dati, ma non sa nulla su cosa fare con i dati, mentre un modello sa tutto su come lavorare con i dati, ma nulla su come visualizzarli. I modelli possono essere complicati, ma non devono esserlo: il modello per la tua app potrebbe essere semplice come una serie di stringhe o dizionari.

Il ruolo di un controller è di mediare tra vista e modello. Pertanto, hanno bisogno di un riferimento a uno o più oggetti vista e uno o più oggetti modello. Supponiamo che il tuo modello sia un array di dizionari, con ogni dizionario che rappresenta una riga nella tabella. La vista principale per l'app mostra quella tabella e potrebbe essere responsabile del caricamento dell'array da un file. Quando l'utente decide di aggiungere una nuova riga alla tabella, tocca un pulsante e il controller crea un nuovo dizionario (mutabile) e lo aggiunge all'array. Per compilare la riga, il controller crea un controller di visualizzazione dettagli e gli fornisce il nuovo dizionario. Il controller della vista dettagli riempie il dizionario e ritorna. Il dizionario fa già parte del modello, quindi non deve succedere nient'altro.


95

Esistono vari modi in cui i dati possono essere ricevuti in una classe diversa in iOS. Per esempio -

  1. Inizializzazione diretta dopo l'assegnazione di un'altra classe.
  2. Delega - per il ritorno dei dati
  3. Notifica: per la trasmissione di dati a più classi contemporaneamente
  4. Salvataggio NSUserDefaults: per accedervi in ​​un secondo momento
  5. Classi singleton
  6. Database e altri meccanismi di archiviazione come plist, ecc.

Ma per il semplice scenario di passare un valore a una classe diversa la cui allocazione viene eseguita nella classe corrente, il metodo più comune e preferito sarebbe l'impostazione diretta dei valori dopo l'allocazione. Questo viene fatto come segue: -

Possiamo capirlo usando due controller: Controller1 e Controller2

Supponiamo che nella classe Controller1 desideri creare l'oggetto Controller2 e inviarlo con un valore String passato. Questo può essere fatto come questo: -

- (void)pushToController2 {

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

Nell'implementazione della classe Controller2 ci sarà questa funzione come-

@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

Puoi anche impostare direttamente le proprietà della classe Controller2 in modo simile al seguente:

- (void)pushToController2 {

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

Per passare più valori è possibile utilizzare più parametri come: -

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

Oppure, se è necessario passare più di 3 parametri correlati a una funzione comune, è possibile memorizzare i valori in una classe Model e passare quel modelloObject alla classe successiva

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

Quindi in breve se vuoi -

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.

Spero che sia di aiuto


84

Dopo ulteriori ricerche, sembrava che Protocolli e Delegati fossero il modo corretto / preferito da Apple per farlo.

Ho finito per usare questo esempio

Condivisione dei dati tra i controller di visualizzazione e altri oggetti su iPhone Dev SDK

Ha funzionato bene e mi ha permesso di passare una stringa e un array avanti e indietro tra le mie viste.

Grazie per tutto il vostro aiuto


3
non usare protocolli e delegati, basta usare svolgersi.
Malhal,

1
@malhal Cosa succede se non usi gli storyboard ??
Evan R,

Odio anche protocolli e delegati inutili. @malhal
DawnSong,

@EvanR È possibile creare ed eseguire segues nel codice. È tutto uguale.
DawnSong,

1
In sostanza l'intero QA in questa pagina è "dai vecchi tempi prima delle visualizzazioni del contenitore". In un milione di anni non ti preoccuperesti mai di protocolli o delegati ora. Ogni piccola cosa che fai su qualsiasi schermata è comunque una vista contenitore, quindi la domanda in realtà non esiste più: hai già tutti i riferimenti "su e giù" da tutte le viste contenitore.
Fattie,

66

Trovo la versione più semplice ed elegante con blocchi di passaggio. Diamo un nome al controller della vista che attende i dati restituiti come "A" e che restituisce il controller della vista come "B". In questo esempio vogliamo ottenere 2 valori: primo di Tipo1 e secondo di Tipo2.

Supponendo di utilizzare Storyboard, il primo controller imposta il blocco di richiamata, ad esempio durante la preparazione seguente:

- (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 il controller di visualizzazione "B" devono dichiarare la proprietà callback, BViewController.h:

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

Che nel file di implementazione BViewController.m dopo che abbiamo desiderato i valori per restituire il nostro callback dovrebbe essere chiamato:

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

Una cosa da ricordare è che l'uso del blocco spesso deve gestire riferimenti forti e __ deboli come spiegato qui


Perché non valore essere un parametro per il blocco di callback piuttosto che essere una proprietà separata?
Timuçin,

56

Ci sono alcune buone informazioni in molte delle risposte fornite, ma nessuna risponde alla domanda in modo completo.

La domanda si pone sul passaggio di informazioni tra i controller di visualizzazione. L'esempio specifico fornito chiede informazioni sul passaggio di informazioni tra viste, ma data la novità dichiarata su iOS, il poster originale probabilmente significava tra viewController, non tra viste (senza alcun coinvolgimento da parte dei ViewController). Sembra che tutte le risposte si concentrino su due controller di visualizzazione, ma cosa succede se l'app si evolve per coinvolgere più di due controller di visualizzazione nello scambio di informazioni?

Il poster originale chiedeva anche di Singletons e l'uso dell'AppDelegate . È necessario rispondere a queste domande.

Per aiutare chiunque guardi a questa domanda, che desidera una risposta completa, cercherò di fornirla.

Scenari applicativi

Piuttosto che avere una discussione altamente ipotetica, astratta, aiuta ad avere in mente applicazioni concrete. Per aiutare a definire una situazione di controller a due viste e una situazione di controller a più di due viste, ho intenzione di definire due scenari applicativi concreti.

Scenario 1: al massimo due controller di visualizzazione devono condividere informazioni. Vedi diagramma uno.

diagramma del problema originale

Esistono due controller di visualizzazione nell'applicazione. Esiste un ViewControllerA (Modulo di immissione dati) e Visualizza Controller B (Elenco prodotti). Gli articoli selezionati nell'elenco prodotti devono corrispondere agli articoli visualizzati nella casella di testo nel modulo di immissione dati. In questo scenario, ViewControllerA e ViewControllerB devono comunicare direttamente tra loro e nessun altro controller di visualizzazione.

Scenario due : più di due controller di visualizzazione devono condividere le stesse informazioni. Vedi diagramma due.

diagramma di applicazione dell'inventario domestico

Ci sono quattro controller di visualizzazione nell'applicazione. Si tratta di un'applicazione basata su schede per la gestione dell'inventario domestico. Tre controller di visualizzazione presentano visualizzazioni filtrate in modo diverso degli stessi dati:

  • ViewControllerA - Articoli di lusso
  • ViewControllerB - Articoli non assicurati
  • ViewControllerC - Inventario di tutta la casa
  • ViewControllerD - Aggiungi nuovo modulo articolo

Ogni volta che un singolo elemento viene creato o modificato, deve anche sincronizzarsi con gli altri controller di visualizzazione. Ad esempio, se aggiungiamo una barca in ViewControllerD, ma non è ancora assicurata, la barca deve apparire quando l'utente accede a ViewControllerA (articoli di lusso) e anche ViewControllerC (intero inventario domestico), ma non quando l'utente accede a ViewControllerB (Articoli non assicurati). Dobbiamo preoccuparci non solo di aggiungere nuovi elementi, ma anche di eliminare elementi (che possono essere consentiti da uno dei quattro controller di visualizzazione) o di modificare elementi esistenti (che possono essere consentiti dal "Aggiungi nuovo modulo articolo", riproponendo lo stesso per l'editing).

Poiché tutti i controller di visualizzazione devono condividere gli stessi dati, tutti e quattro i controller di visualizzazione devono rimanere sincronizzati e pertanto è necessario che vi sia una sorta di comunicazione con tutti gli altri controller di visualizzazione, ogni volta che un controller di visualizzazione singola modifica i dati sottostanti. Dovrebbe essere abbastanza ovvio che non vogliamo che ciascun controller di visualizzazione comunichi direttamente con l'altro controller di visualizzazione in questo scenario. Nel caso in cui non sia ovvio, considera se avevamo 20 controller di visualizzazione diversi (anziché solo 4). Quanto sarebbe difficile e soggetto a errori notificare a ciascuno degli altri 19 controller di visualizzazione ogni volta che un controller di visualizzazione ha apportato una modifica?

Le soluzioni: delegati e modello di osservatore e singoli

Nello scenario uno, abbiamo diverse soluzioni praticabili, come altre risposte hanno dato

  • segues
  • delegati
  • impostazione diretta delle proprietà sui controller di visualizzazione
  • NSUserDefaults (in realtà una scelta sbagliata)

Nello scenario due, abbiamo altre soluzioni praticabili:

  • Modello di osservatore
  • Singletons

Un singleton è un'istanza di una classe, tale istanza è l'unica istanza esistente durante la sua vita. Un singleton prende il nome dal fatto che è la singola istanza. Normalmente gli sviluppatori che usano i singoli hanno metodi di classe speciali per accedervi.

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

Ora che capiamo cos'è un singleton, discutiamo di come un singleton si adatta al modello di osservatore. Il modello di osservatore viene utilizzato per un oggetto per rispondere alle modifiche di un altro oggetto. Nel secondo scenario, abbiamo quattro diversi controller di visualizzazione, che tutti vogliono conoscere le modifiche ai dati sottostanti. I "dati sottostanti" dovrebbero appartenere a una singola istanza, un singleton. La "conoscenza delle modifiche" si ottiene osservando le modifiche apportate al singleton.

L'applicazione di inventario domestico avrebbe un'unica istanza di una classe progettata per gestire un elenco di articoli di inventario. Il gestore gestirà una raccolta di articoli per la casa. Di seguito è una definizione di classe per il gestore dati:

#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 la raccolta di articoli di inventario domestico cambia, i controllori della vista devono essere resi consapevoli di questa modifica. La definizione di classe sopra non rende evidente come ciò accadrà. Dobbiamo seguire il modello di osservatore. I controller di visualizzazione devono osservare formalmente il sharedManager. Esistono due modi per osservare un altro oggetto:

  • Key-Value-Observing (KVO)
  • NSNotificationCenter.

Nello scenario due, non abbiamo una singola proprietà di HouseholdInventoryManager che potrebbe essere osservata usando KVO. Poiché non abbiamo un'unica proprietà che è facilmente osservabile, il modello di osservatore, in questo caso, deve essere implementato utilizzando NSNotificationCenter. Ognuno dei quattro controller di visualizzazione si iscriverebbe alle notifiche e il SharedManager invierà notifiche al centro di notifica quando appropriato. Il gestore dell'inventario non ha bisogno di sapere nulla sui controller della vista o sulle istanze di altre classi che potrebbero essere interessati a sapere quando cambia la raccolta degli articoli dell'inventario; NSNotificationCenter si occupa di questi dettagli di implementazione. I controller di visualizzazione si iscrivono semplicemente alle notifiche e il gestore dati pubblica semplicemente le notifiche.

Molti programmatori principianti sfruttano il fatto che esiste sempre esattamente un delegato dell'applicazione nel corso della vita dell'applicazione, che è accessibile a livello globale. I programmatori principianti usano questo fatto per inserire oggetti e funzionalità nell'appDelegate come comodità per l'accesso da qualsiasi altra parte dell'applicazione. Solo perché AppDelegate è un singleton non significa che dovrebbe sostituire tutti gli altri singleton. Questa è una cattiva pratica in quanto comporta un peso eccessivo per una classe, rompendo le buone pratiche orientate agli oggetti. Ogni classe dovrebbe avere un ruolo chiaro che può essere facilmente spiegato, spesso semplicemente con il nome della classe.

Ogni volta che il delegato dell'applicazione inizia a gonfiarsi, inizia a rimuovere la funzionalità in singoli. Ad esempio, lo stack di dati di base non deve essere lasciato in AppDelegate, ma deve invece essere inserito nella propria classe, una classe coreDataManager.

Riferimenti


41

L'OP non ha menzionato i controller di visualizzazione ma molte delle risposte lo fanno, che volevo entrare in contatto con ciò che alcune delle nuove funzionalità di LLVM consentono di rendere più semplice quando si desidera passare i dati da un controller di visualizzazione a un altro e quindi ottenere alcuni risultati.

I follower dello storyboard, i blocchi ARC e LLVM rendono questo più facile che mai per me. Alcune risposte sopra menzionate storyboard e segues già, ma si basavano ancora sulla delega. Definire i delegati sicuramente funziona, ma alcune persone potrebbero trovare più facile passare puntatori o blocchi di codice.

Con UINavigators e segues, ci sono modi semplici per passare le informazioni al controller del servoassistito e recuperarle. ARC semplifica il passaggio di puntatori a cose derivate da NSObjects, quindi se si desidera che il controller di servizi secondari aggiunga / cambi / modifichi alcuni dati, passare un puntatore a un'istanza mutabile. I blocchi semplificano le azioni di passaggio, quindi se si desidera che il controller subordinato invochi un'azione sul controller di livello superiore, passare un blocco. Definisci il blocco per accettare qualsiasi numero di argomenti che abbia senso per te. Puoi anche progettare l'API per utilizzare più blocchi se si adatta meglio alle cose.

Ecco due esempi banali della colla seguente. Il primo è semplice e mostra un parametro passato per l'input, il secondo per l'output.

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

Questo secondo esempio mostra il passaggio di un blocco callback per il secondo argomento. Mi piace usare i blocchi perché mantiene i dettagli rilevanti vicini nella fonte - la fonte di livello superiore.

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

Passare indietro i dati da ViewController 2 (destinazione) a viewController 1 (Fonte) è la cosa più interessante. Supponendo che usi storyBoard questi sono tutti i modi in cui ho scoperto:

  • Delegare
  • Notifica
  • Impostazioni predefinite dell'utente
  • Singleton

Quelli erano già discussi qui.

Ho scoperto che ci sono altri modi:

-Utilizzo di callback Block:

utilizzare nel prepareForSeguemetodo nel VC1

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

-Utilizzando storyboard Unwind (Esci)

Implementa un metodo con un argomento UIStoryboardSegue in VC 1, come questo:

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

Nella storyBoard agganciare il pulsante "return" al pulsante verde Exit (Unwind) del vc. Ora hai un seguito che "torna indietro" in modo da poter utilizzare la proprietà destinationViewController nel preparForSegue di VC2 e modificare qualsiasi proprietà di VC1 prima che torni indietro.

  • Un'altra opzione di utilizzo degli storyboard Undwind (Esci): puoi usare il metodo che hai scritto in VC1

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

    E nel preparForSegue di VC1 è possibile modificare qualsiasi proprietà che si desidera condividere.

In entrambe le opzioni di svolgimento è possibile impostare la proprietà tag del pulsante e controllarlo in preparForSegue.

Spero di aver aggiunto qualcosa alla discussione.

:) Saluti.


40

Esistono diversi metodi per la condivisione dei dati.

  1. Puoi sempre condividere i dati utilizzando NSUserDefaults. Impostare il valore che si desidera condividere rispetto a una chiave di propria scelta e ottenere il valore da NSUserDefaultassociato a quella chiave nel controller della vista successiva.

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
  2. Puoi semplicemente creare una proprietà in viewcontrollerA. Crea un oggetto di viewcontrollerAin viewcontrollerBe assegna il valore desiderato a quella proprietà.

  3. È inoltre possibile creare delegati personalizzati per questo.


30
Lo scopo tipico di NSUserDefaults è quello di memorizzare le preferenze dell'utente che persistono tra le esecuzioni delle app, quindi tutto ciò che viene archiviato rimarrà qui a meno che non venga rimosso esplicitamente. È davvero una cattiva idea usarlo per passare informazioni tra i controller di visualizzazione (o qualsiasi altro oggetto) in un'app.
José González,

30

Se si desidera passare i dati da un controller all'altro, provare questo codice

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

Questa è una risposta molto vecchia e questo è anti pattern, per favore usa i delegati. Non usare questo approccio !!

1. Creare l'istanza del primo View Controller nel secondo View Controller e renderne la proprietà @property (nonatomic,assign).

2. Assegnare l' SecondviewControlleristanza di questo controller di visualizzazione.

2. Al termine dell'operazione di selezione, copiare l'array nel primo View Controller, quando si scarica SecondView, FirstView conterrà i dati dell'array.

Spero che sia di aiuto.


2
Non credo che questo sia il modo corretto di procedere poiché crea un collegamento molto discontinuo tra i controller di visualizzazione. Non attenersi davvero a MVC.
Matt Price,

1
Se si desidera seguire rigorosamente MVC, utilizzare NSNotificationCenter , è possibile chiamare un metodo da ViewControllerA a ViewControllerB, verificare che ciò possa essere d'aiuto
kaar3k

28

Stavo cercando questa soluzione da molto tempo, Atlast l'ho trovata. Prima di tutto dichiarare come tutti gli oggetti nel tuo file SecondViewController.h

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

Ora nel file di implementazione allocare la memoria per quegli oggetti come questo

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

Ora hai allocato la memoria Arraye l'oggetto. ora puoi riempire quel ricordo prima di spingerloViewController

Vai su SecondViewController.h e scrivi due metodi

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

nel file di implementazione è possibile implementare la funzione

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

aspettandoti che tu CustomObjectdebba avere una funzione setter con essa.

ora il tuo lavoro di base è finito. vai nel punto in cui vuoi spingereSecondViewController e fai le seguenti cose

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

Fai attenzione agli errori di ortografia.


24

Non è questo il modo di farlo, dovresti usare i delegati, suppongo che abbiamo due controller di visualizzazione ViewController1 e ViewController2 e questa cosa di controllo è nel primo e quando il suo stato cambia, vuoi fare qualcosa in ViewController2, per raggiungilo nel modo giusto, dovresti fare quanto segue:

Aggiungi un nuovo file al tuo file di progetto (protocollo Objective-C) -> Nuovo, ora chiamalo ViewController1Delegate o come vuoi e scrivili tra le direttive @interface e @end

@optional

- (void)checkStateDidChange:(BOOL)checked;

Ora vai su ViewController2.h e aggiungi

#import "ViewController1Delegate.h"

quindi cambia la sua definizione in

@interface ViewController2: UIViewController<ViewController1Delegate>

Ora vai su ViewController2.m e all'interno dell'implementazione aggiungi:

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

Ora vai su ViewController1.h e aggiungi la seguente proprietà:

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

Ora se stai creando ViewController1 all'interno di ViewController2 dopo qualche evento, allora dovresti farlo in questo modo usando i file NIB:

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

Ora sei pronto, ogni volta che rilevi un evento di controllo modificato in ViewController1, tutto ciò che devi fare è il seguente

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

Per favore, dimmi se c'è qualcosa che non è chiaro se non ho capito bene la tua domanda.


23

Se si desidera inviare dati da uno all'altro ViewController, ecco un modo per farlo:

Supponiamo che abbiamo viewController: viewControllerA e viewControllerB

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

}

Quindi è così che puoi passare i dati da viewControllerA a viewControllerB senza impostare alcun delegato. ;)


1
Ho provato a usare il tuo codice ur nel mio progetto, ma non sono in grado di ottenere i valori in viewcontrollerB. Puoi dirmi quale potrebbe essere il problema?
X-Coder,

1
@Ajitthala Puoi incollare il tuo codice in una nuova domanda? Proverò a risolvere il tuo problema. :)
Aniruddh Joshi,

1
è sbagliato non usare i metodi init e fare semplicemente qualcosa come vcB.string = @ "asdf" dal viewcontroller A?
khanh.tran.vinh,

1
@ khanh.tran.vinh Dipende dal fatto che tu stia usando ARC o meno.
Aniruddh Joshi il

21

So che questo è un argomento battuto, ma per coloro che cercano di rispondere a questa domanda con una inclinazione SWIFT e vogliono un esempio semplice, ecco il mio metodo di passaggio per passare i dati se stai usando un seguito per spostarti.

È simile al precedente ma senza pulsanti, etichette e simili. Basta semplicemente passare i dati da una vista all'altra.

Installa lo Storyboard

Ci sono tre parti.

  1. Il mittente
  2. The Segue
  3. Il ricevente

Questo è un layout di vista molto semplice con un seguito tra di loro.


Layout della vista molto semplice.  Nota: nessun controller di navigazione


Ecco la configurazione per il mittente


Il mittente


Ecco la configurazione per il ricevitore.


Il ricevente


Infine, l'installazione per il segue.


L'identificatore di Segue


I controller di visualizzazione

Manteniamo questo semplice, quindi nessun pulsante, non azioni, stiamo semplicemente spostando i dati dal mittente al destinatario quando l'applicazione si carica e quindi trasmettendo il valore trasmesso alla console.

Questa pagina prende il valore caricato inizialmente e lo passa.

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

    }

}

Questa pagina invia semplicemente il valore della variabile alla console quando viene caricata. A questo punto, il nostro film preferito dovrebbe trovarsi in quella variabile.

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

    }   
}

È così che puoi affrontarlo se vuoi usare un seguito e non hai le tue pagine sotto un controller di navigazione.

Una volta eseguito, dovrebbe passare automaticamente alla vista del destinatario e passare il valore dal mittente al destinatario, visualizzando il valore nella console.

Ghost Busters è un popolo classico.


19

Nel mio caso ho usato una classe singleton che può funzionare come un oggetto globale che consente l'accesso ai dati da quasi ovunque nell'app. La prima cosa è costruire una classe singleton. Si prega di fare riferimento alla pagina, " Che cosa dovrebbe mio Objective-C Singleton assomigliare? " E quello che ho fatto per rendere l'oggetto accessibile a livello globale è stato semplicemente importare in appName_Prefix.pchche è per l'applicazione di istruzione import in ogni classe. Per accedere a questo oggetto e per usarlo, ho semplicemente implementato il metodo class per restituire l'istanza condivisa, che contiene le sue variabili


Questa è la risposta corretta Usa un singleton come "modello". Nota che, come dice Caleb, "il modello per la tua app potrebbe essere semplice come una matrice di stringhe" . È fondamentale notare che fare un singleton in Swift, è davvero banale . (Così semplice non vale nemmeno la pena menzionarlo qui - solo Google.) Per i nuovi programmatori, vale la pena capire che fare un singleton era un vero dolore nel culo . Tuttavia, i singleton sono assolutamente centrali nella programmazione iOS: tutto ciò che Apple fa è un singleton. Ecco perché Apple ha finalmente realizzato trtvial (in Swift) per creare correttamente i singoli.
Fattie,

1
Si noti, tuttavia, che in questi giorni (2016+) "tutto è una vista contenitore in iOS". Ogni singola cosa che fai sullo schermo fai una piccola vista contenitore. È abbastanza banale ottenere i riferimenti "su e giù" per le catene di viste contenitore (anche se Apple renderà più semplice in futuro), e lo fai comunque per quasi ogni vista contenitore. Quindi, se lo hai fatto comunque, hai la risposta; non c'è bisogno di un singleton. Introduzione alla vista contenitore ... stackoverflow.com/a/23403979/294884
Fattie

19

Swift 5

Bene, la risposta di Matt Price va benissimo per il passaggio dei dati, ma ho intenzione di riscriverli, nella versione più recente di Swift, perché credo che i nuovi programmatori lo trovino molto difficile a causa della nuova sintassi e dei metodi / framework, poiché il post originale è in Objective-C.

Esistono più opzioni per il passaggio dei dati tra i controller di visualizzazione.

  1. Usando Push del controller di navigazione
  2. Usando Segue
  3. Utilizzo del delegato
  4. Utilizzo di Notification Observer
  5. Utilizzando Block

Riscriverò la sua logica in Swift con l'ultimo iOS Framework


Passaggio dei dati tramite il controller di navigazione Push : Da ViewControllerA a ViewControllerB

Passaggio 1. Dichiarare la variabile in ViewControllerB

var isSomethingEnabled = false

Passaggio 2. Stampa variabile nel metodo ViewDidLoad di ViewControllerB

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

Passaggio 3. In ViewControllerA Passare i dati mentre si spinge attraverso il controller di navigazione

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

Quindi ecco il codice completo per:

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

Passaggio dei dati attraverso Segue : da ViewControllerA a ViewControllerB

Passaggio 1. Creare Segue da ViewControllerA a ViewControllerB e fornire Identifier = showDetailSegue nello Storyboard come mostrato di seguito

inserisci qui la descrizione dell'immagine

Passaggio 2. In ViewControllerB Dichiarare un valido chiamato isSomethingEnabled e stamparne il valore.

Passaggio 3. In ViewController Un passaggio è il valore di Qualcosa abilitato durante il passaggio di Segue

Quindi ecco il codice completo per:

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

Passaggio dei dati tramite delegato : da ViewControllerB a ViewControllerA

Passaggio 1. Dichiarare il protocollo ViewControllerBDelegate nel file ViewControllerB ma al di fuori della 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?)
}

Passaggio 2. Dichiara Delega dell'istanza variabile in ViewControllerB

var delegate: ViewControllerBDelegate?

Passaggio 3. Invia i dati per il delegato all'interno del metodo viewDidLoad di ViewControllerB

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

Passaggio 4. Confermare ViewControllerB Eliminare in ViewControllerA

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

Passaggio 5. Confermare che si implementerà il delegato in 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)
            }
        }

Passaggio 6. Implementare il metodo delegato per la ricezione di dati in ViewControllerA

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

Quindi ecco il codice completo per:

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

Passaggio dei dati tramite Notification Observer : da ViewControllerB a ViewControllerA

Passaggio 1. Impostare e pubblicare i dati nell'osservatore delle notifiche in ViewControllerB

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

Passaggio 2. Aggiungi Osservatore notifiche in ViewControllerA

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

Passaggio 3. Ricevi il valore dei dati di notifica in ViewControllerA

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

Quindi ecco il codice completo per:

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

Passaggio dei dati attraverso il blocco : da ViewControllerB a ViewControllerA

Passaggio 1. Dichiarare il blocco in ViewControllerB

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

Passaggio 2. Impostare i dati in blocco in ViewControllerB

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

Passaggio 3. Ricezione dei dati di blocco in ViewControllerA

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

Quindi ecco il codice completo per:

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

Puoi trovare un'applicazione di esempio completa sul mio GitHub Per favore fatemi sapere se avete domande su questo.


18

Passando i dati tra FirstViewController a SecondViewController come di seguito

Per esempio:

FirstViewController Valore stringa come

StrFirstValue = @"first";

così possiamo passare questo valore in seconda classe usando il passaggio seguente

1> Dobbiamo creare un oggetto stringa nel file SecondViewController.h

NSString *strValue;

2> È necessario dichiarare la proprietà come di seguito sotto la dichiarazione nel file .h

@property (strong, nonatomic)  NSString *strSecondValue;

3> È necessario sintetizzare quel valore nel file FirstViewController.m sotto la dichiarazione di intestazione

@synthesize strValue;

e in FirstViewController.h:

@property (strong, nonatomic)  NSString *strValue;

4> In FirstViewController, da quale metodo passiamo alla seconda vista, si prega di scrivere sotto il codice in quel metodo.

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

[secondView setStrSecondValue:StrFirstValue];

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

Dopo essere stato nel SecondViewController, come si passano i dati al FirstViewController?
bruno,

18

Attualmente sto contribuendo a una soluzione open source a questo problema attraverso un progetto chiamato MCViewFactory, che può essere trovato qui:

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

L'idea è di imitare il paradigma delle intenzioni di Android, usando una fabbrica globale per gestire la vista che stai guardando e usando "intenti" per cambiare e passare i dati tra le viste. Tutta la documentazione è sulla pagina di github, ma qui ci sono alcuni punti salienti:

Puoi impostare tutte le tue visualizzazioni in file .XIB e registrarle nel delegato dell'app, inizializzando la fabbrica.

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

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

Ora, nel tuo VC, ogni volta che vuoi passare a un nuovo VC e passare dati, crei un nuovo intento e aggiungi dati al suo dizionario (saveInstanceState). Quindi, basta impostare l'intento corrente di fabbrica:

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

Tutte le tue viste conformi a questo devono essere sottoclassi di MCViewController, che ti consentono di sovrascrivere il nuovo metodo onResume: che ti consente di accedere ai dati che hai passato.

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

Spero che alcuni di voi trovino questa soluzione utile / interessante.


Quindi tutti gli oggetti controller potrebbero ottenere / impostare tutti i dizionari registrati in qualsiasi ambito? Sottoscrivi questo.
Itachi,

15

Crea la proprietà al prossimo view controller .h e definire getter e setter.

Aggiungi questo propertyin NextVC.h su nextVC

@property (strong, nonatomic) NSString *indexNumber;

Inserisci

@synthesize indexNumber; in NextVC.m

E ultimo

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

vc.indexNumber=@"123";

[self.navigationController vc animated:YES];

11

Ci sono molti modi per farlo ed è importante scegliere quello giusto. Probabilmente una delle più grandi decisioni architettoniche risiede nel modo in cui il codice del modello verrà condiviso o accessibile in tutta l'app.

Qualche tempo fa ho scritto un post sul blog su questo: Sharing Model Code . Ecco un breve riassunto:

Dati condivisi

Un approccio consiste nel condividere i puntatori agli oggetti del modello tra i controller di vista.

  • Iterazione della forza bruta sui controller di visualizzazione (in Navigatore o Controller barra delle schede) per impostare i dati
  • Imposta i dati in preparForSegue (se storyboard) o init (se programmatico)

Poiché preparare per segue è il più comune ecco un esempio:

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

Accesso indipendente

Un altro approccio è quello di gestire uno schermo pieno di dati alla volta e invece di accoppiare i controller di vista tra loro, accoppiare ciascun controller di vista a una singola fonte di dati a cui possono accedere in modo indipendente.

Il modo più comune in cui l'ho visto è un'istanza singleton . Quindi, se il tuo oggetto singleton fosse, DataAccesspotresti fare quanto segue nel metodo viewDidLoad di UIViewController:

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

Esistono strumenti di aggiunta che aiutano anche a trasmettere i dati:

  • Osservazione di valori-chiave
  • NSNotification
  • Dati di base
  • NSFetchedResultsController
  • Fonte di dati

Dati di base

La cosa bella di Core Data è che ha relazioni inverse. Quindi, se vuoi semplicemente dare a NotesViewController l'oggetto note, puoi farlo perché avrà una relazione inversa con qualcos'altro come il notebook. Se sono necessari dati sul notebook in NotesViewController, è possibile risalire al grafico degli oggetti nel modo seguente:

let notebookName = note.notebook.name

Maggiori informazioni su questo nel mio post sul blog: Sharing Model Code


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

La delega è l'unica soluzione per eseguire tali operazioni quando si utilizzano file .xib, tuttavia tutte le risposte sopra descritte sono per i storyboardfile .xibs che è necessario utilizzare la delega. questa è l'unica soluzione che puoi.

Un'altra soluzione è utilizzare il modello di classe singleton per inizializzarlo una volta e utilizzarlo nell'intera app.


10

se si desidera passare i dati da ViewControlerOne a ViewController, provare due ..

eseguire queste operazioni in ViewControlerOne.h

 @property (nonatomic, strong) NSString *str1;

eseguire queste operazioni in ViewControllerTwo.h

 @property (nonatomic, strong) NSString *str2;

Sintetizza str2 in ViewControllerTwo.m

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

eseguire queste operazioni in ViewControlerOne.m

 - (void)viewDidLoad
 {
   [super viewDidLoad];

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

 }

sui pulsanti fare clic su evento fare questo ..

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

eseguire queste operazioni in ViewControllerTwo.m

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

10

Puoi salvare i dati nel delegato dell'app per accedervi attraverso i controller di visualizzazione nella tua applicazione. Tutto quello che devi fare è creare un'istanza condivisa del delegato dell'app

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

Per esempio

se dichiari un NSArray object *arrayXYZ, puoi accedervi da qualsiasi controller di visualizzazione daappDelegate.arrayXYZ


È il metodo scelto per l'hackathon
Hai Feng Kao,

9

Se si desidera inviare dati da uno all'altro ViewController, ecco un modo per farlo:

Supponiamo di avere viewController: ViewController e NewViewController.

in 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

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

}

In NewViewController.h

#import <UIKit/UIKit.h>

@interface NewViewController : UITableViewController
{
    NSArray *arrayList;

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

}

@property(nonatomic, retain)NSArray *arrayList;

@end

In 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

Quindi in questo modo possiamo passare i dati da un viewcontroller a un altro controller di visualizzazione ...


8

Mi piace l'idea di oggetti Model e oggetti Mock basati su NSProxy per eseguire il commit o l'eliminazione di dati se ciò che l'utente seleziona può essere annullato.

È facile trasferire dati dal momento che è un singolo oggetto o un paio di oggetti e, se si dice che il controller UINavigationController, è possibile mantenere il riferimento al modello all'interno e tutti i controller di visualizzazione push possono accedervi direttamente dal controller di navigazione.


8

Ho visto molte persone complicare questo usando il didSelectRowAtPathmetodo. Sto usando Core Data nel mio esempio.

- (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 righe di codice all'interno del metodo e il gioco è fatto.


6

Ci sono molte risposte a queste domande che offrono molti modi diversi per eseguire le comunicazioni del controller di visualizzazione che funzionerebbero davvero, ma non vedo da nessuna parte menzionato quale sia effettivamente migliore da usare e quali evitare.

In pratica, a mio avviso, sono consigliate solo alcune soluzioni:

  • Per inoltrare i dati:
    • sovrascrive il prepare(for:sender:)metodo di UIViewControllerquando si utilizza uno storyboard e segue
    • passare i dati attraverso un inizializzatore o attraverso le proprietà quando si esegue la transizione del controller di visualizzazione attraverso il codice
  • Per passare i dati all'indietro
    • aggiorna lo stato condiviso dell'app (che puoi passare tra i controller di visualizzazione con uno dei metodi sopra)
    • usare la delega
    • usa uno svolgersi segue

Soluzioni che consiglio di NON utilizzare:

  • Fare riferimento direttamente al controller precedente anziché utilizzare la delega
  • Condivisione dei dati tramite un singleton
  • Passando i dati attraverso il delegato dell'app
  • Condivisione dei dati tramite le impostazioni predefinite dell'utente
  • Passando i dati attraverso le notifiche

Queste soluzioni, sebbene funzionino a breve termine, introducono troppe dipendenze che danneggeranno l'architettura dell'app e creeranno più problemi in seguito.

Per chi fosse interessato, ho scritto alcuni articoli che affrontano questi punti in modo più approfondito ed evidenziano i vari inconvenienti:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.