i need to pass some data to previous view controller, what is problem with my codes? in this code "contactViewController" is my first view controller and "groupViewController" is my second view controller
ContactEditVC.h (firstViewController)
#import "GroupEditTVC.h"
#interface ContactEditVC : UIViewController <SecondViewControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate> {
UIImageView * imageView;
UIButton * choosePhotoBtn;
UIButton * takePhotoBtn;
UIButton * btnGroup;
}
#property (nonatomic, strong) NSString *groupName;
ContactEditVC.m (firstViewController)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([[segue identifier] isEqualToString:#"selectGroup"]){
//get selected contact
//pass selected contact to MyContactAppViewController for editing
GroupEditTVC *destViewcontroller=segue.destinationViewController;
destViewcontroller.delegate=self;
}
}
-(void)viewWillAppear:(BOOL)animated
{
self.txtFname.text=groupName;
}
- (void)dataFromController:(NSString *)data
{
groupName=data;
}
And in my second vie controller:
#protocol SecondViewControllerDelegate <NSObject>
- (void)dataFromController:(NSString *)data;
#end
#interface GroupEditTVC : UITableViewController <UIAlertViewDelegate>
#property (retain) id <SecondViewControllerDelegate> delegate;
#end
GroupEditTVC.m (secendViewController)
#import "ContactEditVC.h"
#interface GroupEditTVC ()
#end
#synthesize delegate;
- (IBAction)donePressed:(id)sender {
[[self delegate]dataFromController:#"blabla"];
[self dismissViewControllerAnimated:YES completion:nil];
}
Is there any another way to pass back data?
You should try to follow Model View Controller. In my applications I usually pass around a reference to a Model class that holds a lot of data that I want to share and change. If you pass the model reference to the GroupEditTVC and you change the data in that screen, that changed data is available to the "root" view controller that also has a reference to the same data. The only thing that is needed is that that "root" view refreshes it's views with the new data.
So you don't need to pass anything back, you just update the Model class that the edit view and the main view both have a reference to.
Example
The idea is to get around passing strings and other "loosely coupled" data around and just act on one solid data object that holds all related data. This is also easier when you start working with AFNetworking / Mantle / etcetera. You just save the whole class of data and you don't have to worry about each little piece of data.
So all references to loose strings should be replaced to strings inside this object.
-(void)viewWillAppear:(BOOL)animated {
self.txtFname.text = self.contact.name;
}
Data class
// class that holds the data
#interface Contact : NSObject // Nothing special, just an object
#property NSInteger contactId;
#property NSString *name;
// You can add basically any data here
#end
Segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([[segue identifier] isEqualToString:#"selectGroup"]){
//pass selected contact to MyContactAppViewController for editing
GroupEditTVC *destViewcontroller=segue.destinationViewController;
destViewcontroller.contact = self.contact; // Adds reference to the same class
}
}
Done editing
- (IBAction)donePressed:(id)sender {
self.contact.name = self.name.text; // get new name from text box
[self dismissViewControllerAnimated:YES completion:nil];
}
Related
I have four viewControllers in my current design and I am designing an app to sell a product.
FirstViewController gets the product image and when user clicks to the next button then it takes user to the secondviewcontroller where user describes the product and then user clicks next button which takes user to the thirdViewcontroller where price and condition are entered. In the lastviewcontolller there is a post button to send the product info to the server. I am using POST method.
The following segue approach does not fit into what I want, because it sends the firstviewcontroller object (product image) to the secondviewcontoller, and then secondviewcontroller also should forward the product image to the thirdviewcontoller and so on. I do not think it is a feasible way of doing it.
I wonder what is the best way of collection information from the first page till to the last page and send it. What is best way of handling that issue? I am using segue between the viewcontrollers.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"isSecond"])
{
// Get reference to the destination view controller
SecondViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setMyProductImage:productImage];
}
}
Please don't use a singleton, even if the majority of users here tells you so. It would violate the SOLID-Principles for several reasons.
Instead just pass the object from ViewController to ViewController.
If all ViewController expect the same model class, you can create a common base class that has the property for the model.
it could have this method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewControler isKindOfClass:[ProductAwareBaseViewController class]])
{
ProductAwareBaseViewController *vc = (ProductAwareBaseViewController *)segue.destinationViewControler;
vc.product = self.product;
}
}
I created an example project: https://github.com/vikingosegundo/ProductWizard
Note, that all view controller derive from ProductAwareBaseViewController
#import UIKit;
#class Product;
#interface ProductAwareBaseViewController : UIViewController
#property (nonatomic, strong) Product *product;
#end
#import "ProductAwareBaseViewController.h"
#import "Product.h"
#interface ProductAwareBaseViewController ()
#end
#implementation ProductAwareBaseViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController isKindOfClass:[ProductAwareBaseViewController class]]) {
ProductAwareBaseViewController *vc = (ProductAwareBaseViewController *)segue.destinationViewController;
vc.product = self.product;
}
}
#end
This ViewController knows how to pass the model data of class Product to other instances of ProductAwareBaseViewController and subclasses of it.
All other view controller don't deal with passing the data, just adding each portion of data (name, description, price) to the model and displaying it.
i.e:
#import "EditNameProductViewController.h"
#import "Product.h"
#interface EditNameProductViewController ()
#property (weak, nonatomic) IBOutlet UITextField *nameField;
#end
#implementation EditNameProductViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.product = [[Product alloc] init];
}
- (IBAction)continueTapped:(id)sender {
self.product.productName = self.nameField.text;
}
#end
#import "EditDescriptionProductViewController.h"
#import "Product.h"
#interface EditDescriptionProductViewController ()
#property (weak, nonatomic) IBOutlet UITextField *descriptionField;
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#end
#implementation EditDescriptionProductViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.nameLabel.text = self.product.productName;
}
- (IBAction)continueTapped:(id)sender {
self.product.productDescription = self.descriptionField.text;
}
#end
Create an object to act as your application's data model. It can be a singleton or it can be a normal object that's available from a known location...such as owned by the app delegate.
Update your model when you have new information and read from the model when you need to display something. Using prepareForSegue: and linking controllers may be acceptable for simple things but it really doesn't scale well.
One way of doing this would be that you create a mutable dictionary (or a custom object with variables) in the first view controller. Then you would pass a weak reference to second/third/fourth view controllers of the mutable dictionary/object from first view controller. Each view controller would be able to set data to the dictionary/object and the last one would be able to process the information.
Another way would be to create a simple singleton class with variables that you want to store. The first view controller would reset the singleton variables. Then let each view controller access the singleton and store their values there, last view controller would process values from singleton.
It depends how many data you are collecting and what you personally prefer.
if I fill state,province and town in first view controller, how to get just town or town and state in another view controller, when I press OK button.
http://i.stack.imgur.com/jhwGq.png
First of all, you should create a property in the SecondVC.h to receive de text and a property linked to the label in screen:
#property(nonatomic, copy) NSString *townText;
#property (weak, nonatomic) IBOutlet UILabel *townLabel;
After that, in the SecondVC.m, overwrite the "viewWillAppear" method as follows:
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
self.townLabel.text = self.townText;
}
Then, in the FirstVC.m, overwrite the "prepareForSegue" method:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
SecondVC *secondVC = segue.destinationViewController;
secondVC.townText = self.townTextView.text; // townTextView is the TextView in screen where user writes the town name.
}
Don't forget to import the SecondVC.h in the FirstVC.m
Easy way:
Use storyboard's IBOutlet feature. One view controller class can hold a reference to another view controller
If the code just reference the other view controller
-(void)clickOK:(UIButton*)
{
NSString* town = self.anotherController;
//do something with town
}
Suggested way:
Follow the model-view-controller design pattern. Treat first view controller as a data provider
Protocal DataProviderDelegate
{
#required
-(NSString*)getTown;
-(NSString*)getState;
}
#implementation
FirstViewController:UIViewController<DataProviderDelegate>
{
}
#declaration
SecondViewController:UIViewController
#property(nonatomic, weak)DataProviderDelegate* dataProvider;
In the segue, assign first view controller to the second view controller as a delegate
So I have 2 different table views that use the same array (the array is originally created in the Role table view, the below one). How can I connect those two?
(Usually I use prepareForSegue to pass the data but since there is no segue, I'm not sure how can I do this)
EDIT 1: Add the location of the array.
What is a Model and why you need it
In most of the cases it's useless to pass data around if you don't have a Data Model. You can store your data using a technique called Data Persistence.
An example of a pattern you could use is MVC.
MVC or model-view controlelr is an software pattern widely using when making iOS Apps. In this architectural pattern your Controllers are a bridge between your View and your Model.
In this specific scenario both UITableViewControllers would use the same Model but they would display this data differently.
Persisting your Model
There are several ways to do that, the way I like the most is a little framework called CoreData, you can see this question for some reference on that.
You can also refer to this question to see the use of Singletons. But keep in mind that singletons alone do not persist the data. You'll have to add some sort of mechanism if you want the data to remain there between app sessions.
Persisting user preferences
The simplest way to store small chunks of data is using NSUserDefaults (but it's only meant to store defaults):
Let's assume you have an array
NSArray* testArray = #[#"first", #"second", #"third"];
You can set it to a key by using
[[NSUserDefaults standardUserDefaults] setObject:testArray forKey:#"myArray"];
You can sync NSUserDefaults using
[[NSUserDefaults standardUserDefaults] synchronize];
Then, anywhere in your app you can read it doing
[[NSUserDefaults standardUserDefaults] objectForKey:#"myArray"]
Passing data through the app
On the other hand you have to pass your data around somehow. To do so you can use formal protocols, specifically delegates.
As per the Apple documentation:
In a delegate-based model, the view controller defines a protocol for
its delegate to implement. The protocol defines methods that are
called by the view controller in response to specific actions, such as
taps in a Done button. The delegate is then responsible for
implementing these methods. For example, when a presented view
controller finishes its task, it sends a message to the presenting
view controller and that controller dismisses it.
Using delegation to manage interactions with other app objects has key
advantages over other techniques:
The delegate object has the opportunity to validate or incorporate
changes from the view controller.
The use of a delegate promotes
better encapsulation because the view controller does not have to know
anything about the class of the delegate. This enables you to reuse
that view controller in other parts of your app.
For more information on passing data through view controllers (the main point of this question) take a look at this SO answer.
You should never use data persistence just to pass data through the app. Neither user defaults nor core data.
Also using singletons is not good choice. All will mess up your memory.
Instead use call backs — either as delegates or blocks.
Or use unwind segues.
I explain delegates and unwind segues here: Passing row selection between view controllers
this example passes index paths, as it is appropriate in that situation, but the passed object might be of any type or size, as only pointers are passes.
if you use the NSUserDefaults on the other side, data is copied and written to the disk — there for data is copied and slowly processed — without any use.
I created a sample app how to pass data from one view controller to another view controller in another tab bar branch.
click to enlarge
TabBarController
We need to intercept the section of view controllers to set up some callback mechanism. In this case I am using blocks, but delegate would work as-well.
UITabController has a purely optional delegate. I create a subclass of UITabBarController to serv as it's own delegate, but actually a separate delegate should work in the same way.
#import "GameTabBarController.h"
#import "RoleViewController.h"
#interface GameTabBarController () <UITabBarControllerDelegate>
#end
#implementation GameTabBarController
-(void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navController = (UINavigationController *)viewController;
if ([navController.topViewController isKindOfClass:[RoleViewController class]]) {
RoleViewController *rvc = (RoleViewController *)[navController topViewController];
[rvc setSelectedRole:^(Role *role) {
UIViewController *viewController = self.viewControllers[0];
[viewController setValue:role forKey:#"role"];
[self setSelectedIndex:0];
}];
}
}
return YES;
}
#end
I set the initial tab bar controller to this sub class
Role, RoleDatasource and RoleViewController
The RoleViewController displays a list of Roles, but the datasource and delegate for it's table view are a separate class that I add to the role view controller scene in the storyboard, where i also were it up.
Role
#interface Role : NSObject
#property (nonatomic,copy, readonly) NSString *name;
-(instancetype)initWithName:(NSString *)name;
#end
#import "Role.h"
#interface Role ()
#property (nonatomic,copy) NSString *name;
#end
#implementation Role
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
_name = name;
}
return self;
}
#end
RoleDatasource
#import <UIKit/UIKit.h>
#class Role;
#interface RoleDatasource : NSObject <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, copy) void(^roleSelector)(Role *role);
#end
#import "RoleDatasource.h"
#import "Role.h"
#interface RoleDatasource ()
#property (nonatomic,strong) NSArray *roles;
#end
#implementation RoleDatasource
- (instancetype)init
{
self = [super init];
if (self) {
_roles = #[[[Role alloc] initWithName:#"Magician"], [[Role alloc] initWithName:#"Soldier"], [[Role alloc] initWithName:#"Maid"]];
}
return self;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.roles.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"RoleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.textLabel.text = [self.roles[indexPath.row] name];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.roleSelector(self.roles[indexPath.row]);
}
#end
RoleViewController
#import <UIKit/UIKit.h>
#class Role;
#interface RoleViewController : UIViewController
#property (nonatomic, copy) void(^selectedRole)(Role *role);
#end
#import "RoleViewController.h"
#import "RoleDatasource.h"
#interface RoleViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation RoleViewController
- (void)viewDidLoad {
[super viewDidLoad];
RoleDatasource *roleDataSource = (RoleDatasource *)[self.tableView dataSource];
[roleDataSource setRoleSelector:^(Role *role) {
self.selectedRole(role);
}];
}
#end
PlayViewController
As soon as a role is selected on the role view controller we want to tell our tab bar controller to switch to the game view controller and show the selected role there, see the code for the tab bar controller.
The GameViewController is just a simple view controller subclass that has a property to hold a role and if a role is set, it will displays it name.
#import <UIKit/UIKit.h>
#class Role;
#interface PlayViewController : UIViewController
#property (nonatomic, strong) Role *role;
#end
#import "PlayViewController.h"
#import "Role.h"
#interface PlayViewController ()
#property (weak, nonatomic) IBOutlet UILabel *roleNameLabel;
#end
#implementation PlayViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.roleNameLabel.text = (self.role) ? self.role.name : self.roleNameLabel.text;
}
#end
You'll find an example on github.
I think that I should put the array in the Tab bar Controller and connect it to the Role Table view (in order to maintain the behaviour like it is before) and connect it to my new Table view to do what I want to do.
The only problem I can think of is that since my program is small, adding this will not be a big problem. But if I have more vc, it's going to be so much pain.
I have:
UITableViewControllerA (Lists different ways to refine database product results)
UIViewControllerB (Controller associated with one of the rows from list above)
BaseController (Subclass of UIViewController, contains common code for controllers associated with rows in UITableViewControllerA and is their super class)
I can pass data to UIViewControllerB from UITableViewControllerA easily using the perpareForSegue method. I am now trying to pass data from UIViewControllerB to UITableViewControllerA when popViewControllerAnimated is called in my UIButton custom method that a user taps once they have made a selection of what to refine database product results with.
I decided to use delegation. UIViewControllerB which is subclass of a BaseController and in UITableViewControllerA I mentioned above that I have a list of ways to refine products:
Gender - Size - Colour - Price - Type
Rather than define my protocol below in each of their controllers I decided to define it in the BaseController like so:
#import <UIKit/UIKit.h>
#protocol BaseViewControllerDelegate <NSObject>
- (void)didTapDoneButtonWithSelection:(NSString *)selection; // use string for now to test
#end
#interface BaseViewController : UIViewController
#property (nonatomic, retain) id <BaseViewControllerDelegate> delegate;
- (UIButton *)clearButton;
- (UIButton *)doneButton;
#end
Then I went over to UIViewControllerB and accessed the delegate property and passed in the string to be sent over to UITableViewControllerA when done button is tapped:
- (void)doneButtonTapped:(id)sender {
NSLog(#"DONE BUTTON TAPPED");
[[self delegate] didTapDoneButtonWithSelection:#"THIS IS A TEST"];
[[self navigationController] popViewControllerAnimated:YES];
}
In UITableViewControllerA I conform to the delegate protocol:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "BaseController.h"
#interface UITableViewControllerA : UITableViewController <BaseViewControllerDelegate>
#end
In UITableViewControllerA's implementation file I declare an instance var to hold the data passed over from the UIViewControllerB and display in a log message in viewWIllAppear. The didTapDoneButtonWithSelection method gets the data from the argument and stores it in the _test var:
#interface UITableViewControllerA ()
#end
#implementation UITableViewControllerA
{
NSString *_test;
}
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"%#", _test);
}
- (void)didTapDoneButtonWithSelection:(NSString *)selection
{
_test = selection;
}
Issue:
My log message is giving me (null).
Is what I'm doing possible? or am I going about it the wrong way. If so please correct me with a code example.
Thanks for your time
Regards
I had to grab an instance of UIViewControllerB and set UITableViewControllerA as it's delegate in the prepareForSegue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UITableViewControllerA *tvca = [segue destinationViewController];
[tvca setDelegate:self];
}
However this feels odd, like there is another way I should be doing this.
EDIT:I think initially misunderstood the question.
If you are using segues the correct answer is the other one. If you aren't then it's:
In UITableViewControllerA when you are about to push the BaseViewController it should look something like this:
BaseViewController* base = [[BaseViewController alloc] init];
base.delegate = self;
[self.navigationController pushViewController:base animated:YES];
First, I apologize for asking another delegation question. I've read many and can't find any that deal with passing something complex...
Here's what I am doing...
In my app, I have a ViewController: CollisionViewController where I want to let the user select two vehicles that are involved in a collision. All the vehicles are stored using CoreData and presented modally in SelectVehicleViewController. SelectVehicleViewController lists all the vehicles using a UICollectionView. In CollisionViewController, I have properties for Vehicle1 and Vehicle2 which are of a custom class that describes the properties of a vehicle.
In the CollisionViewController, I am using a UIButton to let the user first select Vehicle1, then Vehicle2 from SelectVehicleViewController presented modally.
I am using seques to determine which button was pressed before presenting the modal SelectVehicleViewController.
How do I setup a protocol that allows the user to pass the selected vehicle from the modal view to the correct vehicle object in the CollisionViewController?
collisionViewController should conform your protocol.
collisionViewController.h
#import "SelectVehicleViewController.h"
#interface ContactViewController : UIViewController <SelectVehicleDelegate>
collisionViewController.m
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqual:#"segueName"]) {
SelectVehicleViewController* VC = segue.destinationViewController;
VC.delegate = self;
//here you should probably send if you will select the vehicule 1 or 2
}
}
- (void)ViewController:(UIViewController *)sender
didUpdateData:(NSString *)value {
//do what you need with new data
//here you should have info if it is for vehicle 1 or 2
}
SelectVehicleViewController.h
#protocol SelectVehicleDelegate;
#interface SelectVehicleViewController : UIViewController
#property (nonatomic, assign) id <SelectVehicleDelegate> delegate;
#end
#protocol SelectVehicleDelegate <NSObject>
- (void)ViewController:(UIViewController *)sender
didUpdateData:(NSString *)value; //adapte according to what you should send back
#end
SelectVehicleViewController.m
//somewhere in a button click or....
[self.delegate ViewController:self didUpdateData:#"new value"];
//in this function you should have info if it is for vehicle 1 or 2