Alloc/init in custom UITableViewCell - objective-c

I'm using DYRateView to display a star rating view in a custom UITableViewCell class. An instance of DYRateView (subclass of UIView) is allocated and initialized in my `layoutSubviews' method.
Is it a bad practice to alloc/init the view in `layoutSubviews'? When the cell is reused, does it alloc/init in every cell or reuse the view?
.h
#property (retain, nonatomic) IBOutlet DYRateView *rateView;
.m
- (void)layoutSubviews
{
[super layoutSubviews];
rateView = [[DYRateView alloc] initWithFrame:CGRectMake(100, 60, 160, 20) fullStar:[UIImage imageNamed:#"StarFullLarge.png"] emptyStar:[UIImage imageNamed:#"StarEmptyLarge.png"]];
NSLog(#"created");
rateView.padding = 10;
rateView.alignment = RateViewAlignmentCenter;
rateView.editable = YES;
rateView.delegate = self;
rateView.rate = _rating;
[self.contentView addSubview:rateView];
}

It is ok to allocate it in layoutSubviews, however you are doing it every time layoutSubviews is called, and it will be called more frequently than just after an allocation. So, the way you have it now will leak memory badly (unless you are using ARC). Furthermore, you will be constantly adding new rateView objects to your contentView on every invocation, with rather undesirable consequences.
Since the subview is owned by the cell view, if the cell view is reused, it too will be reused.
Instead, check if the subview exists or not, and allocate accordingly.
- (void)layoutSubviews
{
[super layoutSubviews];
if ( rateView == nil ) {
rateView = [[DYRateView alloc] initWithFrame:CGRectMake(...) fullStar:[UIImage imageNamed:#"..."] emptyStar:[UIImage imageNamed:#"..."]];
// rest of rateView set up here
[self.contentView addSubview:rateView];
}
}

I believe it will just reuse it. You can test it yourself
NSLog(#"Layout subviews, retain count is %d", rateView.retainCount);
Although, why don't you do this in the init method of the custom cell? And just set the frame in layoutSubviews? It looks cleaner.

Related

Using a UICollectionView over an SKScene

I have an app with several SKScenes. To keep it snappy, I have a single UIViewController, that handles 1 SKView.
I'm trying to add a UICollectionView to one of the SKScenes. However the problem comes when I try to set the delegate for the collection view to the SKScene initialising it.
I initialise it here:
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
//Initialise collectionView
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height) collectionViewLayout:layout];
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView setBackgroundColor:[UIColor redColor]];
}
return self;
}
After initialising, I add the collection view as a subview of the UIViewController's view by calling:
- (void)didMoveToView:(SKView *)view
{
[self.view addSubview:_collectionView];
}
But the delegate or datasource methods aren't called. I've set up the header:
#interface BrowseScene : SKScene <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
I'm guessing that despite setting self as the delegate, once the collection view is added, it is added as a subview of the UIViewController's SKView, and therefore has no reference to the SKScene that initialised it. I tried calling:
_collectionView.delegate = self.view.scene;
But I get a compiler error saying SKScene is an incompatible type for UICollectionViewDelegate.
So my question is, how best to approach this?
It turns out, the problem lay in how I was initialising the UICollectionView, not where. And it's perfectly ok to set an SKScene as the delegate class.
The main reason the delegates weren't being called was because I'd initialised the collectionView with an empty layout object (as UICollectionViewLayout is just an abstract class). I initialised with a standard UICollectionViewFlowLayout instead, and the methods were then called when the class initialised.
I was also missing a call to
[_collectionView registerClass:[collectionViewCell class] forCellWithReuseIdentifier:#"collectionViewCell"];
(In the complete code, I had calls to reloadData, so that wasn't the problem here).

Memory keep increasing even though I set it to nil (screenshot enclosed)

In my ParentViewController I type this:
- (void)updateView:(NSInteger)_index article:(AObject *)aObject {
UIView *aUIView = [someUIViews objectAtIndex:_index];
// clear view
for (UIView *view in aUIView.subviews) {
[view removeFromSuperview];
}
// create view
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;
}
In ChildViewController I type this:
.h file
#interface ChildViewController : UIViewController <UIWebViewDelegate> {
AObject *aObject;
UIWebView *webView;
}
.m file
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:(BOOL)animated];
webView.delegate = nil;
webView = nil;
aObject = nil;
}
Remark:
I have to use addChildViewController because I use UIWebViewDelegate in ChildViewController. If I do not use addChildViewController, I cannot retain the ViewController and functions of UIWebViewDelegate cannot be called.
I use ARC
Program Objective:
There is a scrolling view implementing by UIScrollView and everytime I scroll the screen to another page, function updateView will be called.
After I scroll to another page, the previous page will be disappeared so viewWillDisappear in ChildViewController will be called.
Screenshot:
http://postimage.org/image/mzmksm9d5/
I cannot post image directly because of low reputation here.
Based on the figure, ParentViewController is called at about the 4th second. Then the memory keep increasing rapidly without objects released. At about the 30th second, ParentViewController is gone away by clicking the back button on the navigation bar. Memory is dropped a bit but still somethings are remaining.
Goal:
What I wish to do is that the object of ChildViewController can be destroyed when viewWillDisappear is called and it is no longer used. So I expect the memory would always drop after it increased a bit. Otherwise, the memory will increase proportional to scrolling times and crash until memory is fully used.
Thanks in advance for any help you are able to provide.
Not sure if this will solve the problem, but try removing cvc.view explicitly from parent view.
Also, you are creating ChildViewController, but you are not releasing it. By calling init you increased its retain count, but you are not decreasing it anywhere. Like this (if you do not have some more code), your ChildViewController will not be released since retain count will never be 0 again.
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;

Forcing an object to deallocate under ARC

I'm working on an iPad photo collage app that draws perhaps hundreds of UIImageViews on the screen at once.
There is a button that lets the user "re-create", which is suppose to run a for loop to [photo removeFromSuperview] on all photos and then initialize a new batch, in that order.
I'm using ARC, and my console tells me that my Photo's dealloc method isn't being called until AFTER the next batch has been drawn, meaning I'm running into memory issues, even though I'm trying to remove the first set before adding the next set.
Is there a way to either 1) wait until all the photos have been properly dealloc'd or 2) force all the photos to dealloc immediately under ARC?
You are probably putting your image views in an autorelease pool without realizing it. You may be able to fix this by wrapping your own autorelease pool around your for-loop.
For example, I made a very simple test project with one image view and one button under my top-level view. When I tap the button, it removes the image view and creates a new one. It removes the image view by looping over the top-level view's subviews. Here's the code:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initImageView];
}
- (IBAction)redoWasTapped:(id)sender {
[self destroyImageView];
[self initImageView];
}
- (void)destroyImageView {
for (UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
[subview removeFromSuperview];
}
}
}
- (void)initImageView {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"picture.jpg"]];
imageView.frame = CGRectInset(self.view.bounds, 100, 100);
[self.view addSubview:imageView];
}
#end
When I ran this under the Allocations instrument with “Record reference counts” enabled, I saw that each removed image view was not deallocated during destroyImageView. Instead, it was deallocated later when the run loop called -[NSAutoreleasePool release].
Then I changed destroyImageView to manage its own autorelease pool:
- (void)destroyImageView {
#autoreleasepool {
for (UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
[subview removeFromSuperview];
}
}
}
}
When I ran it again under Instruments, I saw that each removed image view was deallocated during destroyImageView, at the end of the #autoreleasepool block.
ARC deallocs any object to which there are no more strong references. So to dealloc something, simply set all the variables pointing to it to nil and make sure the object is not involved in any circular reference.

Working on custom component: subclass UIView or UIViewController?

I'm working on a custom implementation of UISegmentedControl.
I'd like to create a component that able to receive config data and from which obtain a custom View similar to UISegmentedControl.
I started subclassing a UIView and i can create a custom UISegmentedControl with this code:
CustomSegment *segment = [[CustomSegment alloc]
initWithTitles:[NSArray arrayWithObjects:#"one",#"two",nil]];
[self.window addSubview:segment];
But now i'd like to improve my class and add some more customizable parameters to it.
For example i'd like add a custom separators, define the button fonts and so on... here my doubt:
Is it better to work on a UIView subClass or you suggest me to subclass a UIViewController, where i can manage View hierarchy in method like -(void)loadView and -(void)viewDidLoad ?
In a simple UIView subclass, when i launch the custom init method, i setup immediately subviews... while using a UIViewController i can call custom init and define how my subview is builded into -(void)loadView.
Don't use an UIViewController, just extend the UIView class like you did and keep extending its functionality.
Remember to save a pointer to each subview you add (i.e. buttons) in order to be able to access them later.
Define custom setters, for example, a custom setter for changing a button label title would be:
- (void) setButton1Title:(NSString*)str forState:(UIControlState)state{
//You can add some control here
if ([str length] > 20) return;
[_button1 setTitle:str forState:state]; //_button1 is my reference to the button
}
And so on. Don't provide direct access to your subviews, use methods instead.
Also, you can use "layoutSubviews" method to define how your views are going to be displayed in your custom view.
Hope it helps you.
Edit: In your case, I don't see why using lauoutSubviews method but I want to show you what I was trying to say.
Lets say that for example I need to create an UIView class to represent a "Contact" object in my application.
This is what I would do:
#interface ContactView : UIView{
UILabel* _nameLabel;
UILabel* _ageLabel;
Contact* _contact;
}
#property (retain) Contact* contact;
#end
#implementation ContactView
#synthetize contact = _contact;
-(id)initWithContact:(Contact*)c{
self = [super init];
if (self) {
_nameLabel = [[UILabel alloc] init];
_nameLabel.frame = CGRectZero;
[self addSubview:_nameLabel];
[_nameLabel release];
_ageLabel = [[UILabel alloc] init];
_ageLabel.frame = CGRectZero;
[self addSubview:_ageLabel];
[_ageLabel release];
self.contact = c;
}
}
- (void) layoutSubviews{
[super layoutSubviews];
_nameLabel.frame = CGRectMake(0.0f, 0.0f, 200.0f, 25.0f);
_ageLabel.frame = CGRectMake(0.0f, 25.0f, 200.0f, 25.0f);
if (self.contact){
_nameLabel.text = self.contact.name;
_ageLabel.text = self.contact.age;
}else{
_nameLabel.text = #"Unavailable";
_ageLabel.text = #"Unavailable";
}
}
- (void) setContact:(Contact*)c{
self.contact = c;
[self layoutSubviews];
}
#end
Check out how the "layoutSubiews" is used to set the correct frame and data to the labels.
Usually, I use it a lot when creating custom UITableViewCells where you have to reuse the view.
Let me know if I'm being confusing.

Setting a ViewController's properties after instantiation

I'm creating an instance of a viewController, and then trying to set the text on of it's properties, a UILabel.
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
NSString *newText = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign.text = newText;
NSLog(#" the boyviewcontroller.sign.text is now set to: %#", boyViewController.sign.text);
[newText release];
I tried this, but it didn't work...
So I tried the following:
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
UILabel *newUILabel = [[UILabel alloc] init];
newUILabel.text = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign = newUILabel;
NSLog(#" the boyviewcontroller.sign.text is now set to: %#", newUILabel.text);
[newUILabel release];
But no avail..
I'm not sure why I can't set the text property of the UILabel "sign" in boyViewController..
The problem here is that the initializer does not actually load the nib file into memory. Instead, loading the nib is delayed until your application requests the view controller's view property. As such, your controller's sign property is null when you access it.
Manually requesting the controller's view property would make your example work...
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
[boyViewController view]; // !!!: Calling [... view] here forces the nib to load.
NSString *newText = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign.text = newText;
// and so on...
However, I'd guess that what you're really trying to do is create and configure your view controller before setting it free to do it's own thing. (Perhaps to display it modally, say.) Calling [... view] manually is not going to be a long-term solution.
Better is to set a separate property on your view controller for the label text and then implement viewDidLoad to assign it to the label:
#interface BoyViewController : UIViewController {
IBOutlet UILabel *label;
NSString *labelText;
}
#property(nonatomic, copy)NSString *labelText;
#end
#implementation
#synthesize labelText;
- (void)viewDidLoad
{
[label setText:[self labelText]];
}
// and so on...
#end
This has the added benefit of your label text being reset in case the view is purged during a low memory event.
Did you bind your outlets at Interface Builder?
It seems that you need to bind sign outlet of the first example into Interface Builder in order to actually set that text to whatever you want.
Once you bind your outlet to the actual UI component at Interface Builder, then you should be able to do something like:
NSString *newText = [astrology getSignWithMonth:month withDay:day];
[[boyViewController sign] setText:newText];
This is what you need to know about binding.
Your second example does not make sense at all to me.