I have a custom view controller called TimerViewController and subclasses of this called FirstViewController and FourthViewController.
I declared an instance of FirstViewController in FirstViewController's .h named controller.
In the viewDidLoad method of FourthViewController's .m, I have:
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"mainController"];
In the storyboard, I've declared the view controller ID as mainController and declared a custom class of FourthViewController. Then, in FourthViewController's .m I have:
controller.mainLab.text = [NSMutableString stringWithFormat:#"This is a string"];
NSLog(#"%#", controller.mainLab.text);
This, however, outputs (null).
Why does this happen?
mainLab must be nil. So your outlet probably isn't connected to your XIB.
As an aside, using stringWithFormat: with a string that isn't a format is wasteful.
Your neglecting to tell us something about the rest of your project, im just not sure what it is.
I fired up Xcode just to run through this real quick and the process is simple.
Drag UI Label to your XIB
Control click from label to your .h
For testing I did
#import "SOViewController.h"
- (void)viewDidLoad
{
[super viewDidLoad];
self.mainLabel.text = #"This is my label";
NSLog(#"%#", self.mainLabel.text);
}
My .h looks like this:
#import <UIKit/UIKit.h>
#interface SOViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *mainLabel;
#end
Is this part of a custom class? Is there something else going on? If its a vanilla label it should work without issue using the aforementioned code.
it looks like your mainLab has not yet been created. When you call methods on nil objects, the method automatically returns nil. Make sure you actually create the label before running this line of code.
You can't access the label (or any other UI element) of another controller right after you instantiate it, because its viewDidLoad method has not yet run. If you want to set the text of a label in another controller, you have to pass the text to that controller, and have it set the text on the label in its viewDidLoad method. So instead of the this:
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"mainController"];
controller.mainLab.text = [NSMutableString stringWithFormat:#"This is a string"];
You need to do this:
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"mainController"];
controller.mainLabText = #"This is a string";
where mainLabText is a string property you create in FourthViewController. Then populate the label in FourthViewController's viewDidLoad:
self.mainLab.text = self.mainLabText;
Related
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 am making an app, and I want to the able to change the tint color of the UITabBarController. I created a custom class for UITabBarController and assigned it to the UITabBar in IB. It works fine. This class has an IBAction that changes it's color called alterColor:
That's all fine and well when the app first launches. But after that, I can not seem to run that action from another class. I have a settings class, where I try to change the color. I get the correct instance by doing the following in the settings class.
.H
#property (nonatomic, strong) TabBarController *tabController;
.M
#implementation LogbookThirdViewController
#synthesize CarbsTextField;
#synthesize tabController;
...
-(IBAction)colorRedPicked:(id)sender {
NSString *writableDBPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"colorChoice.txt"];
NSString *carbRatio = #"red";
[carbRatio writeToFile:writableDBPath atomically:YES encoding:NSUTF16StringEncoding error:nil];
NSString *readFile;
readFile = [NSString stringWithContentsOfFile:writableDBPath encoding:NSUTF16StringEncoding error:nil];
NSLog(#"Color = %#", readFile);
readFile = nil;
[tabController alterColor:tabController]; //This line should run the method.
}
However, nothing happens.
Assuming that this viewController is contained within your tabbarController, you don't need that property.
There is an existing property on UIViewController:
#property(nonatomic, readonly, retain) UITabBarController *tabBarController
(you don't need to add that in code, it is in the class you inherit from when you make your viewController sublcass)
Refer to the tabBarController thus:
self.tabBarController
You may need to typecast it to your custom controller to encourage the compiler to allow you to send your colorchanging method to it. But either way you will be getting the message to the correct object.
I have a custom UITableViewCell that I am instantiating from a nib using instantiateWithOwner:(id)owner options:(NSDictionary *)options. When the nib is instantiated, I am saving it to an IBOutlet defined in my view controller, which is set as the file's owner in the .xib file. Everything's been working great.
I've now come across the need to use this custom cell in multiple view controllers. I was hoping that I could define a protocol (e.g. CustomCellOwner), which multiple view controllers could implement. The protocol would simply define the IBOutlet used to reference the cell when instantiated.
So ideally, I would like to set "file's owner" to:
id <CustomCellOwner>
in Interface Builder.
However, Interface Builder only seems to allow you to set file's owner to a known class, not to an id implementing a protocol?
Is there any way to do this? Or, a simpler way to approach this problem?
Thanks!
This isn't the solution you're asking for, but you could make a UIViewController subclass that you subclass for each view controller that needs to use your nib. Something like:
#interface CustomCellOwnerViewController : UIViewController
#property (nonatomic, strong) IBOutlet UIButton *someButton;
-(IBAction)doSomething;
#end
And then use that as the base class for each:
#interface FirstView : CustomCellOwnerViewController
Then you could simply set File's Owner to CustomCellOwnerViewController with no problems.
Just an idea.
I ran into this today and didn't find a good solution. I did however hack it so that it seems to work ok. It definitely feels like a hack though.
First I created a "fakeOwner" class like this:
#interface fakeOwner : NSObject
#property (nonatomic, assign) IBOutlet MyBaseCell* itemTableCell;
#end
#implementation fakeOwner
#synthesize itemTableCell;
#end
I then set the object's owner in the XIB as fakeOwner and connected the outlet. Then for each controller that wants to use these cells I add the same property and create the class like this:
[[NSBundle mainBundle] loadNibNamed:#"MyBaseCell" owner:self options:nil];
MyBaseCell* itemCell = self.itemTableCell;
self.itemTableCell = nil;
Since the fakeOwner and my controller have the same IBOutlet, loading the cell with the controller as the owner causes the connection to happen even though that isn't what is explicitly set in the XIB.
Not 100% if the memory management is right currently (I think it's ok), but other than that it seems to work great. I would love to see a better way of doing this though.
Making a fake owner will work; however, such a solution may be fragile and inextensible. In a sense, the cell owns itself, but even that is technically incorrect. The truth is that UITableViewCells do not have owners.
The proper way to implement a custom table view cells is to first create a custom subclass of UITableViewCell. In this class you will define all of the IBOutlets and such for the cell. Here is a sample of a header file:
#interface RBPersonCell : UITableViewCell
#property (nonatomic, strong) IBOutlet UILabel * nameLabel;
#property (nonatomic, strong) IBOutlet UILabel * ageLabel;
- (void)setupWithPerson:(Person *)person;
#end
From there, I have a convenience method that creates the cell from the nib, if necessary:
+ (id)cellForTableView:(UITableView *)tableView reuseIdentifier:(NSString *)reuseID fromNib:(UINib *)nib {
if (!reuseID)
reuseID = [self cellIdentifier];
id cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
if (!cell) {
NSArray * nibObjects = [nib instantiateWithOwner:nil options:nil];
// Sanity check.
NSAssert2(([nibObjects count] > 0) &&
[[nibObjects objectAtIndex:0] isKindOfClass:[self class]],
#"Nib '%#' does not appear to contain a valid %#",
[self nibName], NSStringFromClass([self class]));
cell = [nibObjects objectAtIndex:0];
}
return cell;
}
This method encapsulates all of the creation code so I never have to see it or rewrite it. It assumes that the custom cell is the first root view in the nib. This is a fairly safe assumption since you should only have the custom cell as a root view.
With all this code in place, you are ready to work in Interface Builder. You first need to set the custom class in the identity inspect. Next, don't forget to set your cell identifier. For convenience, it's best to use the name of the custom class. When you drag your connections, rather than drag them to File's Owner, drag your connections to the custom cell itself.
Most of what I have learned about custom table view cells comes from iOS Recipes recipes 15-16. Here is a free extract directly from The Pragmatic Bookshelf. You can check out that book for more details.
EDIT:
I finally got around to open sourcing my RBSmartTableViewCell class. You can find it on my GitHub. You should find this class more useful than the code directly from iOS Recipes, since my class treats all cells the same, regardless of whether they are constructed using XIBs, UIStoryboard, or code. This repo also includes working samples.
In iOS 5.0 there is now the registerNib:forCellReuseIdentifier: method on UITableView which I believe attempts to solve a similar problem.
From the documentation:
When you register a nib object with the table view and later call the dequeueReusableCellWithIdentifier: method, passing in the registered identifier, the table view instantiates the cell from the nib object if it is not already in the reuse queue.
This could be an alternative approach depending on your requirements.
Another option might be to create a lightweight 'factory' object that handles the creation of the cells for you. This object would be the FilesOwner in interface builder, with the rootObject outlet set appropriately.
#interface NibLoader : NSObject
#property (nonatomic, strong) UINib * nib;
#property (nonatomic, strong) IBOutlet id rootObject;
- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundleOrNil;
- (id)instantiateRootObject;
#end
#implementation NibLoader
#synthesize nib, rootObject;
- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundleOrNil {
self = [super init];
if (self) {
self.nib = [UINib nibWithNibName:name bundle:bundleOrNil];
}
return self;
}
- (id)instantiateRootObject {
self.rootObject = nil;
[self.nib instantiateWithOwner:self options:nil];
NSAssert(self.rootObject != nil, #"NibLoader: Nib did not set rootObject.");
return self.rootObject;
}
#end
Then in the view controllers:
NibLoader *customCellLoader = [[NibLoader alloc] initWithNibName:#"CustomCell" bundle:nil];
self.customCell = customCellLoader.instantiateRootObject;
I prefer explicitly setting the root object instead of searching through the array returned from instantiateWithOwner:options: because I know the position of the objects in this array has changed in the past.
I'm coding an iPhone app.
Instead of writing all the code here, this is basicly what I want to do:
testViewController.m:
- (void)viewDidLoad { label.text=#"text"; }
This works.
Now I want to change the label text from the testAppDelegate file.
- (void)applicationDidBecomeActive:(UIApplication *)application {
testViewController *tvc=[[testViewController alloc] init];
tvc.label.text=#"another text";
[tvc release];
}
This isn't working!
How can I do this?
Thanks for all answers :)
In applicationDidBecomeActive you are creating a new instance of the view controller (this is what alloc / init) setting the label text in your new instance and then releasing it.
You need to be referring to the actual instance of your view controller that is on the screen. This is probably already referred to somewhere in your application delegate - is it the root view controller, for example? When is it created in the first place?
You may need to set a property on your application delegate to hold a reference to this view controller. The code you have is fine except that you are talking to a new controller instead of the one that is presented on the screen - assuming of course that your label is a property on the view controller.
In .h file: define property:
#property (nonatomic, retain) UILabel *label;
In .m file: implement set/get methods for that propery (for example, automatically):
#synthesize label;
Declare testViewController as outlet in testAppDelegate.h, perform its binding in xib, probably MainMenu.xib, now try changing value of its label in -
- (void)applicationDidBecomeActive:(UIApplication *)application
I'm sorry! Should have specified more what I was doing. Now I've learned.
I had a tab-bar view controller template.
I solved this by adding in the delegate .h file:
#class testViewController
#property(nonatomic,retain) UIViewController *testViewcontroller;
and in the .m file:
#synthesize testViewController=_tvc;
- (void)applicationDidBecomeActive:(UIApplication *)application
{
_tvc.label.text=#"another text";
}
And of course imported the testViewController.h and declearing the methods there too.
Thanks for all your responses :)
I have created a UIViewController class called MyViewController with a UIImageView in its XIB file. I then import this class into another class. I make an instance of the class and I change the image in the UIImageView using code:
myViewController.myImageView.image = [UIImage imageNamed:#"myImage.png"];
This works swimmingly. My app is essentially an image viewer. I wanted to cache next and previous images by preloading a subview with an image. When I place myViewController into a variable like this:
UIViewController *pager = myViewController;
And attempt to use the variable to set the image for the UIImageView like this:
pager.myImageView.image = [UIImage imageNamed:#"myImage.png"];
I get an "error: request for member 'myImageView' in something not a structure or union". I've tried doing it using square brackets:
[[[pager myImageView] setImage:[UIImage imageNamed:#"myImage.png"]];
I get "warning 'UIViewController' may not respond to '-myImageView' ". How do I access the hierarchy to get to myImageView? I've used a #class in the header and a #import and I've synthesized the class instance. The only solution I have so far is a hack, by doing this:
UIImageView *pager = myViewController.myImageView;
pager.image = [UIImage imageNamed:#"myImage.png"];
Which doubles the amount of variables I have, one for the UIImageView variable, and one for the UIViewController variable. Thanks in advance to anyone that can help out.
quick answer:
You'll need to change the line:
UIViewController *pager = myViewController;
to:
MyViewController *pager = myViewController;
To be able to access the custom properties you added in your MyViewController class.
explanation:
Your MyViewController class probably looks something like this (simplified):
// MyViewController.h:
#interface MyViewController : UIViewController
{
UIImageView * myImageView;
}
#property (readonly) UIImageView * myImageView;
#end
// MyViewController.m:
#implementation MyViewController
#synthesize myImageView;
#end
You have extended the UIViewController class by adding a member variable and a property (myImageView) to the original class.
When you use a pointer to the base class (UIViewController) to access your variable, you are telling the compiler to treat that variable as if it was a plain old UIViewController, and a plain old UIViewController has no concept of a myImageView. This is what causes the compiler errors.