I tried to push to a ViewController using prepareForSegue. When I'm pushing, I want to set an image on ImageView in pushed view controller. Here what I tried,
ViewController
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
UIButton *btn = sender;
if (btn.tag == 50) {
if (jpegData) {
[self saveTempImage:jpegData];
}
if ([segue.identifier isEqualToString:#"HomeView"]) {
HomeViewController *vc = [segue destinationViewController];
vc.backImageView.image = capturedImage;
vc.isBackImage = true;
}
}
}
I have an ImageView in HomeViewController. I tried to set it's image using this vc.backImageView.image = capturedImage;. capturedImage is not null. But the image is not set in ImageView.
How can I fix this?
Thanks in Advance!
Your outlates are not yet set there, I believe backImageView is null at this point. Have backImage property and when backImageView is ready (added to view hierarchy, viewDidLoad is good place for that) then set its image property.
1) You should make the backImageView property (and all the other outlets of HomeViewController) private, because for this view, HomeViewController is exclusively responsible and no other class should be able to manipulate this view. This is current established convention in iOS development. You can do this by adding class extension above the #implementation keyword in HomeViewController .m file.
#interface HomeViewControler ()
#property (nonatomic, strong) IBOutlet UIImageView *backImageView;
//...other private properties ...
#end
#implementation MyViewControler
Right after that, you need to move view outlet properties from the .h interface file to the .m interface extension to have the declared privately.
2) You should create a private property called capturedImage in the extension too.
#interface HomeViewControler ()
#property (nonatomic, strong) IBOutlet UIImageView *backImageView;
#property (nonatomic, strong) UIImage *capturedImage;
#end
3) Declare a public method in h. file called configureWithImage
-(void)configureWithImage:(UIImage *)paramImage;
and implement it i m. file like this
-(void)configureWithImage:(UIImage *)paramImage
{
self.capturedImage = paramImage;
}
4) Next you need to make sure the passed image is used in the imageview, for that HomeViewController's viewDidLoad makes a lot of sense.
-(void)viewDidLoad
{
[super viewDidLoad];
self.backImageView.image = self.capturedImage;
//...other code...
}
5) Last step, in prepareForSegue you configure your view controller with the image
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIButton *btn = sender;
if (btn.tag == 50)
{
//...other code...
if ([segue.identifier isEqualToString:#"HomeView"])
{
HomeViewController *vc = [segue destinationViewController];
[vc configureWithImage:capturedImage];
//...other code...
}
}
The "morale of the story" is that by having a proper public interface that is the sole entry point for configuration (the config method) you decouple the two entities. It means the source view controller merely passes the image without having to know ANYTHING about what happens in the destination VC. The image is then processed by the responsible destination view controller.
Should you change your mind and do some layout/view content changes in the HomeViewController (possible filter and process the image for visual effects) later in time, the source view controller will not be affected at all because wheat happens in HomeViewController is nobody else's concern, and you will keep the public configuration method intact. That means the change will not require to maintain code in prepareForSegue, only in the destination VC.
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.
I have a prepareForSegue method setup in that sends everything I want to the destinationViewController except the value for a UILabel on the destinationVC. I threw a NSLog statement in to see what value it prints and it prints what I want.
It doesn't crash, but it doesn't set. I know I'm missing something very basic here, but it's not jumping out at me.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(UIButton *)sender {
if ([[segue identifier] isEqualToString:#"directionsSegue"]) {
// Set destination view controller
DirectionsViewController *destinationVC = segue.destinationViewController;
// Pick out the "thing" you want to send to destinationVC
CGPoint point = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:point];
// Set the "thing" on the destinationVC
PointOfInterest *poi = [self.fetchedResultsController objectAtIndexPath:indexPath];
destinationVC.destinationLabel.text = poi.name;
NSLog(#"destinationVC.destinationLabel.text = %#", poi.name);
destinationVC.destinationLatitude = poi.latitude;
destinationVC.destinationLongitude = poi.longitude;
}
}
My property declared in the header of my destinationVC:
#property (strong, nonatomic) IBOutlet UILabel *destinationLabel;
Solution from answers below:
Mystery solved! Here's what I did:
on my destinationVC, I added this to my header:
#property (nonatomic, strong) NSString *destinationName;
I put this back in the implementation:
#property (strong, nonatomic) IBOutlet UILabel *destinationLabel;
In destinationVC, I added this to my viewDidLoad:
self.destinationLabel.text = self.destinationName;
Your label will be nil in prepareForSegue because it won't be instantiate at this time. In fact, IBOutlet are initialised yet once your view is loaded. That's why it's not working.
The best way to solve your issue is to create another property in your DirectionsViewController where will be stored your text. This one is available directly after your controller initialisation, and then you can set your label directly wherever in your controller.
IBOutlet objects are not initialized until the view controller's view loads. That happens after the segue. Create a custom property and update that during prepare and then copy it to your label during viewDidLoad.
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
I created a Master/Detail application with xCode and changed very little about it. In prepareForSegue in the MasterViewController, I added this to try to put some text in a label in the detail view controller
MMDetailViewController *detailVC = [segue destinationViewController];
detailVC.testdetail.text = #"test";
I also added a label to the detailViewController on the storyboard and then did control/drag to the detailViewController to connect them.
#property (strong, nonatomic) IBOutlet UILabel *testdetail;
Therefore, when I set the text in prepareForSegue, I expected it to show in the label once I ran the code on the simulator. However, it didn't show. Can you explain why?
Its because you can't update the UILabel before it load...
So what you need to do is this:
Make Nsstring and update it value, then in viewDidLoad update your Label.
In your detailViewController.h add
#property (strong, nonatomic)NSString *testString;
Then in the detailViewController.m in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.testdetail.text = testString;
}
in your masterViewController:
MMDetailViewController *detailVC = [segue destinationViewController];
detailVC.testString = #"test";
That's it, it should work now :)
Happy coding
The outlets are accessible after -viewDidLoad is fired. That didn't happen yet in -prepareForSegue:, so you probably access a label with nil value.
Ensure that detailVC.testdetail is not nil in -prepareForSegue: first.
If it's nil, which I assume, the UILabel isn't loaded yet. See IBOutlet properties does not update when using prepareForSegue method
Instead, use a NSString property to pass the value around. Put the value into the label in -viewDidLoad of your destination MMDetailViewController because it's responsible for doing that itself.
I have two view controllers
DetailViewController
BlogViewController
I have a 'push' set-up on the storyboard with the identifier 'ShowBlog'
i need to send the title of the blog from the UILabel below on the DetailViewController:
#property (strong, nonatomic) IBOutlet UILabel *TitleLabel;
to a UILabel on the BlogViewController called BlogTitleLabel:
#property (strong, nonatomic) IBOutlet UILabel *BlogTitleLabel;
i know i need to use:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"ShowBlog"]) {
// Im struggling with the code
}
}
But im struggling with the code to go in it
Use NSUserDefaults the code goes something like this:
Store the title:
[[NSUserDefaults standardUserDefaults] setObject:label.title forKey:#"nameForStoredVariableHere"];
[[NSUserDefaults standardUserDefaults] synchronize];
Retrieve the stored title:
NSString *storedTitle = [[NSUserDefaults standardUserDefaults] objectForKey:#"nameForStoredVariableHere"];
After that you're free to use the title as you please
prepareForSegue is called before destination VC's viewDidLoad so don't try to access any view object of destination view controller in prepareForSegue. Its better you create a string property in destination and set that in prepareForSegue method. In destination viewController's viewDidLoad set it to label.
You should not try to send data from label to label. Labels are view objects. They display information and collect input from the user. They do not store information.
Also, you should never, ever try to manipulate another view controller's views directly. That violates the other view controller's encapsulation.
Both view controllers should have NSString properties for this. Let's call it blogTitle on both VCs.
Your DetailViewController should set it's blogTitle somewhere during it's setup, and then in viewWillAppear:animated, display that value to it's titleLabel outlet:
- (void) viewWillAppear: animated;
{
[super viewWillAppear: animated];
self.titleLabel.text = self.blogTitle;
//your other code here
}
Then, in your prepareForSegue:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ShowBlog"])
{
BlogViewController *theBlogController = [segue destinationViewController];
theBlogController.blogTitle = self.blogTitle; //Pass the blog title to the other VC
}
}
And then in the BlogViewController's viewWillAppear, copy the blogTitle to it's title label:
- (void) viewWillAppear: animated;
{
[super viewWillAppear: animated];
self.titleLabel.text = self.blogTitle;
//your other code here
}
Note that Cocoa/iOS programming has strong naming conventions that you should follow. Only filenames and class names should start with a capital letter. Method names, instance variable names, and property names should start with a lower-case letter. So your TitleLabel should be titleLabel, and BlogTitleLabel should be blogTitleLabel.