Maintain a Link to Subviews in View Controller - objective-c

Here's my situation: I have a UIViewController that manages a hierarchy of subviews, perhaps as shown below:
This view is built from a .xib. I would like to be able to maintain access to each subview of topView – that is, I want a pointer to each so that I can, for example, say something like:
[button1 setText:#"Hello!"];
Usually, to do this, I wire up each element to which I would like access using Interface Builder, resulting in a header that looks something like this:
#interface MyViewController : UIViewController
{
__weak IBOutlet UIView *view;
__weak IBOutlet UILabel *label;
__weak IBOutlet UIButton *button1;
__weak IBOutlet UIButton *button2;
}
#end
These instance variables are __weak, which is fine, since by the time my view controller "gets" them, they're already owned by my view controller's root view (which, confusingly, I labeled "topView" in my quick diagram). In fact, I want these references to be weak – when my root view is released, so too should all its subviews be released. Great.
But let's say I want to create a new element of the UI, maybe a custom button, entirely in code. I'll call this element CustomViewClass, which will be subclassed from UIView. The instance of CustomViewClass that I will create will be called customButton. As with the other subviews of my view, I would like "access" to customButton so that I can interact with it. I know, however, that like any other subview, customButton will be owned by its superview, and that's how it should be – again, I want it to be released whenever my view is released. This makes me think that I should declare this view as a __weak instance variable or property of my view controller. Let's do that:
#interface MyViewController : UIViewController
{
__weak IBOutlet UIView *view;
__weak IBOutlet UILabel *label;
__weak IBOutlet UIButton *button1;
__weak IBOutlet UIButton *button2;
__weak CustomViewClass *customButton;
}
#end
Then, in my implementation:
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
customButton = [[CustomViewClass alloc] init];
[[self view] addSubview:customButton];
}
#end
As you've probably already realized, this won't work, and the compiler will throw a warning to boot. Something like:
Assigning retained object to weak variable; object
will be released after assignment
I currently dodge this sort of warning with some very poor style:
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
CustomViewClass *customButtonLocal = [[CustomViewClass alloc] init];
[[self view] addSubview:customButtonLocal];
customButton = customButtonLocal;
}
#end
That way, I get what I want:
An instance of CustomViewClass on the screen...
...with exactly one owner, its superview...
...and no lingering variables (customButtonLocal is released immediately after the block ends).
But this can't be the "right" way to do this. So, finally, my question:
How should I be allocating and instantiating this programmatically-created __weak variable without using this middle-man workaround?

Make CustomViewClass *customButton a strong reference.
The reason why you usually declare variables for your subviews as __weak IBOutlet is that the existence of these links does not imply ownership. Subviews are owned by the object instantiated from a NIB/Storyboard. You own that object directly, and you also own its dependent objects indirectly.
The customButton is a different story: you create it programmatically, so your NIB/Storyboard does not own it. Therefore, you should make the reference to it __strong (which is the default when there are no ARC modifiers).

Related

IBOutlet is nil after initWithCoder is called

