I have a UITableViewController which pushes a UIViewController and I need to pass two NSDates and a BOOL from that child view controller back to the table view controller.
How could I do this? Let me know if I am not being clear or you need more explanation.
I'd appreciate any help, thanks.
Using a delegate is the proper way of passing info from one controller to another.
Generally, what you want to do is to declare a protocol in your UITableViewController header, implement it, and then init the UIViewController with a delegate.
In a nutshell, your UITableViewController .h file should have something like this:
#protocol setDateDelegate <NSObject>
#required
- (void) setDateOne:(NSDate *)one dateTwo:(NSDate *)two;
#end
Then, you ought to implement the above mentioned class doing whatever you want to do with the dates in your UITableViewController .m file.
In your UIViewController, you could (alternative at the end) define a custom init method:
First, define a delegate property in your .h, and a custom init method, then implement it in your .m with something like this:
- (id)initWithDelegate:(NSObject *)myDelegate
{
self = [super init];
if (self) {
self.delegate = myDelegate;
}
return self;
}
Next, when you need to pass the dates to your UITableViewController, just use
[self.delegate setDateOne:one dateTwo:two];
There are more ways of doing this, including skipping the custom init method and just setting the delegate after you create a UIViewController:
MyUIViewController *viewController = [[[MyUIViewController alloc] init] autorelease];
viewController.delegate = self;
...
It's more of a personal preference style.
I've probably messed up the code (and forgotten a few bits) because I'm writing this off the top of my head, but it should be good enough to get you started.
Related
Hy guys, I'm new at ObjC and I'm still learning;
[sliderContrast setHidden:YES] (i also used slider.hidden = YES) doesn't make the slider invisible, instead it works fine with textfields. Do you know why?
I've also tried using property and synthesize but the result doesn't change
---Interface
#interface Controller : NSWindowController{
IBOutlet NSTextField *labelContrast;
IBOutlet NSTextField *valueContrast;
IBOutlet NSSlider *sliderContrast;
}
- (IBAction)changeContrast:(id)sender;
#end
---Implementation
#import "Controller.h"
#interface Controller ()
#end
#implementation Controller
- (void)windowDidLoad {
[super windowDidLoad];
[labelContrast setHidden:YES];
[valueContrast setHidden:YES];
[sliderContrast setHidden:YES];
}
- (IBAction)changeContrast:(id)sender {
}
#end
If you declare pointers for your objects but you don't allocate them yourself you can not set anything that is not there. Your setHidden: method calls end up in local void aka nowhere.
programmatically
If you go the coding way you would declare, allocate and initiate first. With
labelContrast = [NSTextField alloc] initWithFrame:NSMakeRect(x,y,w,h)];
before you call other methods of the object class (Or similar init methods).After that you can call methods on the object.
Almost all objects inherit an according -(instancetype)init or -(instancetype)initWith... method you can use. If there is no init method given, then there is another way to do it right and the moment to start reading again :).
With Interface Builder
By typing IBOutlet or IBAction in front of a declaration you just give a hint for Xcodes Interface Builder where to hook up and apply onto (associate) the placed object in (nib,xib,storyboard) with its object ID in the XML scheme to a reference in code.
So after you connected your code and the object in IB you can avoid allocation and init process for that particular object. Be aware that calling methods on objects that are not instanced yet is not working. Which is why you code in - (void)windowDidLoad, -(void)viewDidLoad or -(void)awakeFromNib because those are the methods that get called after "IB" has done its job for you.
I have a table view in my previous view which is get data from an array in my app. I have a view to update data which is push on cell select. Once data is updated in the view i call
[self.navigationController popViewControllerAnimated:YES];
to go back to previous view. But the label get stacked with old and new data I don't know why... If I go one view back and come back again to the tableview everything is fine only new data is shown..
So I guess I have to rebuild view to avoid the problem. Is this possible ?
Your question initially asked about rebuilding a controller, so here's the answer to that:
I'm assuming that your have a navigation stack like this:
An instance of FirstController
An instance of SecondController
An instance of ThirdController
A thing happens in your third controller, and you now want the stack to look like this:
An instance of FirstController
A new instance of SecondController
The first thing to do is to define a delegate protocol for ThirdController, in your header file like this:
#protocol ThirdControllerDelegate;
#class ThirdController : UIViewController
#property (nonatomic, weak) id<ThirdControllerDelegate> delegate;
... your existing stuff ...
#end
#protocol ThirdControllerDelegate <NSObject>
- (void)thirdControllerDidDoTheThing:(ThirdController *)thirdController;
#end
Instead of having the ThirdController pop itself, it should tell its delegate that the thing happened, like so:
[self.delegate thirdControllerDidDoTheThing:self];
You'll also want to define a delegate protocol for SecondController, in the same way, and you'll want to specify that SecondController can act as a delegate for a ThirdController:
#import "ThirdController.h"
#protocol SecondControllerDelegate;
#class SecondController : UIViewController <ThirdControllerDelegate>
#property (nonatomic, weak) id<SecondControllerDelegate> delegate;
... your existing stuff ...
#end
#protocol SecondControllerDelegate <NSObject>
- (void)secondControllerDidDoTheThing:(SecondController *)secondController;
#end
Notice the extra bit in there where we put <ThirdControllerDelegate> after the #class line.
Now we find the part of the SecondController that shows the ThirdController, and have it set the controller's delegate first:
- (void)showThirdControllerAnimated:(BOOL)animated
{
ThirdController *thirdController = [[ThirdController alloc] init];
thirdController.delegate = self;
[self.navigationController pushViewController:thirdController animated:animated];
}
When the SecondController gets the message from the ThirdController, it should pass it on to its delegate, like this:
- (void)thirdControllerDidDoTheThing:(ThirdController *)thirdController
{
[self.delegate secondControllerDidDoTheThing:self];
}
Finally we modify FirstController so that it can act as the delegate to the SecondController:
#import "SecondController."
#class FirstController : UIViewController <SecondControllerDelegate>
When we show the SecondController, we make the FirstController its delegate:
- (void)showSecondControllerAnimated:(BOOL)animated
{
SecondController *secondController = [[SecondController alloc] init];
secondController.delegate = self;
[self.navigationController pushViewController:secondController animated:animated];
}
Finally we implement the SecondController's delegate method to pop to the first controller, then show a new secondController.
- (void)secondControllerDidDoTheThing:(SecondController *)secondController
{
[self.navigationController popToViewController:self animated:NO];
[self showSecondControllerAnimated:NO];
}
Done.
You've since altered your question; in the case you now describe you can follow the steps above to make the SecondController the delegate of the ThirdController, but then inside thirdControllerDidDoTheThing you just reload the data of your SecondController's view; if it's a UITableView or UICollectionView you'd do that with the reloadData method.
you should refresh your table view every time it is going to be shown; otherwise, the old data would be cached.
In the controller that controls the table view:
- (void)viewWillAppear {
[tableView reloadData];
}
Edit - I've revised to clarify.
//myViewController.h
#property (strong, nonatomic) SCDataObject *dataObject;
In storyboard, I've created a single VC with the custom class myViewController. I've given it the storyboard ID myViewControllerStoryboardId.
//anotherClass.m
UIViewController *viewController =
[self.storyboard instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
I instantiate the custom VC, but notice I'm setting it to a pointer of type UIViewController on purpose for a few reasons.
//anotherClass.m
//I want to set the property, dataObject, of the instantiated VC, but this doesn't work.
viewController.dataObject = something;
The actual object has the property, but the pointer to it is of a different class. How do I set the property?
You need to use your viewcontrollers class. I used MyViewController, just replace it with your class name. Here is the code:
#import "MyViewController.h"
...
MyViewController *viewController =
[self.storyboard
instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
//It works!
viewController.dataObject = something;
EDIT:
If you really don't want to use your custom class, give this code a shot:
[viewController setValue:something forKey:#"dataObject"];
But i can't think of a reason for doing this.
if myViewController is a subclass of UIViewController then you should really do:
myViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
You also need to make sure that you change the class of your custom UIViewController in your Storyboard from the generic UIViewController to myViewController (select your VC in the StoryBoard and then go to the "Identity Inspector", that is the 3rd tab, in the right-hand Utilities screen. You can change there the class of the custom VC to your subclass).
Update after Question Edit:
The UIViewController class is part of UIKit. Anytime you need to customize a VC beyond what can be done in SB, you need to subclass UIViewController (and create a custom VC). So if you are trying to have a custom property (dataObject) of a UIVC without subclassing UIVC, the answer is you can't.
Having said that, maybe the question is not fully clear. If you are looking to have a subclass of UIVC with a property dataObject but with multiple instances of that subclass; you can always create that subclass once and then have multiple instances of that class that you would give different names so:
myViewController* viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
myViewController* viewController2 = [self.storyboard instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
myViewController* viewController3 = [self.storyboard instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
They will all be different instances of the same subclass (myViewController) and the values of the dataObject will be distinct for each instance. In other words, you can set:
viewController1.dataObject=someObject;
viewController2.dataObject=anotherObject;
If you just type viewController as id, though you still can't use the property directly, you could send it a message the corresponds to that property, without warnings. You could use respondsToSelector: to see if it supports the accessor function for that property.
id viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
if( [viewController respondsToSelector:#selector(setDataObject:)] ) {
[viewController setDataObject:something];
}
Personally, I would probably just:
MyViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"myViewControllerStoryboardId"];
viewController.dataObject = something;
You will get warnings, but this should work for setting:
[viewController setDataObject:something];
And this for getting:
[viewController dataObject];
To avoid a crash, you probably would do something like this:
if ([viewController respondsToSelector:#selector(setDataObject:)])
{
[viewController setDataObject:something];
}
I work on a project for iPad with Xcode 4.
I have a main view controller with many UITextField.
The TextFieldDelegate is a separate class in a separate file.
How can I refer, from TextFieldDelegate to a property (to a UITextField) of the main view controller (for example assign a value to a double)?
Thank you.
In most cases, if you want to use a separate delegate you should not need more information than what is passed to the delegate (the method's parameters). However, if you don't want to use your MainViewController as a delegate for your UITextField, you can initialize your TextFieldDelegate in your MainViewController instance and pass it the MainViewController instance.
For example you could have:
#import "MainViewController.h"
#interface TextFieldDelegate<UITextFieldDelegate> {
MainViewController* mainViewController;
}
#property(nonatomic,retain) MainViewController* mainViewController;
-(id)initWithController:(MainViewController*)controller;
#end
#implementation TextFieldDelegate
#synthesize mainViewController;
-(id)initWithController:(MainViewController*)controller {
if(self = [super init]) {
//some stuff
self.mainViewController = controller;
}
return self;
}
#end
Then in your MainViewController:
TextFieldDelegate tfd = [[TextFieldDelegate alloc] initWithController:self];
You just need to set the TextFields' delegate to tfd and you should be able to reference the MainViewController properties from the TextFieldDelegate instance. It's also possible to initiate it somewhere else, as long as you send the MainViewController instance to your TextFieldDelegate instance.
Edit: woups forgot a few '*'
Is it good practice to be setting the delegate of a UITextField in viewDidLoad or in an init method?
I tried setting the delegate as self in an init method, but it wasn't calling the corresponding delegate methods, when I moved the code into viewDidLoad, it registered as setting self as the delegate?
It seems that I should be able to set it in either method, if someone can help clear this up for me it would be much appreciated.
-(id) init {
self = [super init];
if (self)
textField.delegate = self; //this text field is an IBOutlet
//some other code here as well
return self;
}
OR
-(void)viewDidLoad {
[super viewDidLoad];
textField.delegate = self;
}
If your text field is an IBOutlet then until viewDidLoad method is called your text field will be nil (hence you set delegate to nil object). When viewDidLoad gets called it literally means that view was loaded and all IBOutlets and IBActions were connected and are at your disposal.
Assuming that your class is a UIViewController and is loaded from a NIB (since you have an IBOutlet), the proper init method to override is initWithCoder:(NSCoder*)decoder. What's happening right now:
iOS loads your NIB file and creates your UIViewController
The UIViewController is created by calling its initWithCoder:(NSCoder*)decoder method.
The first thing that initWithCoder does is to call init and therefore your code, before it has even decoded the NIB.
Because it hasn't decoded the NIB yet, your textField IBOutlet hasn't been set yet (if you debug it you should be able to see that its value is nil inside your init). And therefore setting the delegate doesn't do anything.
The easiest way to proceed is indeed to set your delegate in the viewDidLoad method; it can't be done in init. However it can be done by overriding initWithCoder:
- (id)initWithCoder:(NSCoder*)decoder {
self = [super initWithCoder:decoder];
if (self)
textField.delegate = self;
return self;
}
This time you wait until UIViewController's implementation of initWithCoder has finished decoding the NIB and so all your outlets have been set.