How to Unload a sub-view? - objective-c

I've got a UITableView which loads a view when the accessory button is tapped:
MyView *newView = [[MyView alloc] ...];
[self.navigationController pushViewController:newView];
Inside the view, when the user performs a certain action, I return back to the UITableView by doing this:
[self.navigationController popViewControllerAnimated:YES];
This works, but the next time the view is accessed by tapping the accessory button, it retains some of the values that it contained the first time the view used.
Is there a proper way to actually "completely unload" the view so that it's fresh the next time it is displayed?

You should autorelease the newView (which I assume is actually a View Controller, since you're pushing it to a navigation stack).
As it currently stands your view controller is never being deallocated and is being leaked:
It is allocated (retain count 1)
Pushed into a navigation stack which retains the view controller (retain count 2)
It is then popped from the stack, which releases it (retain count 1)
It is never released again after that
As for why you're getting previous values, you must also be pushing the same instance rather than creating a new one each time you push it to the stack - I can't tell since you haven't posted all of the relevant code.

You need to release any retained subviews of the main view on viewDidUnload...
For instance if you have retained UI elements by declaring them as outlets in IB, set them to nil before releasing them on dealloc.
Here's an example:
#interface ...
{
IBOutlet UIButton *button;
IBOutlet UITextLabel *label;
IBOutlet UITableView *tableview;
}
#property (nonatomic, retain) UIButton *button;
...
Then you should set these to nil on viewDidUnload
- (void)viewDidUnload {
self.button = nil;
self.textlabel = nil;
self.tableview = nil;
...
}

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.

UILabel text not changing, however xx.title is working

I have two view controller. In first view controller I have list of names. When I click on that, I want the same name to be displayed in second view controller.
I have below code.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// PropDetailViewController is second view controller
PropDetailViewController *prop = [self.storyboard instantiateViewControllerWithIdentifier:#"prop"];
ListOfProperty *propList = [propListFinal objectAtIndex:indexPath.row];
NSString *myText = [NSString stringWithFormat:#"%#", propList.addressOfFlat];
prop.detailLabel.text = myText;
prop.title = myText;
[self.navigationController pushViewController:prop animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
and in PropDetailViewController, I have #property (retain, nonatomic) IBOutlet UILabel *detailLabel;.
What I was expecting is when I click say Name 1, I will see Name 1 as text in UILabel and on the UINavigationBar too. However I only see Name 1 on navigation bar and not on UILabel.
It is not advisable to access an UIView item at that point in the program flow. When setting the value of prop.detailLabel.text the view may not have been loaded. When the view is loaded later then the UIView is updated with the default settings given in the XIB file in IB.
You should rather set an NSString property, lets say
#property (retain, nonatomic) NSString *propName;
assign it before pushing the view controller as you do. But use this property and not the UILable. And in PropDetailViewController in viewDidLoad do the following:
(void) viewDidLoad {
// call super viewDidLoad and all the works ...
self.detailLabel.text = propName;
}
Instead of viewDidLoad you could use viewWillAppear. Because viewDidLoad COULD be executed already when you assign the property's value.
If you want to be on the save side then invent a new init method where you hand over all the values that you want to be set upfront.
But I never did that in combination with storyboard (where you may use instantiate... rather than init...) and therefore I cannot give any advise out of the top of my head.
Another clean approach would be to stick with the propName property but to implement a custom setter -(void)setPropName:(NSString)propName; where you set the property (probably _propName if you autosynthesize) AND set the UILable text plus setting the UILable text within viewDidLoad.
Try this:
in .h
#property (retain, nonatomic) NSString *detailText;
in PropDetailViewController.m
Change line of code with
NSString *myText = [NSString stringWithFormat:#"%#", propList.addressOfFlat];
prop.detailText = myText;
prop.title = myText;
in ViewDidLoad:
[self.detailLabel setText:self.detailText];

Maintain a Link to Subviews in View Controller

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).

(iOS) How can I Reuse an UIImageView in another View?

I have a view controller with a UIScrollView with a heap of UIImageViews as subviews. There is a segue to another view controller but i want to reuse the UIImages that are already in memory rather than load them again for efficiency's sake. But if I point my new UIImageView to an existing one, nothing shows up.
I had to use an intermediate pointer variable (imageViewPointer) as if I assign it directly in the prepareForSegue method, the NSLog's from each view controller would show different addresses. This workaround NSLogs show the same address but still nothing shows up.
If I use reassign the .image property instead, the image shows up but the images from the first view controller are load asynchronously so the .image property might change.
In the first view controller
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NextViewController *nextViewController = [segue destinationViewController];
nextViewController.imageViewPointer = [[myScrollView subviews] objectAtIndex:myIndex];
NSLog(#"%#", [[myScrollView subviews] objectAtIndex:myIndex]);
}
In the second view controller
#interface nextViewController : UIViewController
#property (retain, nonatomic) IBOutlet UIImageView *imageView;
#property (retain, nonatomic) UIImageView *imageViewPointer;
#implementation nextViewController
#synthesize imageView, imageViewPointer;
{
self.imageView = self.imageViewPointer;
// [self.imageView setNeedsDisplay]; Doesn't help
NSLog(#"IMAGE VIEW POINTER = %#", self.imageViewPointer);
NSLog(#"IMAGE VIEW = %#", self.imageView);
[super viewDidLoad];
}
If you simply assign a view like that, it won't appear in the view hierarchy of your current view controller's view. You'll need to do something like:
[self.view addSubview: imageView];
However, I wouldn't recommend that. It is better to just assign the image of the view to the image of the other view.
self.imageView.image = self.imageViewPointer.image;
Either you reuse the UIImage or the method you are doing is a bit wrong as you are assigning the imageViewPointer to imageView which is(imageViewPointer) not a subview of your view controller's view. So add subview imageViewPointer or imageView after assigning imageViewPointer.

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;
}