Simple problem, I have defined a UIImageView, called bigImageView in a UIViewController using the storyboard,
It's declared in the h file of that UIViewController as follows:
#property (retain, nonatomic) IBOutlet UIImageView *bigImageView;
on my appDelegate I init the UIViewController as follows:
imageViewController = [storyboard instantiateViewControllerWithIdentifier:#"chosenImageController"];
this calls initWithCoder on my UIViewController m file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Custom initialization
}
return self;
}
This function is only called once so there's no double init.
However, later, when I check my bigImageView pointer, it's still nil.
Isn't the init supposed to allocate memory to it?
I think that's why when I try to set this UIImageview to hold a UIImage it doesn't display the image
Thanks
It's all working how it's meant to. First every object in the nib/storyboard gets alloc/init called on them, then all the connections are made, and then viewDidLoad is called.
You need to wait for - (void)viewDidLoad to be called on your controller, and then bigImageView should be set. If it's not set then you did something wrong in the storyboard.
init methods are not responsible for allocating any memory. All memory is allocated by the alloc method which is always called before init. Alloc will fill all your instance variables with nil/NULL/0 values, and then init gives the chance to assign initial values to each one (based on the contents of the NSCoder object usually, but it's up to you to decide what should be done).
For IB outlets however, those are setup by the nib loading process after init.
EDIT:
// ViewControllerA.m:
imageViewController = [storyboard instantiateViewControllerWithIdentifier:#"chosenImageController"];
imageViewController.image = imageToShow;
// ViewControllerB.h
#property (retain) NSImage *image;
#property (retain, nonatomic) IBOutlet UIImageView *bigImageView;
// ViewControllerB.m
- (void)viewDidLoad
{
self.bigImageView.image = self.image;
[super viewDidLoad];
}
You don't need to define initWithCoder, since you have no custom logic in there. I would delete that boilerplate code.
Here is what I would check:
In the storyboard, ensure that the class of the view controller is set properly.
Ensure that the outlet is hooked up properly in the storyboard by looking for a circle near your #property. It should be a filled in circle, not an outline of a circle.
Make sure you are reading the value only after viewDidLoad is called. Apple's only guarantee is that the outlet is set after this method call.
Update: It sounds like you want to access the image view before the view is loaded. There is no way to do this. One hack is to call viewController.view which will force the view to load, but there are many reasons why you should not do this.
A better approach would be to implement properties on your view controller which work for both when the view is not loaded and when the view is loaded. You can see an example of an elegant solution in this question. Notice how if the view is loaded, the photographerLabel will get set via the didSet method. On the other hand, if the view is not loaded, it will get set via the viewDidLoad method. For an Objective-C version of that code or for more details, see the linked video in that question.

Loading XIB file without a UIViewController

I'd like to design a UIView and some sub-views (UIWebView, UIToolbar, some UIBarButtonItems, a progress indicator and so-forth) using the Interface Builder, but I think it's unnecessary to do this traditionally, by using a UIViewController, using presentViewController:animated etc.
So, I created a custom class, with the .h file code as follows:
#interface FileInteractionManager : NSObject {
}
#property (nonatomic, retain) IBOutlet UIView *fileView;
#property (nonatomic, retain) IBOutlet UIWebView *fileWebView;
#property (nonatomic, retain) IBOutlet UIBarButtonItem *printButton;
#property (nonatomic, retain) IBOutlet UIBarButtonItem *optionsButton;
#property (nonatomic, retain) IBOutlet UIBarButtonItem *doneButton;
My .m file is as follows:
#implementation FileInteractionManager
#synthesize fileView, fileWebView, doneButton, optionsButton, printButton;
-(id)init {
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"FileInteractionView" owner:self options:nil];
NSLog(#"Load success!");
return self;
}
Finally, I create a stand-alone xib file named 'FileInteractionView.xib', change the file's owner to the custom class I created above, and wire up the IBOutlets.
When I call the init method on my class, I can see in the debugger that all my IBOutlet objects are instantiated properly.
My questions are:
Is the loadNibNamed:owner:options: method the right way to load my stand-alone .xib file? I don't like the fact that this method returns an array I have no use for (the top-level object returned matches my variable fileView, but I've already linked them through the Interface Builder).
Is my general approach correct in solving my problem? I carried out the above steps because I wanted a simple UIView object that I could add to my existing UIViewController, rather than present and dismiss a whole new UIViewController.
I use a little different approach. I create a subclass of UIView (MyCustomView i.e.) then the xib with the UI of the view and change the (main) view class the the one just defined. In the xib then you can link the outlet to the custom view itself (not the file owner).
Finally in the class definition I create a function like this:
+ (id) newFromNib
{
NSArray *nibArray = [[UINib nibWithNibName:NSStringFromClass([self class]) bundle:nil] instantiateWithOwner:nil options:nil];
return nibArray[0];
}
Just a couple of notes:
1) this's a class method, you can use "self" just for stuff like "NSStringFromClass([self class])" but the real object is the variable returned
2) this example suppose the xib have the same name of the class (via NSStringFromClass([self class]) so I can copy-paste it without changing anything ;) ) and that your view is the first one defined in the xib (the standard). If you store more than a "main" view inside one xib pick the right element.
so where I need MyCustomView I do something like:
MyCustomView* mycv = [MyCustomView newFromNib];
then set frame/center and add to superview...
I think this way is pretty usefull if you have a "library" of complex UI elements and want to design them via xib then add when needed.
il Malvagio Dottor Prosciutto answer is nice. Here is a possible alternative.
Load nib in NS_DESIGNATED_INITIALIZER and become owner of subview
If we accept the xib to only hold a subview instead of the view itself, then we can load the subview in initWithFrame: and keep an ownership construction in xib.
#interface MyCustomView ()
#property (strong, nonatomic) IBOutlet UIView *subview;
#end
#implementation MyCustomView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
[self addSubview:self.subview];
return self;
}
#end

