ios - mvc - uiviewcontroller add child view controller
Passage de données entre les contrôleurs de vue (20)
Rapide
Il y a des tonnes et des tonnes d'explications ici et autour de StackOverflow, mais si vous êtes un débutant essayant juste de faire quelque chose de basique, essayez de regarder ce tutoriel sur YouTube (c'est ce qui m'a aidé à comprendre).
- Tutoriel YouTube: Comment envoyer des données via segue (swift)
Passage des données au prochain View Controller
Ce qui suit est un exemple basé sur la vidéo. L'idée est de passer une chaîne du champ de texte du First View Controller à l'étiquette du second View View.
Créez la disposition du storyboard dans l'interface Builder. Pour faire le segue, il suffit de cliquer sur le bouton Control et de glisser vers le second View View.
Premier contrôleur de vue
Le code du First View Controller est
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!
}
}
Deuxième contrôleur de vue
Et le code pour le second View View est
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
}
}
Ne pas oublier
-
UITextField
les prises pourUITextField
etUILabel
. - Définissez les premier et deuxième contrôleurs de vue sur les fichiers Swift appropriés dans IB.
Renvoyer les données au contrôleur de vue précédent
Pour renvoyer des données du second contrôleur de vue au premier contrôleur de vue, vous utilisez un protocole et un délégué . Cette vidéo est une marche très claire cependant de ce processus:
- Didacticiel YouTube: Didacticiel de base sur Swift iOS: Protocoles et délégués Mais lisez également cet article pour vous assurer de ne pas entrer dans un cycle de référence fort.
Ce qui suit est un exemple basé sur la vidéo (avec quelques modifications).
Créez la disposition du storyboard dans l'interface Builder. Encore une fois, pour faire le segue, il vous suffit de faire glisser le bouton depuis le bouton jusqu'au second contrôleur de vue. Définissez l'identificateur de segue sur showSecondViewController
. Aussi, n'oubliez pas de brancher les prises et les actions en utilisant les noms dans le code suivant.
Premier contrôleur de vue
Le code du First View Controller est
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
}
}
Notez l'utilisation de notre protocole DataEnteredDelegate
personnalisé.
Deuxième contrôleur de vue et protocole
Le code du second contrôleur de vue est
import UIKit
// protocol used for sending data back
protocol DataEnteredDelegate: class {
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)
}
}
Notez que le protocol
est en dehors de la classe View Controller.
C'est tout. En cours d'exécution de l'application, vous devriez être en mesure de renvoyer les données du second contrôleur de vue au premier.
Je suis nouveau sur iOS et Objective-C et tout le paradigme MVC et je suis coincé avec ce qui suit:
J'ai une vue qui agit comme un formulaire de saisie de données et je veux donner à l'utilisateur la possibilité de sélectionner plusieurs produits. Les produits sont répertoriés dans une autre vue avec un UITableViewController
et j'ai activé plusieurs sélections.
Ma question est, comment puis-je transférer les données d'une vue à l'autre? Je vais conserver les sélections sur UITableView
dans un tableau, mais comment puis-je le renvoyer à la vue précédente du formulaire d'entrée de données afin qu'il puisse être sauvegardé avec les autres données dans Core Data à la soumission du formulaire?
J'ai surfé autour et j'ai vu certaines personnes déclarer un tableau dans le délégué de l'application. J'ai lu quelque chose à propos de Singletons mais je ne comprends pas ce que c'est et j'ai lu quelque chose sur la création d'un modèle de données.
Quelle serait la bonne façon de faire cela et comment j'y arriverais?
Après plus de recherches, il a semblé que les Protocoles et les Délégués étaient la bonne façon de faire cela.
J'ai fini par utiliser cet exemple
Partage de données entre les contrôleurs de vue et d'autres objets @ iPhone SDK
Travaillé bien et m'a permis de passer une chaîne et un tableau en avant et en arrière entre mes vues.
Merci pour votre aide
Il existe plusieurs façons par lesquelles une donnée peut être reçue dans une classe différente dans iOS. Par exemple -
- Initialisation directe après l'attribution d'une autre classe.
- Délégation - pour renvoyer des données
- Notification - pour diffuser des données à plusieurs classes en une seule fois
- Sauvegarde dans
NSUserDefaults
- pour y accéder plus tard - Cours Singleton
- Bases de données et autres mécanismes de stockage comme plist, etc.
Mais pour le scénario simple de transmission d'une valeur à une classe différente dont l'allocation est effectuée dans la classe en cours, la méthode la plus courante et préférée serait la définition directe des valeurs après allocation. Cela se fait comme suit:-
Nous pouvons le comprendre en utilisant deux contrôleurs - Controller1 et Controller2
Supposons que dans la classe Controller1 vous voulez créer l'objet Controller2 et le pousser avec une valeur String en cours de transmission. Cela peut être fait comme ceci: -
- (void)pushToController2 {
Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passValue:@"String"];
[self pushViewController:obj animated:YES];
}
Dans l'implémentation de la classe Controller2, il y aura cette fonction
@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
Vous pouvez également définir directement les propriétés de la classe Controller2 de la même manière:
- (void)pushToController2 {
Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj setStringPassed:@"String"];
[self pushViewController:obj animated:YES];
}
Pour passer plusieurs valeurs, vous pouvez utiliser les paramètres multiples comme:
Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passValue:@“String1” andValues:objArray withDate:date];
Ou si vous devez transmettre plus de 3 paramètres liés à une fonctionnalité commune, vous pouvez stocker les valeurs dans une classe Model et transmettre ce modelObject à la classe suivante
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];
Donc, si vous voulez -
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.
J'espère que cela t'aides
Il existe plusieurs méthodes pour partager des données.
Vous pouvez toujours partager des données à l'aide de
NSUserDefaults
. Définissez la valeur que vous souhaitez partager par rapport à une clé de votre choix et obtenez la valeur deNSUserDefault
associée à cette clé dans le contrôleur de vue suivant.[[NSUserDefaults standardUserDefaults] setValue:value forKey:key] [[NSUserDefaults standardUserDefaults] objectForKey:key]
Vous pouvez simplement créer une propriété dans
viewcontrollerA
. Créez un objet deviewcontrollerA
dansviewcontrollerB
et affectez la valeur désirée à cette propriété.Vous pouvez également créer des délégués personnalisés pour cela.
Je trouve la version la plus simple et la plus élégante avec des blocs qui passent. Nommez le contrôleur de vue qui attend les données renvoyées comme "A" et renvoyez le contrôleur de vue comme "B". Dans cet exemple, nous voulons obtenir 2 valeurs: d'abord Type1 et seconde de Type2.
En supposant que nous utilisions Storyboard, le premier contrôleur règle le bloc de rappel, par exemple pendant la préparation du 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);
};
}
}
et le contrôleur de vue "B" doit déclarer la propriété de rappel, BViewController.h:
// it is important to use "copy"
@property (copy) void(^callback)(Type1 *value1, Type2 *value2);
Que dans le fichier d'implémentation BViewController.m après que nous ayons les valeurs désirées pour retourner notre callback devrait être appelé:
if (self.callback)
self.callback(value1, value2);
Une chose à retenir est que l'utilisation du bloc doit souvent gérer des références fortes et faibles comme expliqué here
Le M dans MVC est pour "Modèle" et dans le paradigme MVC le rôle des classes de modèles est de gérer les données d'un programme. Un modèle est l'opposé d'une vue - une vue sait comment afficher des données, mais elle ne sait rien sur les données, alors qu'un modèle sait tout sur la façon de travailler avec les données, mais rien sur la façon de les afficher. Les modèles peuvent être compliqués, mais ils ne doivent pas l'être - le modèle de votre application peut être aussi simple qu'un tableau de chaînes ou de dictionnaires.
Le rôle d'un contrôleur est de servir de médiateur entre la vue et le modèle. Par conséquent, ils ont besoin d'une référence à un ou plusieurs objets de vue et un ou plusieurs objets de modèle. Disons que votre modèle est un tableau de dictionnaires, chaque dictionnaire représentant une ligne dans votre tableau. La vue racine de votre application affiche cette table et elle peut être chargée de charger le tableau à partir d'un fichier. Lorsque l'utilisateur décide d'ajouter une nouvelle ligne à la table, il appuie sur un bouton et votre contrôleur crée un nouveau dictionnaire (modifiable) et l'ajoute au tableau. Afin de remplir la ligne, le contrôleur crée un contrôleur de vue de détail et lui donne le nouveau dictionnaire. Le contrôleur de vue détaillée remplit le dictionnaire et revient. Le dictionnaire fait déjà partie du modèle, donc rien d'autre ne doit arriver.
1. Create the instance of first View Controller in the second View Controller and make its property @property (nonatomic,assign)
.
2. Assign the SecondviewController
instance of this view controller.
2. When you finish the selection operation copy the array to first View Controller,When u unload the SecondView ,FirstView will hold the Array Data.
Hope This Helps.
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;
Create the property on next view controller .h
and define getter and setter.
Add this property
in NextVC.h on nextVC
@property (strong, nonatomic) NSString *indexNumber;
Ajouter
@synthesize indexNumber;
in NextVC.m
And last
NextVC *vc=[[NextVC alloc]init];
[email protected]"123";
[self.navigationController vc animated:YES];
Delegation is the only one solution to perform such operations when you are using .xib files however all answers described above are for storyboard
for .xibs files you need to use delegation. that's only solution you can.
Another solution is use singleton class pattern initialize it once and use it in your entire app.
I have seen a lot of people over complicating this using the didSelectRowAtPath
method. I am using Core Data in my example.
- (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 lines of code inside the method and you are done.
I know this is a beaten subject but for those looking to answer this question with a SWIFT slant and want a bare-bones example, here my go-to method for passing data if you are using a segue to get around.
It is similar to the above but without the buttons, labels and such. Just simply passing data from one view to the next.
Setup The Storyboard
There are three parts.
- The Sender
- The Segue
- The Receiver
This is a very simple view layout with a segue between them.
Here is the setup for the sender
Here is the setup for the receiver.
Lastly, the setup for the segue.
The View Controllers
We are keeping this simple so no buttons, not actions, we are simply moving data from the sender to the receiver when the application loads and then outputting the transmitted value to the console.
This page takes the initially loaded value and passes it along.
//
// ViewControllerSender.swift
// PassDataBetweenViews
//
// Created by Chris Cantley on 8/25/15.
// Copyright (c) 2015 Chris Cantley. All rights reserved.
//
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
}
}
This page just sends the value of the variable to the console when it loads. By this point our favorite movie should be in that variable.
//
// ViewControllerReceiver.swift
// PassDataBetweenViews
//
// Created by Chris Cantley on 8/25/15.
// Copyright (c) 2015 Chris Cantley. All rights reserved.
//
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)")
}
}
That is how you can tackle it if you want to use a segue and you don't have your pages under a navigation controller.
Once it is run it should switch to the receiver view automatically and pass the value from the sender to the receiver, displaying the value in the console.
I was searching this solution for long time, Atlast I found it. First of all declare all the objects in your SecondViewController.h file like
@interface SecondViewController: UIviewController
{
NSMutableArray *myAray;
CustomObject *object;
}
Now in your implementation file allocate the memory for those objects like this
- (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;
}
Now you have allocated the memory for Array
and object. now you can fill that memory before pushing this ViewController
Go to your SecondViewController.h and write two methods
-(void)setMyArray:(NSArray *)_myArray;
-(void)setMyObject:(CustomObject *)_myObject;
in implementation file you can implement the function
-(void)setMyArray:(NSArray *)_myArray
{
[myArra addObjectsFromArray:_myArray];
}
-(void)setMyObject:(CustomObject *)_myObject
{
[object setCustomObject:_myObject];
}
expecting that your CustomObject
must have a setter function with it.
now your basic work is done. go to the place where you want to push the SecondViewController
and do the following stuff
SecondViewController *secondView= [[SecondViewController alloc] initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]] ;
[secondView setMyArray:ArrayToPass];
[secondView setMyObject:objectToPass];
[self.navigationController pushViewController:secondView animated:YES ];
Take care for spelling mistakes.
If you want to pass data from one controller to other try this code
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];
}
If you want to send data from one to another viewController, here's a way to it:
Say we have viewControllers: viewControllerA and viewControllerB
Now 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];
}
So this is how you can pass data from viewControllerA to viewControllerB without setting any delegate. ;)
In my case I used a singleton class which can work as a global object allowing accesses to the data from almost everywhere in the app. First thing is to build a singleton class. Please refer to the page," What should my Objective-C singleton look like? " And what I did to make the object globally accessible was simply import it in appName_Prefix.pch
which is for applying import statement in every classes. To access this object and to use, I simply implemented class method to return the shared instance, which contains its own variables
There are many answers to this questions offering many different ways to perform view controller communication that would indeed work, but I don't see anywhere mentioned which one are actually best to use and which ones to avoid.
In practice, in my opinion only a few solutions are recommended:
- To pass data forward:
- override the
prepare(for:sender:)
method ofUIViewController
when using a storyboard and segues - pass data through an initializer or through properties when performing view controller transitions thtough code
- override the
- To pass data backwards
- update the app shared state (which you can pass forward between view controllers with either one of the methods above)
- use delegation
- use an unwind segue
Solutions I recommend NOT to use:
- Referencing the previous controller directly instead of using delegation
- Sharing data through a singleton
- Passing data through the app delegate
- Sharing data through the user defaults
- Passing data through notifications
These solutions, although working in the short term, introduce too many dependencies that will garble the architecture of the app and create more problems later.
For those interested, I wrote some articles that address these points more in depth and highlight the various drawbacks:
There are tons of ways to do this and it's important to pick the right one. Probably one of the biggest architectural decisions lies on how the model code will be shared or accessed throughout the app.
I wrote a blog post about this a while back: Sharing Model Code . Here's a brief summary:
Shared data
One approach is to share pointers to the model objects between view controllers.
- Brute force iteration on view controllers (in Navigation or Tab Bar Controller) to set the data
- Set data in prepareForSegue (if storyboards) or init (if programmatic)
Since prepare for segue is the most common here is an example:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var next = segue.destinationViewController as NextViewController
next.dataSource = dataSource
}
Independent access
Another approach is to handle a screen full of data at a time and instead of coupling the view controllers to each other couple each view controller to single data source that they can get to independently.
The most common way I've seen this done is a singleton instance. So if your singleton object was DataAccess
you could do the following in the viewDidLoad method of UIViewController:
func viewDidLoad() {
super.viewDidLoad()
var data = dataAccess.requestData()
}
There are addition tools that also help pass along data:
- Key-Value Observing
- NSNotification
- Core Data
- NSFetchedResultsController
- La source de données
Core Data
The nice thing about Core Data is that it has inverse relationships. So if you want to just give a NotesViewController the notes object you can because it'll have an inverse relationship to something else like the notebook. If you need data on the notebook in the NotesViewController you can walk back up the object graph by doing the following:
let notebookName = note.notebook.name
Read more about this in my blog post: Sharing Model Code
This is not the way to do it, you should use delegates, I'll assume we have two view controllers ViewController1 and ViewController2 and this check thing is in the first one and when its state changes, you want to do something in ViewController2, to achieve that in the proper way, you should do the below:
Add a new file to your project (Objective-C Protocol) File -> New, now name it ViewController1Delegate or whatever you want and write these between the @interface and @end directives
@optional
- (void)checkStateDidChange:(BOOL)checked;
Now go to ViewController2.h and add
#import "ViewController1Delegate.h"
then change its definition to
@interface ViewController2: UIViewController<ViewController1Delegate>
Now go to ViewController2.m and inside the implementation add:
- (void)checkStateDidChange:(BOOL)checked {
if (checked) {
// Do whatever you want here
NSLog(@"Checked");
}
else {
// Also do whatever you want here
NSLog(@"Not checked");
}
}
Now go to ViewController1.h and add the following property:
@property (weak, nonatomic) id<ViewController1Delegate> delegate;
Now if you are creating ViewController1 inside ViewController2 after some event, then you should do it this way using NIB files:
ViewController1* controller = [[NSBundle mainBundle] loadNibNamed:@"ViewController1" owner:self options:nil][0];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];
Now you are all set, whenever you detect the event of check changed in ViewController1, all you have to do is the below
[delegate checkStateDidChange:checked]; // You pass here YES or NO based on the check state of your control
Please tell me if there's anything that's not clear of if I didn't understand your question properly.
You can save data in App delegate to access it across view controllers in your application. All you have to do is create a shared instance of app delegate
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
For Example
if you declare a NSArray object *arrayXYZ
then you can access it in any view controller by appDelegate.arrayXYZ