3

Long story:

A colleague of mine asked me for a little help. I am a C# developer and he is a iOS developer and reading each others code gives us some good insight some times.

He is writing some function which needs to return an object of basetype UITableViewCell and has an integer as input. The actual return type is a subclass of UITableViewCell. He asked me if I knew a better solution then a simple switch like so:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    switch (row) {
        case 0: {
            NSString *CellIdentifier = @"personCell";

            PersonInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
            if (cell == nil) {
                cell = [[PersonInfoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
            }
            cell.selectionStyle = UITableViewCellSelectionStyleNone;

            return cell;
            break;
        }
        case 1: {
            NSString *CellIdentifier = @"photoCell";

            PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
            if (cell == nil) {
                cell = [[PhotoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
            }
            cell.selectionStyle = UITableViewCellSelectionStyleNone;

            return cell;
            break;
        }
        default:
            return nil; //Don't care about this atm. Not the problem.
            break;
    }
}

My C# implementation would be something as the following:

public UITableViewCell TableViewCellForRowAtIndexPath(UITableView tableView, NSIndexPath indexPath)
{
    switch(indexPath.row)
    {
        case 0:
        return MakeCell<PersonInfoCell>();
        case 1:
            return MakeCell<PhotoCell>();
        default:
            return null; //Still doesn't matter
    }
}


public TCell MakeCell<TCell>() where TCell : UITableViewCell, new()
{
    TCell cell = new TCell();
    cell.selectionStyle = UITableViewCellSelectionStyleNone;

    return cell;
}

public class PersonInfoCell : UITableViewCell
{
    //Dont care about implementation yet....
    //TL;DR
}

public class PhotoCell : UITableViewCell
{
    //Dont care about implementation yet....
    //TL;DR
}

Short story:

Does anyone know a way to convert my C# generic code to objective-c equilivant?


Update 1

Our faulty implementation based on the idea of Nicholas Carey.


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    switch (row) {
        case 0: {
            NSString *CellIdentifier = @"personCell";

            PersonInfoCell *cell = (PersonInfoCell *)[self makeCell:CellIdentifier];
            //Do PersonInfoCell specific stuff with cell
            return cell;
            break;
        }
        case 1: {
            NSString *CellIdentifier = @"photoCell";

            PhotoCell *cell = (PhotoCell *)[self makeCell:CellIdentifier];
            //Do PhotoCell specific stuff with cell
            return cell;
            break;
        }
        default:
            return nil; //Don't care about this atm. Not the problem.
            break;
    }
}

- (id *)makeCell:(NSString *)cellIdentifier 
{
    id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil) {
        cell = [[PhotoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; // <---- how does this method know it is a PhotoCell I want?
               //PhotoCell???
    }
    cell.selectionStyle = UITableViewCellSelectionStyleNone;

    return cell;
}

SynerCoder
  • 12,493
  • 4
  • 47
  • 78

3 Answers3

1

In Objective-C, classes are first class constructs, which can be passed around and used just like any other object.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = nil;
    Class cellClass = nil;
    switch (row) {
        case 0:
            cellIdentifier = @"personCell";
            cellClass = [PersonInfoCell class];
            break;
        case 1:
            cellIdentifier = @"photoCell";
            cellClass = [PhotoCell class];
            break;
        default:
            return nil; //Don't care about this atm. Not the problem.
    }

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil)
        cell = [[cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];

    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    return cell;
}

If you really want to factor out the factory method, you could do something like this:

- (UITableViewCell *)getOrCreateCellForTable:(UITableView *)tableView withIdentifier:(NSString *)cellIdentifier class:(Class)cellClass
{
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  if (cell == nil)
      cell = [[cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];

  cell.selectionStyle = UITableViewCellSelectionStyleNone;
  return cell;
}

If you want to get rid of the paired cellIdentifier and cellClass parameters, one option is to create a defaultIdentifier class method (static method, in C# parlance) on each of cell classes that you use. That way, you can pass just the class into the factory method, and the factory method can query the class for the proper identifier.

Amanda Mitchell
  • 2,665
  • 1
  • 16
  • 23
  • David Mitchell. Thanks! We edited it a little bit and it worked perfectly. See @Apox his answer for the full implementation. – SynerCoder May 22 '13 at 18:55
1

Based on David Mitchell's code we (colleague of Synercoder) came up with this, works perfectly!

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = nil;
    Class cellClass = nil;
    switch (indexPath.row) {
        case 0: {
            cellIdentifier = @"personCell";
            cellClass = [PersonInfoCell class];
            PersonInfoCell *personInfoCell = (PersonInfoCell *)[self getOrCreateCellForTable:tableView withIdentifier:cellIdentifier class:cellClass];
            personInfoCell.delegate = self;
            return personInfoCell;
        }
        case 1: {
            cellIdentifier = @"photoCell";
            cellClass = [LastMeasureMent class];
            LastMeasureMent *measurementCell = (LastMeasureMent *)[self getOrCreateCellForTable:tableView withIdentifier:cellIdentifier class:cellClass];
            measurementCell.selectionStyle = UITableViewCellSelectionStyleBlue;
            return measurementCell;
        }
        default: {
            cellIdentifier = @"photoCell";
            cellClass = [LastMeasureMent class];
            LastMeasureMent *measurementCell = (LastMeasureMent *)[self getOrCreateCellForTable:tableView withIdentifier:cellIdentifier class:cellClass];
            measurementCell.selectionStyle = UITableViewCellSelectionStyleBlue;
            return measurementCell;
        }
    }


}

- (UITableViewCell *)getOrCreateCellForTable:(UITableView *)tableView withIdentifier:(NSString *)cellIdentifier class:(Class)cellClass
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil)
        cell = [[cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];

    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    return cell;
}

Thanks David!

Apox
  • 11
  • 1
0

See this question: Are there strongly-typed collections in Objective-C?

Objective-C is late-bound and dynamically typed, so you don't need generics. You can send any message to an object. What it does with it is up to the object. Early-bound languages with static typing, like C#, Java, C++ need generics. Without them, the problem wouldn't [readily] know what it can or cannot do with the contained objects.

Community
  • 1
  • 1
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • If I create a `MakeCell` method in objective-c and have the return type be `id`. Then in the switch cast it to the right type. Possible... but how does the `MakeCell` method know what to instantiate in that method? How can I pass the method to the `MakeCell` method so it knows to make a `PersonInfoCell` or a `PhotoCell`. – SynerCoder May 22 '13 at 17:50
  • We added what we thought you meant. Could you elaborate what is missing from our implementation? – SynerCoder May 22 '13 at 18:04