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:- Every
NSString
gets its own row. - A non-recursive
List
gets displayed as single element (just the name). - A recursive
List
defines a new section with its entries as content (for now there are nowList
's contained in a recursiveList
).
- Every
The example data will be displayed as 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
andNSString
). What can I do to make this more general?
readInitialListFromJSONFile:
into List.mlistFromJSONFile:
\$\endgroup\$