I am trying to pass data from a UITextView in one view controller to UITextView in another view controller. My application is using Storyboards.
in the first view controller I say:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
LyricKaraokeViewController *karaokeController = segue.destinationViewController;
karaokeController.hidesBottomBarWhenPushed = YES;
SongDoc *song = [[SongDoc alloc] initWithTitle:#"Title" lyrics:#"Test" thumbImage:nil];
karaokeController.detailItem = song;
NSLog(#"%#", karaokeController.detailItem.data.lyrics);
}
The NSLog outputs the appropriate text
In my second view controller I declare this interface:
#class SongDoc;
#interface LyricKaraokeViewController : UIViewController
#property (nonatomic, retain) SongDoc * detailItem;
#end
and this implementation ( just showing viewDidLoad for simplicity ):
- (void)viewDidLoad
{
NSLog(#"%#", self.detailItem.data.lyrics);
[super viewDidLoad];
}
and I confirm that there is the properties data.lyrics in the detailItem because we are passing in a SongDoc object and I output the SongDocs contents in prepareForSegue just prior....
My problem is in the second view controller I am receiving an error saying the detail item doesn't declare the property lyrics
But I know this is untrue so why is this happening?
any help would be great thanks :)
#class SongDoc;
only tells the compiler that SongDoc is a class, but does not read the implementation file. You have to
#import "SongDoc.h"
instead, so that the compiler knows which properties are declared by the class.
Related
Im new to programming with objective C and am working on moving data between View controllers. I am wondering if Bi-directional flow of data (variables) between ViewControllers is possible.
I can move data backwards (to the presentingViewController / sourceViewController) however i cannot move data forward (to the presentedViewController / destinationViewController).
I have made a simple case scenario (involving strings to get a principle of the idea) of this and it involves updating a UItextField on the destinationViewController using a UILabel in the sourceViewController and vice-versa.
I CANNOT update the UITextField using the UILabel, but can update the UILabel using the UITextField.
I have made Logs of different statements to track the variable values however when I switch ViewControllers the variables Data returns to null even if they are marked as strong.
Can you please offer any guidance, its been tearing away at my mind, or am I missing something obvious? I don't get why I keep getting a (null) value (in my NSLog) when I switch ViewControllers.
My sourceViewController / presentingViewController is named "ViewController."
My destinationViewController / presentedViewController is named "Gears2ViewController".
I have attached my code files below:
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *outputLabel;
- (IBAction)ExitToHere:(UIStoryboardSegue *)sender;
#end
ViewController.m:
#import "ViewController.h"
#import "Gear2ViewController.h"
#interface ViewController ()
- (IBAction)changeItem:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)changeItem:(id)sender {
Gear2ViewController *G2VC=[[Gear2ViewController alloc] init];
G2VC.peterSido=[NSString stringWithFormat:#"%#",self.outputLabel.text];
[self performSegueWithIdentifier:#"toGear2" sender:self];
NSLog(#"ViewController UILabel reads %#",G2VC.peterSido);
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
}
- (IBAction)ExitToHere:(UIStoryboardSegue *)sender {
}
#end
Gears2ViewController.h:
#import <UIKit/UIKit.h>
#import "ViewController.h"
#interface Gear2ViewController : UIViewController
#property (strong, nonatomic) NSString *peterSido;
#end
Gears2ViewController.m:
#interface Gear2ViewController ()
#property (strong, nonatomic) IBOutlet UITextField *updatedOutput;
- (IBAction)updateOutput:(id)sender;
#end
#implementation Gear2ViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"Gears2ViewController ViewDidAppear reads %#",self.peterSido);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"Gears2ViewController ViewDidLoad responds %#",self.peterSido);
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)updateOutput:(id)sender {
self.peterSido = self.updatedOutput.text;
((ViewController *)self.presentingViewController).outputLabel.text = self.peterSido;
NSLog(#"Gears2View Controller updating ViewController UILabel reads %#",self.peterSido);
}
#end
NSLog:
2015-06-29 18:52:58.798 testerBeta[21735:645772] Gears2ViewController ViewDidLoad responds (null)
2015-06-29 18:52:58.799 testerBeta[21735:645772] ViewController UILabel reads I like Pie
2015-06-29 18:52:59.317 testerBeta[21735:645772] Gears2ViewController ViewDidAppear reads (null)
2015-06-29 18:53:12.651 testerBeta[21735:645772] Gears2View Controller updating ViewController UILabel reads No I dont
Quite Lengthy but Thanks in Advance!!!
You want to pass the data in prepareForSegue:, like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)__unused sender
{
if ([[segue identifier] isEqualToString:#"toGear2"])
{
Gear2ViewController *controller = (Gear2ViewController *)segue.destinationViewController;
controller.peterSido = self.outputLabel.text;
}
}
The reason why is that the segue instantiates the presented view controller for you, and you then set the property of the instantiated view controller which the segue will present.
To pass the data back, you can use an unwind segue, which can get the value from the presented view controller's property.
- (IBAction)unwindFromGear2:(UIStoryboardSegue *)segue
{
Gear2ViewController *controller = (Gear2ViewController *)segue.sourceViewController;
self.outputLabel.text = controller.peterSido;
}
This is the proper way to pass data back and forth via segues. Gear2ViewController shouldn't be setting properties on its presentingViewController.
Update:
The preferred way to test that a property isn't nil is like this:
if (self.peterSido)
{
self.updatedOutput.text = self.peterSido;
}
else // No need for if test here
{
self.updatedOutput.text = #"";
}
That's the long form, but the assignment and if test can be more concisely written as:
self.updatedOutput.text = self.peterSido ?: #"";
When you declare any variable as #property then you need to synthesize it in .m file .
You have declared your outputLabel as #property but you missed to synthesize it in .m file.
When you synthesize any variable then it allows you to get and set the values to it .
Do it it will help you.
Thank you.
Two properties:
#property (retain, nonatomic) NSString *drinkType;
#property (retain, nonatomic) NSString *wheelType;
When accessed from viewDidLoad as self.drinkType, etc, they hold the value I expect. However, when accessed from a public method
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas;
they are null. What is happening here?
The "selectedAromas" array is passed from another controller to this method.
ViewController *aromaVC = [[ViewController alloc] init];
[aromaVC updateSentenceWithSelectedAromas:selectedAromas];
ViewController.h
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas;
#property (retain, nonatomic) NSString *drinkType;
#property (retain, nonatomic) NSString *wheelType;
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// This is working
NSLog(#"The drink type is:%#", self.drinkType);
}
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas {
// This returns null
NSLog(#"The drink type is:%#", self.drinkType);
}
I think your missing quite a few things, which leads me to think that you're missing some basic understanding of variable scope in ObjectiveC, let's see if this helps you in some way:
First, your selectedAromas array has no relation whatsoever with drinkType and wheelType. So passing this array to the ViewController seems irrelevant.
Second, in your ViewController you're declaring your own drinkType and wheelType variables, so there's no way they will have the value of some other class or Controller.
You probably aren't setting your properties soon enough (init would be a good place). viewDidLoad is called much later in relation to the code you posted.
Okay, Michael Dautermann was absolutely right. The method updateSentenceWithSelectedAromas was in fact running in a separate instance of the view controller. To solve this problem I implemented a protocol listener with my method and set the delegate of the child controller to its parent using a segue.
Thank you everyone for all your help.
In case anyone stumbles upon this, here is what I did:
ViewController2.h
#protocol updateSentenceProtocol <NSObject>
//Send Data Back To ViewController
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas;
#end
#interface ViewController2 : UIViewController
// delegate so we can pass data to previous controller
#property(nonatomic,assign)id delegate;
#end
ViewController2.m
#synthesize delegate;
-(void)someMethod {
[delegate updateSentenceWithSelectedAromas:selectedAromas];
}
ViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"viewController2Segue"])
{
// Get reference to the destination view controller
ViewController2 *vc = [segue destinationViewController];
vc.delegate = self;
}
}
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas {
// do stuff with array and properties as needed
}
I have a view controller with an image view in it.
I have a popover with a table view in it which is anchored to a bar button in this view controller.
I would like to be able to load images into the image view by using the table in the popover.
Both the popover and the main view controller have separate view controller classes.
I have launched the popover from a segue.
How can I do this?
I am assuming that your segue takes you from your imageViewController to your popped-over tableViewController.
Then you can set your imageViewController as delegate to the tableViewController, so that you can call methods on it from the tableViewController in a decoupled manner.
MyTableViewController.h
In your tableViewController header file declare a protocol which it will expect it's delegate to follow. Place it above your #interface section:
#protocol MyTableViewControllerDelegate <NSObject>
- (void) dismissPopoverAndLoadImage:(NSString*)imageName;
#end
Also declare a property to hold a reference to it's delegate:
#property (nonatomic, weak) id <MyTableViewControllerDelegate> delegate;
The protocol declares the method signature that your tableView will expect to be able to call on its delegate. It allows it to send back some data, and get itself dismissed. The delegate (in this case, your imageViewController) will have to implement this method.
MyTableViewController.m
The method is called on the delegate when a table cell is selected:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
NSString* imageName = cell.textLabel.text;
[self.delegate dismissPopoverAndLoadImage:imageName];
}
MyImageViewController.h
include MyTableViewController.h and add the delegate protocol to the #interface.
#include "TableViewController.h
#interface MyImageViewController: UIViewController <MyTableViewControllerDelegate>
Declare a property to hold a reference to your UIPopOverController so that you can send it a dismiss message:
#property (nonatomic, weak) UIPopoverController* seguePopoverController;
(these steps could be moved to your .m file's category extension for better encapsulation).
MyImageViewController.m
You will set the delegate property in MyImageViewController's prepareForSegue method, which gets called when the segue is invoked.You will also set the reference to the popoverController here.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"popoverTable"]) {
self.seguePopoverController = [(UIStoryboardPopoverSegue*)segue popoverController];
[segue.destinationViewController setDelegate:self];
}
}
}
Lastly, you implement the tableViewController's delegate method:
- (void) dismissPopoverAndLoadImage:(NSString*)imageName
{
self.imageView.image = [UIImage imageNamed:imageName];
[self.seguePopoverController dismissPopoverAnimated:YES];
}
update
Aside from the fact that the popOverController itself is a slightly unusual entity (a controller without a view, inheriting directly from NSObject), most of this is the standard delegation pattern. You could simplify it somewhat by using a bit of indirection and runtime checking in didSelectRowAtIndexPath:
if ([[self delegate] respondsToSelector:#selector(dismissPopoverAndLoadImage:)])
[[self delegate] performSelector:#selector(dismissPopoverAndLoadImage:)
withObject:imageName];
In this case you would not need to define the protocol or <adhere> to it, and you wouldn't need to #import MyTableViewController. However the compiler would give you no help if you did not implement the method correctly. Which, as you can see from my earlier mistake, is probably unwise.
I have this storyboard:
When I press the "Insegnante" button in the first view controller (wich is called newCourseViewController) it show me a table view with a list of teacher. When I press on a teacher (and the method tableView:canEditRowAtIndexPath: is called) I want that the UITableViewController "pass" the object pressed to the first view controller.
This is my code for the first view controller newCourseViewController.h
#import <UIKit/UIKit.h>
#import "Teacher.h"
#interface newCourseViewController : UIViewController
#property (nonatomic , strong) Teacher *teacher;
#end
And this is my code for the first view controller newCourseViewController.m (only important code)
#import "newCourseViewController.h"
#import "Courses.h"
#import "Teacher.h"
#import "addTeacherToCourseViewController.h"
#interface newCourseViewController ()
#property (weak, nonatomic) IBOutlet UITextField *textField;
#end
#implementation newCourseViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)setTeacher:(Teacher *)teacher
{
self.teacher = teacher;
NSLog(#"Maestro settato!");
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"addTeacherToCourse"]) {
[segue.destinationViewController setPreviousViewController:self];
}
}
Now the code for the second view controller addTeacherToCourseViewController-h
#interface addTeacherToCourseViewController : UITableViewController
#property (nonatomic , weak) id previousViewController;
#end
and the addTeacherToCourseViewController.m (only the important method)
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Teacher *teacher = [self.teachers getTeacherInPosition:indexPath.row];
[self.previousViewController setTeacher:teacher];
[self.navigationController popViewControllerAnimated:YES];
}
In the first view controller in the prepareForSegue method I set myself to the previousViewController in the second view. Then I "pass" the teacher selected and than I dismiss the second view controller.
When the application execute the [self.navigationController popViewControllerAnimated:YES]; Xcode crash and the simulator crash.
I can't figure out what is the problem. Can you help me?
To send values to parent controller you have to use protocols. I will provide proper steps you should take in order to have your desired functionality working.
1.
Create a protocol for your AddTeacherToCourseController.
In your AddTeacherToCourseController.h add the following right below the imports:
#protocol AddTeacherToCourseControllerProtocol <NSObject>
- (void)yourDelegateMethod:(Teacher *)insegnante;
#end
And below interface tag add:
#property (strong, nonatomic) id <AddTeacherToCourseControllerProtocol> delegate;
2.
In AddTeacherToCourseController.m:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// I would use the existing array you are using to display the teachers in order to select the correct one you want to send back like this:
// Teacher *teacher = [self.teachers getTeacherInPosition:indexPath.row];
[self.delegate yourDelegateMethod:[yourTeacherArray objectAtIndex:indexPath.row]];
}
[this method will call your delegate method through the protocol and will pass your selected professor to the parent controller]
3.
In your parent controller, your newCourseViewController.h right after interface line add:
<AddTeacherToCourseControllerProtocol>
4.
If you do not have an Insegnante button action, create one in interface builder [dragging and naming]. Then add the following to this action:
// assuming your storyboard is named MainStoryboard. here you create your segue programmatically:
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
addTeacherToCourseViewController *addTeacherController = (addTeacherToCourseViewController *)[storyBoard instantiateViewControllerWithIdentifier:#"addTeacherToCourseViewController"];
addTeacherController.delegate = self;
[self.navigationController pushViewController:addTeacherController animated:YES];
5.
In Interface Builder:
Remove your segue from Insegnante button.
Edit the Storyboard Id of 'addTeacherToCourseViewController' to 'addTeacherToCourseViewController'
6.
In newCourseViewController.h write your delegate method:
- (void)yourDelegateMethod:(Teacher *)insegnante{
// Do whatever you want with your Insegnante
// and be sure to pop the second controller from the view stack:
[self.navigationController popViewControllerAnimated:YES];
}
Let me know if you have questions and if my answer helped anyone.
In order to give you an exact answer please tell me what object you are using to display your list of professors in your second controller, the tableViewController. I am guessing that is an array of Teacher instances. Is that correct? [class Teacher]
I have a little problem with the application I currenty work on. I create a simpliest project to illustrate my problem.
So, I create a "Navigate-Base Application". I add an other UITableViewController named TableViewController (the one which is created with the project is named RootViewController). I create an instance of TableViewController when I touch a line in the RootViewController.
I create a custom class named "MyCustomClass".
MyCustomClass.h (full code) :
#import <Foundation/Foundation.h>
#interface MyCustomClass : NSObject {
NSString *name;
}
#property (nonatomic, retain) NSString * name;
#end
MyCustomClass.m (full code) :
#import "MyCustomClass.h"
#implementation MyCustomClass
#dynamic name;
#end
I had a MyCustomClass attibute in TableViewController class.
TableViewController.h (full code) :
#import <UIKit/UIKit.h>
#import "MyCustomClass.h"
#interface TableViewController : UITableViewController {
MyCustomClass *aCustomObject;
}
#property (nonatomic, retain) MyCustomClass *aCustomObject;
#end
At the load of TableViewController, I try to display aCustomObject's content.
TableViewController.m (top of the file and what I modify in the template's file) :
#import "TableViewController.h"
#implementation TableViewController
#synthesize aCustomObject;
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
NSLog(#"Name : %#",self.aCustomObject.name);
}
Before, I create and give a value to aCustomObject.name in RootViewController :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewController *detailViewController = [[TableViewController alloc] initWithNibName:#"TableViewController" bundle:nil];
detailViewController.aCustomObject.name = #"The Name";
[self.navigationController pushViewController:detailViewController animated:YES];
}
Console said :
2011-06-22 07:21:11.087
MyTestApp[12822:207] Name : (null)
I think it's a stupid thing but I don't find myself after hours of try.
Thanks a lot and excuse me for my english mistakes,
You forget to initialize your custom object in the tableViewController's viewDidLoad Method.
Try this.
- (void)viewDidLoad {
[super viewDidLoad];
if(aCustomObject == nil){
self.aCustomObject = [[[MyCustomClass alloc] init] autoRelease];
}
self.aCustomObject.name = #"";
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
//this will show empty here.
NSLog(#"Name : %#",self.aCustomObject.name);
}
You use the #dynamic keyword to tell
the compiler that you will fulfill the
API contract implied by a property
either by providing method
implementations directly or at runtime
using other mechanisms such as dynamic
loading of code or dynamic method
resolution. It suppresses the warnings
that the compiler would otherwise
generate if it can’t find suitable
implementations. You should use it
only if you know that the methods will
be available at runtime.
from Apple Documentation
You are claiming in the question that you included full source for MyCustomClass.m. Where did you implement the getter and setter for the property? If you want the compiler to generate the methods for you, you should use
#synthesize name;