2
\$\begingroup\$

I basically want to display JSON data in a TableView. The TableView always displays a List object I came up with. The data structure looks like this:

@interface List ()

@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSArray *entries;
@property (nonatomic) BOOL recursiveListing;

@end

Every List has a name, some entries and the property, whether it should be displayed recursively, if itself is an element of another List.

The JSON data could looks like this:

{
  "name": "Minimal-Example",
  "objectType": "list",
  "recursiveListing": false,
  "entries": [
    "Some",
    "String",
    {
      "name": "Non-Recursive List",
      "objectType": "list",
      "recursiveListing": false,
      "entries": [
        "Non-Recursive Entry",
        "Non-Recursive Entry"
      ]
    },
    "AnotherString",
    {
      "name": "Recursive List",
      "objectType": "list",
      "recursiveListing": true,
      "entries": [
        "Recursive",
        "List",
        "Content"
      ]
    },
    "Basic",
    "Content"
  ]
}

My app basically does the following:

  • Take the JSON file and read it into a List.
  • Display the List the following way:
    1. Every NSString gets its own row.
    2. A non-recursive List gets displayed as single element (just the name).
    3. A recursive List defines a new section with its entries as content (for now there are now List's contained in a recursive List).

The example data will be displayed as here:

enter image description here

Here is the code to handle all of this:

List.h

#import <Foundation/Foundation.h>

@interface List : NSObject

- (id)initFromDictionary:(NSDictionary *)dict;

- (NSInteger)numberOfSectionsToDisplay;
- (NSInteger)numberOfElementsInSection:(NSInteger)section;

- (NSString *)titleForHeaderInSection:(NSInteger)section;
- (NSString *)titleForCellAtRow:(NSInteger)row inSection:(NSInteger)section;

@end

List.m

#import "List.h"
#import "TypeUtility.h"

@interface List ()

@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSArray *entries;
@property (nonatomic) BOOL recursiveListing;

@end

static NSString *DEFAULT_LIST_NAME = @"Not Set";
static BOOL DEFAULT_RECURSIVE_LISTING = NO;

@implementation List

- (id)initFromDictionary:(NSDictionary *)dict {
    self = [super init];

    if (self) {
        id nameEntry = [dict objectForKey:@"name"];
        if ([nameEntry isKindOfClass:[NSString class]]) {
            self.name = nameEntry;
        } else {
            NSLog(@"ListInitializer: Default name used.");
            self.name = DEFAULT_LIST_NAME;
        }

        id recursiveListingEntry = [dict objectForKey:@"recursiveListing"];
        if ([recursiveListingEntry isKindOfClass:[NSNumber class]]) {
            self.recursiveListing = [recursiveListingEntry boolValue];
        } else {
            NSLog(@"ListInitializer: Default recursiveListing used.");
            self.recursiveListing =  DEFAULT_RECURSIVE_LISTING;
        }

        id entriesEntry = [dict objectForKey:@"entries"];
        if ([entriesEntry isKindOfClass:[NSArray class]]) {
            self.entries = entriesEntry;
        } else {
            NSLog(@"ListInitilaizer: Default entries used.");
            self.entries = @[];
        }
    }

    return self;
}

- (NSArray *)sectionsToDisplay {
    NSMutableArray *sections = [[NSMutableArray alloc] init];
    BOOL elementWillStartSection = YES;

    NSInteger entryIndex = 0;
    for (id entry in self.entries) {
        NSNumber *entryIndexAsNSNumber = [NSNumber numberWithLong: entryIndex];
        if ([entry isKindOfClass:[NSString class]] && elementWillStartSection) {
            [sections addObject:@{ @"name" : @" ", @"startIndex": entryIndexAsNSNumber }];
            elementWillStartSection = NO;
        } else if ([entry isKindOfClass:[NSDictionary class]] && [[entry objectForKey:@"recursiveListing"] boolValue]) {
            [sections addObject:@{ @"name" : [entry objectForKey:@"name"], @"startIndex" : entryIndexAsNSNumber }];
            elementWillStartSection = YES;
        }
        entryIndex++;
    }

    return sections;
}

- (NSInteger)numberOfSectionsToDisplay {
    return [[self sectionsToDisplay] count];
}

- (NSString *)titleForHeaderInSection:(NSInteger)section {
    NSString *recommendedName = [self sectionsToDisplay][section][@"name"];
    return (section != 0 || [recommendedName caseInsensitiveCompare:@" "] != NSOrderedSame) ? recommendedName : @"";
}

- (NSArray *)elementsInSection:(NSInteger)section {
    NSInteger indexOfFirstEntryInSection = [[self sectionsToDisplay][section][@"startIndex"] longValue];
    id firstEntryInSection = self.entries[indexOfFirstEntryInSection];

    if ([firstEntryInSection isKindOfClass:[NSDictionary class]] &&
        [[firstEntryInSection objectForKey:@"recursiveListing"] boolValue]) {
        return [firstEntryInSection objectForKey:@"entries"];
    }

    NSMutableArray *elements = [[NSMutableArray alloc] init];
    NSInteger currentEntryIndex = indexOfFirstEntryInSection;


    while (currentEntryIndex < [self.entries count]) {
        id currentEntry = self.entries[currentEntryIndex];
        if ([currentEntry isKindOfClass:[NSDictionary class]] && [[currentEntry objectForKey:@"recursiveListing"] boolValue]) {
            break;
        } else {
            [elements addObject:currentEntry];
            currentEntryIndex++;
        }
    }

    return elements;
}

- (NSInteger)numberOfElementsInSection:(NSInteger)section {
    return [[self elementsInSection:section] count];
}

- (NSString *)titleForCellAtRow:(NSInteger)row inSection:(NSInteger)section {
    id element = [self elementsInSection:section][row];
    if ([element isKindOfClass:[NSString class]]) {
        return element;
    } else {
        return [element objectForKey:@"name"];
    }
}

@end

ListViewController.h

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

@interface ListViewController : UIViewController

@property (strong, nonatomic) List *list;

@end

ListViewController.m

#import "ListViewController.h"
#import "TypeUtility.h"

@interface ListViewController ()<UITableViewDelegate, UITableViewDataSource>

@end

@implementation ListViewController

- (List *)list {
    if (!_list) {
        // TODO: Filename is currently hardcoded.
        _list = [TypeUtility readInitialListFromJSONFile:@"minimal"];
    }

    return _list;
}

// @required UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.list numberOfElementsInSection:section];
}

