iosobjective-cuitableviewpropertiesinitwithcoder

set property to custom subview init through IB (for uitableviewcell templating)


I want to create a uitableview mixing different type of items to display. For each item, I want to display some generic infos (always same type and style) and some specifics (different type and style according to item type). = I need some kind of "template manager".

I store and retrieve items using coredata (Item is an entity), hook data to tableview through a nsfetchedresultscontroller, and item type is stored as an attribute called item_nature.

I'm using storyboard, so first I thought about using different prototype cells and, in cellForRowAtIndexPath, assigning the needed cell prototype to use according to the item_nature value. Almost worked, but as I'll have around 10 type of different items, managing 10 prototype cells in storyboard isn't very practical. Plus, I need to adapt row height according to content type/lenght, and this approach imply lot of redundant code...

Now I try a second approach but can't make it work. What I have in mind :

1> In storyboard I create only one prototype cell used for every item type. See image bellow, this cell is composed of one image and two labels (blue) + one view (red)

enter image description here

Blue zones would be for "generic infos" (same type, size and style whatever the item, only the content change), and Red zone would be like a container where I adapt a specific template by adding a specific subview according to item_nature

2> In cell custom class I setup IBOutlets for the three labels and the view, and link those outlets with the UIelements in storyboard

MainTLCustomCell.h

#import <UIKit/UIKit.h>
#import "CellTemplateContainer.h"

@interface MainTLCustomCell : UITableViewCell

@property (nonatomic, strong) IBOutlet UIImageView *aboIcon;
@property (nonatomic, strong) IBOutlet UILabel *itemAboName;
@property (nonatomic, strong) IBOutlet UILabel *itemDate;
@property (nonatomic, strong) IBOutlet CellTemplateContainer *itemTemplate;

@end

3> I create one dedicated xib file per item template, e.g. BasicArticleItemTemplate.xib, ArticleWithPicItemTemplate.xib etc Those xib should be loaded in the view container (still the red in the cell) according to the item_nature of the item

4> I create a subclass of UIView called CellTemplateContainer with a property "templateType", and assign this class to my "container" view in the prototype cell. As this view is instantiated by the storyboard, in -initWithCoder (I also tried in -awakeFromNib), I put a condition to add subviews according to the value of templateType property (value i'll try to send from the tableViewController)

CellTemplateContainer.h

@interface CellTemplateContainer : UIView {
    NSNumber *templateType;
    NSString *stringTemplateType;
}

// We'll choose the correct template to load according to item_nature passed to this property
@property (nonatomic, strong)NSNumber *templateType;
@property (nonatomic, strong)NSString *stringTemplateType;

@end

CellTemplateContainer.m

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if ((self = [super initWithCoder:aDecoder]))
    {
        NSLog(@"initwithcoder /// self.templateType = %i", [self.templateType intValue]);       

        if (templateType == [NSNumber numberWithInt:1]) {
            [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"BasicArticleItemTemplate" owner:self options:nil] objectAtIndex:0]];
        } else if (self.templateType == [NSNumber numberWithInt:2]) {
            [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ArticleWithPicItemTemplate" owner:self options:nil] objectAtIndex:0]];
        }
    }
    return self;
}

5> In the tableViewClass, in -cellForRowAtIndexPath, I set the values for the three generics labels, and try to set the value for the property "templateType" of the view container :

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];

    static NSString *CellIdentifier = @"kopTLCell";

    MainTLCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Abo icon & displayName
    UIImage *aboIcon = [UIImage imageNamed:item.estProduitPar.abo_icon];
    cell.aboIcon.image = aboIcon;
    cell.itemAboName.text = item.estProduitPar.abo_displayName;

    // Date
    _dateFormatter = [[NSDateFormatter alloc] init];
    //[_dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [_dateFormatter setTimeStyle:NSDateFormatterShortStyle];
    [_dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    [_dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"]];
    [_dateFormatter setDoesRelativeDateFormatting:YES];
    NSString *stringFromDate = [_dateFormatter stringFromDate:item.date];
    cell.itemDate.text = stringFromDate;

    cell.itemTemplate.templateType = item.item_nature;
    cell.itemTemplate.stringTemplateType = @"hello";

    return cell;
}

Adding the subview in -initWithCoder works well as long as I don't make a condition about templateType value.

My problem is here :

cell.itemTemplate.templateType = item.item_nature;

The itemTemplate view never get this value (always 0, so I assume to be nil). I'm still confused with object vs instance and clearly don't master the order in which init method are called. But I thought that I would be able to "send" a value to the container view to take into account before or during its init... I was also hoping that with this approach I could easily add a new item_nature and the associated template to the model + having a simpler way to manage the code about cell height calculation.

So, Two questions : - What am I doing wrong ? - What do you recommend as a good approach for dealing with cell template management in this kind of project ?

Thanks


Solution

  • By the time you are calling cell.itemTemplate.templateType = item.item_nature;, initWithCoder on your CellTemplateContainer has been and gone.

    Suggest you put your cell setup code in a setter such as:

    -(void)setTemplateType:(NSNumber*) templateType {
        _templateType = templateType;
    
        if ([templateType intValue] == 1) {
            [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"BasicArticleItemTemplate" owner:self options:nil] objectAtIndex:0]];
        } else if ([templateType intValue] == 2) {
            [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ArticleWithPicItemTemplate" owner:self options:nil] objectAtIndex:0]];
        }
    }
    

    Also, you ought to factor out the creation and configuration of your NSDateFormatter out of cellForRowAtIndexPath