IBOutlet in ARC releases and sets to nil. How to avoid this? objective c

I'm new to ARC and Storyboarding. I've set IBOutlet to UITableView from my UIViewController.
After some time my IBOutlet sets to nil and I can't reload it from other classes.
Here is my dataTable IBOutlet:
#property (weak, nonatomic) IBOutlet UITableView *dataTable;
At the start dataTable is not nil, but not when I try to access it from another class (via appDelegate). How to solve this problem?
UPDATE
I call this method from my UIViewController
[appDelegate.myClass loginWithUserName:loginField.text andPassword:pwdField.text];
When it's done, and I have data to show, I call this code from loginWithUserName method:
MyViewController *controller = [[AppDelegate sharedStoryboard] instantiateViewControllerWithIdentifier:#"MyViewController"];
[controller audioLoaded];
And here is that method in my UIViewController, wich reloads data
-(void) audioLoaded
{
//it is nil here
[self.dataTable reloadData];
}
Set the property to strong retain the object:
#property (strong, nonatomic) IBOutlet UITableView *dataTable;
It's not good practice to access a UITableView from another view controller though..
EDIT:
You shoul reconsider the whole approach, by moving that logic from your appdelegate to a dedicated class that will perform the login. You can create a simple protocol that the UIViewController with the table can implement, then, when calling the login method, pass a reference to the current viewcontroller, something like
loginWithUserName:andPassword:andCaller:(id<LoginDelegate>)sender
Where LoginDelegate is something on this line:
#protocol LoginDelegate
- (void)audioLoaded;
#end
In this way you can just call
[sender audioLoaded];

Error when ViewController is implementing UITextFieldDelegate

When implementing the UITextFieldDelegate in my ViewController class, the following error is thrown when entering the first character in the text field:
-[MyViewController respondsToSelector:]: message sent to deallocated instance...
So, I tried creating a separate class (inheriting only NSObject) and implementing UITextFieldDelegate. Guess what, it worked perfectly. However, that introduces some other problems as I have to do a lot of ugly cross-class-communication that I'd like to avoid. Here's the relevant parts of my app delegate code:
#interface RMSAppDelegate : NSObject <UIApplicationDelegate,
UITabBarControllerDelegate>
#property (nonatomic, retain) UIViewController* myViewController;
#end
#implementation MyAppDelegate
#synthesize myViewController;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
myViewController = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self.window setRootViewController:myViewController];
[self.window makeKeyAndVisible];
return YES;
}
#end
.. and here's what is being displayed:
#interface MyViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, retain) IBOutlet UITextField* pinTextField;
- (void)viewDidLoad;
#end
#implementation MyViewController
#synthesize pinTextField;
- (void)viewDidLoad {
// DOES NOT WORK (WHY?)
//[pinTextField setDelegate:self];
// WORKS, BUT I'D LIKE TO AVOID
[pinTextField setDelegate:[[[MyTextFieldDelegate alloc] init] autorelease];
[pinTextField becomeFirstResponder];
[super viewDidLoad];
}
#end
And please, if you see any code (even off topic) that I could be doing better, leave a comment.
Since you asked for off-topic code comments: You forget to call [super viewDidLoad]. You also don't need to redeclare the prototype in order to override it. And the #synthesize textFieldDelegate is not valid, as you have no property in the class named textFieldDelegate. And your dealloc method is releasing an ivar named tfd which doesn't seem to actually exist in the class.
Your real problem is that you are not properly retaining the view controller at whatever point you allocate it. It may be that the view controller is being instantiated in a nib and associated with an ivar rather than a property declared retain, or is not being associated with anything. Or it could be that you are allocating it in code, adding its view as a subview of something, and then releasing it without ever retaining the view controller itself. Or it could just be that you are just releasing it when you shouldn't.
Your other class works specifically because you are leaking the object, so it never gets deallocated. The better solution, were you to go with this method, would be to store the object in an ivar when you allocate it and then release it (and set the ivar to nil) in both dealloc and viewDidUnload.
Okay, I finally solved this on my own. I have not changed the code. My NIB (.xib) was the culprit!
I thought that nested UIViewControllers was OK, and I still think they are in some cases (and maybe using another programmatic method). Anyway, I was initializing my class MyViewController with a NIB that in the Objects panel had a UIViewController as the first object.
I solved this by having the UIView as the first object in the Objects panel, and setting the File's Owner to be the UIViewController instead.
Correct code, incorrect NIB. Thank you for your help.

release view in viewDidUnload

I know that if I retain an IBOutlet by using property then I have to set it to nil in viewDidUnload but what about others?
For example, i have three subviews view1, view2 and view3, that load from nib and that is the controller's header file
#interface MyViewController : UIViewController {
IBOutlet UIView *view1;
UIView *view2;
//no reference for view3
}
#property (nonatomic, retain) IBOutlet UIView *view2; //property view2 is an IBOutlet!!
#end
and method viewDidUnload
- (void)viewDidUnload {
self.view2 = nil;
//[view1 release];
//view1 = nil;
[super viewDidUnload];
}
do I have to release view1 and set it to nil? or UIViewController will set it to nil for me?
what about view3?
also do I have to release view1 in dealloc?
edit:
I think many people does not understand my question
Firstly, view1 is an IBOutlet which declared as an ivar and assign an ivar will not retain it. I know that UIViewController definitely will retain it but do i have to release it or UIViewController will release it for me? If UIViewController will release it then there is no point that i have to release it again.
Secondly, view2 is also an IBOutlet although it is declared as a property not ivar.
#property (nonatomic, retain) IBOutlet UIView *view2;
It is a retain property, therefore set it will retain it so I know that I have to set it to nil in order to release it. I have no problem about it.
For view3, there is no reference for it, therefore I am assuming I don't have to do anything about it. I also assuming there is no need to make a reference for every object in nib.
All outlets are retained by default even if they don't have a property declared for them. So you will need to release them. If you go on to declare an assigned property as an outlet, then you don't need to release but you can't rely on it either as you are not an owner.
So you need to release both view1 and view2 as they are declared as outlets. view3 is a variable that doesn't exist and hence needn't be worried about.
When a nib is loaded, all its objects are automatically instantiated and retained.
The file's owner of your nib file is then the owner of your UIView.
If you use UIView *view2 you can't connect them using interface builder.
So that doesn't make really sense to me.
You have to release in dealloc as well.
- (void)viewDidUnload {
self.view1 = nil;
self.view2 = nil;
[super viewDidUnload];
}
- (void)dealloc {
[self.view1 release];
self.view1 = nil;
[self.view2 release];
self.view2 = nil;
}