// @required UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"ListElementCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];

    cell.textLabel.text = [self.list titleForCellAtRow:indexPath.row inSection:indexPath.section];

    return cell;
}

// @optional UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.list numberOfSectionsToDisplay];
}

// @optional UITableViewDataSource
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [self.list titleForHeaderInSection:section];
}


@end

TypeUtility.m

#import "TypeUtility.h"

@implementation TypeUtility

+ (List *)readInitialListFromJSONFile:(NSString *)filename {
    NSString *filePath = [[NSBundle mainBundle] pathForResource:filename ofType:@"json"];
    NSData *rawData = [NSData dataWithContentsOfFile:filePath];
    id jsonData = [NSJSONSerialization JSONObjectWithData:rawData options:kNilOptions error:nil];

    if (![jsonData isKindOfClass:[NSDictionary class]])
        return [[List alloc] initFromDictionary:@{}];

    return [[List alloc] initFromDictionary:jsonData];
}

@end

This is my first Objective-C / iOS project and I'd especially like these aspects to get reviewed:

  • General Objective-C style understanding
  • This code seems pretty complicated for what it actually does and I expect it to become way more complicated, when I want to display multiple kinds of objects (more than List and NSString). What can I do to make this more general?
\$\endgroup\$
1
  • \$\begingroup\$ I would move readInitialListFromJSONFile: into List.m listFromJSONFile: \$\endgroup\$
    – trapper
    Aug 2, 2017 at 7:41

1 Answer 1

1
\$\begingroup\$

Firstly ListViewController does not need to know where the List comes from. Use dependency injection here, so set up List and inject it into ListViewController before it gets presented.

Now why not just make List conform to UITableViewDataSource protocol and move all that unnecessary redirection out of ListViewController completely.

- (void)viewDidLoad
{
    [super viewdidLoad];
    self.tableView.dataSource = self.list;
}

Job done!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.