SlideShare a Scribd company logo
1 of 76
Download to read offline
LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended
recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited.
Table View 

Pain Points
Ken Auer

ken.auer@rolemodelsoftware.com

@kauerrolemodel
LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended
recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited.
Section
The Missing Object
@implementation RMSViewController
!
static NSString *cardCellIdentifier = @"CardCell";
!
-(RMSGoFishGame *)game {
if (!_game) {
_game = [RMSGoFishGame new];
[_game deal];
}
return _game;
}
!
-(RMSGoFishPlayer *)player {
if (!_player) {
_player = self.game.players[0];
}
return _player;
}
!
-(NSArray *)hand {
return [self.player hand];
}
!
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[self hand] count];
}
!
-(UITableViewCell *)tableView:(UITableView *)tableView 

cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cardCellIdentifier];
RMSPlayingCard *card = [self hand][indexPath.row];
cell.textLabel.text = [card description];
cell.imageView.image = [UIImage imageNamed:[card imageName]];
return cell;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {	

return 3;	

}	

!
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {	

return @[@"Cards", @"Books", @"Opponents"][section];	

}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {	

return 3;	

}	

!
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {	

return @[@"Cards", @"Books", @"Opponents"][section];	

}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {	

return [[self hand] count];	

}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {	

return 3;	

}	

!
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {	

return @[@"Cards", @"Books", @"Opponents"][section];	

}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {	

switch (section) {	

case 0:	

return [[self hand] count];	

case 1:	

return [self.player.books count];	

case 2:	

return [[self opponents] count];	

}	

return 0;	

}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {	

return 3;	

}	

!
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {	

return @[@"Cards", @"Books", @"Opponents"][section];	

}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {	

switch (section) {	

case 0:	

return [[self hand] count];	

case 1:	

return [self.player.books count];	

case 2:	

return [[self opponents] count];	

}	

return 0;	

}	

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {	

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cardCellIdentifier];	

RMSPlayingCard *card = [self hand][indexPath.row];	

cell.textLabel.text = [card description];	

cell.imageView.image = [UIImage imageNamed:[card imageName]];	

return cell; 	

}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {	

return 3;	

}	

!
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {	

return @[@"Cards", @"Books", @"Opponents"][section];	

}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {	

switch (section) {	

case 0:	

return [[self hand] count];	

case 1:	

return [self.player.books count];	

case 2:	

return [[self opponents] count];	

}	

return 0;	

}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {	

static NSString *valueCellIdentifier = @"ValueCell";	

static NSString *buttonCellIdentifier = @"ButtonCell";	

	

UITableViewCell *cell = nil;	

if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) {	

cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier];	

if (cell == nil) {	

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier];	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

}	

}	

	

if (indexPath.section == FKProfileSectionIndexRequired) {	

cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier];	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

	

UILabel *label = (UILabel *)[cell viewWithTag:10];	

UITextField *textField = (UITextField *)[cell viewWithTag:11];	

textField.secureTextEntry = NO;	

textField.keyboardType = UIKeyboardTypeDefault;	

	

UITableViewController *twin = self;	

if (indexPath.row == 0) {	

label.text = @"First name";	

textField.text = self.profile.firstname;	

textField.delegate = self.firstNameObserver;	

} else if (indexPath.row == 1) {	

label.text = @"Last name";	

textField.text = self.profile.lastname;	

textField.delegate = self.lastNameObserver;	

[self.firstNameObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {	

static NSString *valueCellIdentifier = @"ValueCell";	

static NSString *buttonCellIdentifier = @"ButtonCell";	

	

UITableViewCell *cell = nil;	

if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) {	

cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier];	

if (cell == nil) {	

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier];	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

}	

}	

	

if (indexPath.section == FKProfileSectionIndexRequired) {	

cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier];	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

	

UILabel *label = (UILabel *)[cell viewWithTag:10];	

UITextField *textField = (UITextField *)[cell viewWithTag:11];	

textField.secureTextEntry = NO;	

textField.keyboardType = UIKeyboardTypeDefault;	

	

UITableViewController *twin = self;	

if (indexPath.row == 0) {	

label.text = @"First name";	

textField.text = self.profile.firstname;	

textField.delegate = self.firstNameObserver;	

} else if (indexPath.row == 1) {	

label.text = @"Last name";	

textField.text = self.profile.lastname;	

textField.delegate = self.lastNameObserver;	

[self.firstNameObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 2) {	

label.text = @"Email";	

textField.text = self.profile.email;	

textField.delegate = self.emailObserver;	

textField.keyboardType = UIKeyboardTypeEmailAddress;	

[self.lastNameObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 3) {	

label.text = @"Password";	

textField.text = self.passwordOne;	

textField.secureTextEntry =YES;	

textField.delegate = self.passwordOneObserver;	

[self.emailObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 4) {	

label.text = @"Confirm";	

textField.text = self.passwordTwo;	

textField.secureTextEntry =YES;	

textField.delegate = self.passwordTwoObserver;	

[self.passwordOneObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

}	

} else if (indexPath.section == FKProfileSectionIndexGeneral) {	

if (indexPath.row == 0) {	

cell.textLabel.text = @"Gender";	

cell.detailTextLabel.text = self.profile.gender;	

} else if (indexPath.row == 1) {	

cell.textLabel.text = @"Birthday";	

cell.detailTextLabel.text = [NSDateFormatter localizedStringFromDate:self.profile.birthday	

dateStyle:NSDateFormatterMediumStyle	

timeStyle:NSDateFormatterNoStyle];	

} else if (indexPath.row == 2) {	

cell.textLabel.text = @"Height";	

if (self.profile.height != nil) {	

NSInteger height = [self.profile.height integerValue];	

cell.detailTextLabel.text = [NSString stringWithFormat:@"%d' %d"", height / 12, height % 12];	

} else {	

cell.detailTextLabel.text = nil;	

}	

}	

} else if (indexPath.section == FKProfileSectionIndexGolf) {	

if (indexPath.row == 0) {	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

cell.textLabel.text = @"Handicap";	

cell.detailTextLabel.text = [self.profile.handicap description];	

} else if (indexPath.row == 1) {	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

cell.textLabel.text = @"Stance";	

cell.detailTextLabel.text = self.profile.stance;	

}	

} else if (indexPath.section == FKProfileSectionIndexSave || indexPath.section == FKProfileSectionIndexDelete) {	

cell = [tableView dequeueReusableCellWithIdentifier:buttonCellIdentifier];	

NSUInteger labelTag = 1919;	

if (cell == nil) {	

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:buttonCellIdentifier];	

UIView *contentView = [cell contentView];	

UILabel *label = [[UILabel alloc] initWithFrame:[contentView frame]];	

[label setAutoresizingMask:UIViewAutoresizingFlexibleWidth];	

label.tag = labelTag;	

UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]];	

[label setFont:boldFont];	

[label setTextAlignment:NSTextAlignmentCenter];	

[label setBackgroundColor:[UIColor clearColor]];	

[contentView addSubview:label];	

label.text = @"Save";	

}	

if (indexPath.section == FKProfileSectionIndexSave) {	

cell.textLabel.textColor = [UIColor blackColor];	

if (self.inRegistrationMode) {	

UILabel *label = (UILabel *)[cell viewWithTag:labelTag];	

label.text = @"Join";	

}	

! if ([self profileInformationIsValid]) {	

cell.selectionStyle = UITableViewCellSelectionStyleGray;	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]];	

cell.backgroundColor = [UIColor darkGrayColor];	

! } else {	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

cell.backgroundColor = [UIColor lightGrayColor];	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor grayColor]];	

}	

} else if (indexPath.section == FKProfileSectionIndexDelete) {	

UILabel *label = (UILabel *)[cell viewWithTag:labelTag];	

label.text = @"Delete";	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]];	

cell.backgroundColor = [UIColor redColor];	

}	

}	

	

return cell;	

}
@implementation RMSViewController
!
static NSString *cardCellIdentifier = @"CardCell";
!
-(RMSGoFishGame *)game {
if (!_game) {
_game = [RMSGoFishGame new];
[_game deal];
}
return _game;
}
!
-(RMSGoFishPlayer *)player {
if (!_player) {
_player = self.game.players[0];
}
return _player;
}
!
-(NSArray *)hand {
return [self.player hand];
}
!
!
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[self hand] count];
}
!
-(UITableViewCell *)tableView:(UITableView *)tableView 

cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cardCellIdentifier];
RMSPlayingCard *card = [self hand][indexPath.row];
cell.textLabel.text = [card description];
cell.imageView.image = [UIImage imageNamed:[card imageName]];
return cell;
}
@implementation RMSViewController
!
static NSString *cardCellIdentifier = @"CardCell";
!
-(RMSGoFishGame *)game {
if (!_game) {
_game = [RMSGoFishGame new];
[_game deal];
}
return _game;
}
!
-(RMSGoFishPlayer *)player {
if (!_player) {
_player = self.game.players[0];
}
return _player;
}
!
-(NSArray *)hand {
return [self.player hand];
}
@implementation RMSViewController
!
static NSString *cardCellIdentifier = @"CardCell";
!
-(RMSGoFishGame *)game {
if (!_game) {
_game = [RMSGoFishGame new];
[_game deal];
}
return _game;
}
!
-(RMSGoFishPlayer *)player {
if (!_player) {
_player = self.game.players[0];
}
return _player;
}
!
-(NSArray *)hand {
return [self.player hand];
}
@implementation RMSViewController	

!
-(NSArray *)sections {	

if (!_sections) {	

RMSCardSection *cardSection = [[RMSCardSection alloc] initWithTitle:@"Cards" cellIdentifier:@"CardCell" player:self.player];	

RMSBookSection *bookSection = [[RMSBookSection alloc] initWithTitle:@"Books" cellIdentifier:@"BookCell" player:self.player];	

RMSOpponentsSection *opponentsSection = [[RMSOpponentsSection alloc] initWithTitle:@"Opponents"
cellIdentifier:@"OpponentCell" rows:[self opponents]];	

_sections = @[cardSection, bookSection, opponentsSection];	

}	

return _sections;	

}	

!
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {	

return [self.sections count];	

}	

!
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {	

return [self.sections[section] titleForHeader];	

}	

!
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {	

return [self.sections[section] numberOfRows];	

}	

!
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {	

return [self.sections[indexPath.section] tableView:tableView cellForRowAtIndex:indexPath.row];	

}
A Missing Section
@protocol RMSTableViewSectionDelegate <NSObject>
- (NSString *)titleForHeader;
- (NSInteger)numberOfRows;
- (UITableViewCell *)tableView:(UITableView *)tableView 

cellForRowAtIndex:(NSUInteger)index;
@end
A Missing Section
@protocol RMSTableViewSectionDelegate <NSObject>
- (NSString *)titleForHeader;
- (NSInteger)numberOfRows;
- (UITableViewCell *)tableView:(UITableView *)tableView 

cellForRowAtIndex:(NSUInteger)index;
@end
@interface RMSAbstractTableViewSection : NSObject<RMSTableViewSectionDelegate>
@property (nonatomic) NSString *titleForHeader;
@property (nonatomic) NSString *cellIdentifier;
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier
-(NSArray *)rows;
-(NSString *)textForRowAtIndex:(NSUInteger)index;
-(UIImage *)imageForRowAtIndex:(NSUInteger)index;
@end
A Missing Section
@protocol RMSTableViewSectionDelegate <NSObject>
- (NSString *)titleForHeader;
- (NSInteger)numberOfRows;
- (UITableViewCell *)tableView:(UITableView *)tableView 

cellForRowAtIndex:(NSUInteger)index;
@end
@interface RMSAbstractTableViewSection : NSObject<RMSTableViewSectionDelegate>
@property (nonatomic) NSString *titleForHeader;
@property (nonatomic) NSString *cellIdentifier;
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier
-(NSArray *)rows;
-(NSString *)textForRowAtIndex:(NSUInteger)index;
-(UIImage *)imageForRowAtIndex:(NSUInteger)index;
@end
@interface RMSGenericTableViewSection : RMSAbstractTableViewSection
@property (readonly) NSArray *rows;
-(instancetype)initWithTitle:(NSString *)title 

cellIdentifier:(NSString *)cellIdentifier rows:(NSArray *)rows;
@end
@implementation RMSAbstractTableViewSection
static NSString *genericCellIdentifier = @"Cell";
!
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier {
self = [super init];
if (self) {
_titleForHeader = title;
_cellIdentifier = cellIdentifier;
}
return self;
}
!
-(NSString *)cellIdentifier {
if (!_cellIdentifier) {
_cellIdentifier = genericCellIdentifier;
}
return _cellIdentifier;
}
!
-(NSInteger)numberOfRows {
return [[self rows] count];
}
!
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndex:(NSUInteger)index {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[self cellIdentifier]];
cell.textLabel.text = [self textForRowAtIndex:index];
cell.imageView.image = [self imageForRowAtIndex:index];
return cell;
}
!
!
@end
@implementation RMSAbstractTableViewSection
static NSString *genericCellIdentifier = @"Cell";
!
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier {
self = [super init];
if (self) {
_titleForHeader = title;
_cellIdentifier = cellIdentifier;
}
return self;
}
!
-(NSString *)cellIdentifier {
if (!_cellIdentifier) {
_cellIdentifier = genericCellIdentifier;
}
return _cellIdentifier;
}
!
-(NSInteger)numberOfRows {
return [[self rows] count];
}
!
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndex:(NSUInteger)index {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[self cellIdentifier]];
cell.textLabel.text = [self textForRowAtIndex:index];
cell.imageView.image = [self imageForRowAtIndex:index];
return cell;
}
!
!
@end
-(NSArray *)rows {
return @[];
}
!
-(NSString *)textForRowAtIndex:(NSUInteger)index {
return [[self rows][index] description];
}
!
-(UIImage *)imageForRowAtIndex:(NSUInteger)index {
return nil;
}
A Common Section
@implementation RMSGenericTableViewSection
!
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier
rows:(NSArray *)rows {
self = [super initWithTitle:title cellIdentifier:cellIdentifier];
if (self) {
_rows = rows;
}
return self;
}
!
@end
A Common Section
@implementation RMSViewController	

!
-(NSArray *)sections {	

if (!_sections) {	

RMSCardSection *cardSection = [[RMSCardSection alloc] initWithTitle:@"Cards" cellIdentifier:@"CardCell" player:self.player];	

RMSBookSection *bookSection = [[RMSBookSection alloc] initWithTitle:@"Books" cellIdentifier:@"BookCell" player:self.player];	

RMSOpponentsSection *opponentsSection = [[RMSOpponentsSection alloc] initWithTitle:@"Opponents"
cellIdentifier:@"OpponentCell" rows:[self opponents]];	

_sections = @[cardSection, bookSection, opponentsSection];	

}	

return _sections;	

}	

!
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {	

return [self.sections count];	

}	

!
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {	

return [self.sections[section] titleForHeader];	

}	

!
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {	

return [self.sections[section] numberOfRows];	

}	

!
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {	

return [self.sections[indexPath.section] tableView:tableView cellForRowAtIndex:indexPath.row];	

}
@interface RMSCardSection : RMSAbstractTableViewSection
@property RMSGoFishPlayer *player;
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier
player:(RMSGoFishPlayer *)player;
@end
!
!
@implementation RMSCardSection
@synthesize player = _player;
!
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier
player:(RMSGoFishPlayer *)player {
self = [super initWithTitle:title cellIdentifier:cellIdentifier];
if (self) {
_player = player;
};
return self;
}
!
-(NSArray *)rows {
return [self.player hand];
}
!
-(UIImage *)imageForRowAtIndex:(NSUInteger)index {
RMSPlayingCard *card = [self rows][index];
return [UIImage imageNamed:[card imageName]];
}
!
@end
@interface RMSBookSection : RMSAbstractTableViewSection
@property RMSGoFishPlayer *player;
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier player:
(RMSGoFishPlayer *)player;
@end
!
!
@implementation RMSBookSection
!
-(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier player:
(RMSGoFishPlayer *)player {
self = [super initWithTitle:title cellIdentifier:cellIdentifier];
if (self) {
_player = player;
};
return self;
}
!
-(NSArray *)rows {
return [self.player books];
}
!
-(UIImage *)imageForRowAtIndex:(NSUInteger)index {
RMSPlayingCard *card = [self rows][index][0];
return [UIImage imageNamed:[card imageName]];
}
!
-(NSString *)textForRowAtIndex:(NSUInteger)index {
RMSPlayingCard *card = [self rows][index][0];
return [NSString stringWithFormat:@"%@s", card.rank];
}
!
@end
@interface RMSOpponentsSection : RMSGenericTableViewSection
@end
!
@implementation RMSOpponentsSection
!
-(UIImage *)imageForRowAtIndex:(NSUInteger)index {
return [UIImage imageNamed:@"backs_blue"];
}
!
-(NSString *)textForRowAtIndex:(NSUInteger)index {
RMSGoFishPlayer *opponent = [self rows][index];
return [NSString stringWithFormat:@"%@ (%d cards, %d books)", 

opponent.name, 

[opponent numberOfCards], 

[[opponent books] count]];
}
!
@end
LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended
recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited.
So What About
Handling Selections?
RMSTableViews

https://github.com/RoleModel/RMSTableViews
An Improved
UITableViewController
@interface RMSTableViewController : UITableViewController
@property (nonatomic, strong, readonly) NSArray *sections;
- (NSArray *)generateSections;
- (void)sectionGenerationDidComplete;
@end
An Improved
UITableViewController
@interface RMSTableViewController : UITableViewController
@property (nonatomic, strong, readonly) NSArray *sections;
- (NSArray *)generateSections;
- (void)sectionGenerationDidComplete;
@end
@interface RMSTableViewSection : NSObject
@property (nonatomic, strong) NSString *headerTitle;
@property (nonatomic, strong) NSString *footerTitle;
- (NSInteger)rowCount;
- (UITableViewCell *)cellForIndex:(NSInteger)index;
@end
!
An Improved
UITableViewController
@interface RMSTableViewController : UITableViewController
@property (nonatomic, strong, readonly) NSArray *sections;
- (NSArray *)generateSections;
- (void)sectionGenerationDidComplete;
@end
@interface RMSTableViewSection : NSObject
@property (nonatomic, strong) NSString *headerTitle;
@property (nonatomic, strong) NSString *footerTitle;
- (NSInteger)rowCount;
- (UITableViewCell *)cellForIndex:(NSInteger)index;
@end
!
!
@protocol RMSTableViewCell <NSObject>
@optional
- (void)respondToSelection;
@end
@implementation RMSTableViewController
!
- (NSArray *)generateSections {
return @[];
}
!
- (void)sectionGenerationDidComplete {
}
!
- (NSArray *)sections {
if (_sections == nil) {
_sections = [self generateSections];
[self sectionGenerationDidComplete];
}
return _sections;
}
!
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
id cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell respondsToSelector:@selector(respondToSelection)]) {
[cell respondToSelection];
}
}
…
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self.sections[indexPath.section] cellForIndex:indexPath.row];
}
!
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.sections count];
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)sectionIndex {
return [self.sections[sectionIndex] rowCount];
}
!
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:
(NSInteger)sectionIndex {
RMSTableViewSection *section = self.sections[sectionIndex];
return section.headerTitle;
}
!
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:
(NSInteger)sectionIndex {
RMSTableViewSection *section = self.sections[sectionIndex];
return section.footerTitle;
}
!
@end
@implementation RMSTableViewSection
!
- (NSInteger)rowCount {
@throw [NSException exceptionWithName:NSGenericException
reason:@"Subclasses must implement rowCount"
userInfo:nil];
}
!
- (UITableViewCell *)cellForIndex:(NSInteger)index {
@throw [NSException exceptionWithName:NSGenericException
reason:@"Subclasses must implement cellForIndex:"
userInfo:nil];
}
!
@end
Sections Were Missing, but…
• In a dynamic table, they just about
always do the same thing

• Identify the represented objects

• Provide the cell

• The cell is unique

• Don’t put the intelligence in the
Section, but in the Cell
@interface RMSDynamicSection : RMSTableViewSection
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSString *cellIdentifier;
@property (nonatomic, strong) NSArray *representedObjects;
- (id)initWithTableView:(UITableView *)tableView cellIdentifier:(NSString *)cellIdentifier;
@end
!
@implementation RMSDynamicSection
!
-(id)initWithTableView:(UITableView *)tableView 

cellIdentifier:(NSString *)cellIdentifier {
self = [super init];
if (self) {
_tableView = tableView;
_cellIdentifier = cellIdentifier;
}
return self;
}
!
- (NSInteger)rowCount {
return [self.representedObjects count];
}
!
- (UITableViewCell *)cellForIndex:(NSInteger)index {
id cell = [self.tableView dequeueReusableCellWithIdentifier:self.cellIdentifier];
if ([cell respondsToSelector:@selector(bindObject:)]) {
[cell bindObject:self.representedObjects[index]];
}
return cell;
}
@end
@implementation RMSWordCell
!
- (void)bindObject:(id)object {
self.textLabel.text = object;
}
!
@end
@implementation RMSWordCell
!
- (void)bindObject:(id)object {
self.textLabel.text = object;
}
!
@end
@implementation RMSCardCell
!
- (void)bindObject:(id)object {
RMSPlayingCard *card = object;
self.textLabel.text = [card description];
self.imageView.image = = [UIImage imageNamed:[card imageName]];
}
!
@end
LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended
recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited.
So What About The
“Form View”?
Data editing, Settings views…
The Form Descriptor
•Defines the structure of a form

•Can be expressed in code, as a Plist, or JSON

•Consists of an array of section dictionaries

•Section dictionaries specify section properties
and contain an array of “row” dictionaries 

•Row dictionaries describe cells
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {	

static NSString *valueCellIdentifier = @"ValueCell";	

static NSString *buttonCellIdentifier = @"ButtonCell";	

	

UITableViewCell *cell = nil;	

if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) {	

cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier];	

if (cell == nil) {	

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier];	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

}	

}	

	

if (indexPath.section == FKProfileSectionIndexRequired) {	

cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier];	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

	

UILabel *label = (UILabel *)[cell viewWithTag:10];	

UITextField *textField = (UITextField *)[cell viewWithTag:11];	

textField.secureTextEntry = NO;	

textField.keyboardType = UIKeyboardTypeDefault;	

	

UITableViewController *twin = self;	

if (indexPath.row == 0) {	

label.text = @"First name";	

textField.text = self.profile.firstname;	

textField.delegate = self.firstNameObserver;	

} else if (indexPath.row == 1) {	

label.text = @"Last name";	

textField.text = self.profile.lastname;	

textField.delegate = self.lastNameObserver;	

[self.firstNameObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 2) {	

label.text = @"Email";	

textField.text = self.profile.email;	

textField.delegate = self.emailObserver;	

textField.keyboardType = UIKeyboardTypeEmailAddress;	

[self.lastNameObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 3) {	

label.text = @"Password";	

textField.text = self.passwordOne;	

textField.secureTextEntry =YES;	

textField.delegate = self.passwordOneObserver;	

[self.emailObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 4) {	

label.text = @"Confirm";	

textField.text = self.passwordTwo;	

textField.secureTextEntry =YES;	

textField.delegate = self.passwordTwoObserver;	

[self.passwordOneObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

}	

} else if (indexPath.section == FKProfileSectionIndexGeneral) {	

if (indexPath.row == 0) {	

cell.textLabel.text = @"Gender";	

cell.detailTextLabel.text = self.profile.gender;	

} else if (indexPath.row == 1) {	

cell.textLabel.text = @"Birthday";	

cell.detailTextLabel.text = [NSDateFormatter localizedStringFromDate:self.profile.birthday	

dateStyle:NSDateFormatterMediumStyle	

timeStyle:NSDateFormatterNoStyle];	

} else if (indexPath.row == 2) {	

cell.textLabel.text = @"Height";	

if (self.profile.height != nil) {	

NSInteger height = [self.profile.height integerValue];	

cell.detailTextLabel.text = [NSString stringWithFormat:@"%d' %d"", height / 12, height % 12];	

} else {	

cell.detailTextLabel.text = nil;	

}	

}	

} else if (indexPath.section == FKProfileSectionIndexGolf) {	

if (indexPath.row == 0) {	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

cell.textLabel.text = @"Handicap";	

cell.detailTextLabel.text = [self.profile.handicap description];	

} else if (indexPath.row == 1) {	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

cell.textLabel.text = @"Stance";	

cell.detailTextLabel.text = self.profile.stance;	

}	

} else if (indexPath.section == FKProfileSectionIndexSave || indexPath.section == FKProfileSectionIndexDelete) {	

cell = [tableView dequeueReusableCellWithIdentifier:buttonCellIdentifier];	

NSUInteger labelTag = 1919;	

if (cell == nil) {	

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:buttonCellIdentifier];	

UIView *contentView = [cell contentView];	

UILabel *label = [[UILabel alloc] initWithFrame:[contentView frame]];	

[label setAutoresizingMask:UIViewAutoresizingFlexibleWidth];	

label.tag = labelTag;	

UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]];	

[label setFont:boldFont];	

[label setTextAlignment:NSTextAlignmentCenter];	

[label setBackgroundColor:[UIColor clearColor]];	

[contentView addSubview:label];	

label.text = @"Save";	

}	

if (indexPath.section == FKProfileSectionIndexSave) {	

cell.textLabel.textColor = [UIColor blackColor];	

if (self.inRegistrationMode) {	

UILabel *label = (UILabel *)[cell viewWithTag:labelTag];	

label.text = @"Join";	

}	

! if ([self profileInformationIsValid]) {	

cell.selectionStyle = UITableViewCellSelectionStyleGray;	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]];	

cell.backgroundColor = [UIColor darkGrayColor];	

! } else {	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

cell.backgroundColor = [UIColor lightGrayColor];	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor grayColor]];	

}	

} else if (indexPath.section == FKProfileSectionIndexDelete) {	

UILabel *label = (UILabel *)[cell viewWithTag:labelTag];	

label.text = @"Delete";	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]];	

cell.backgroundColor = [UIColor redColor];	

}	

}	

	

return cell;	

}
@interface RMSProfileViewController ()	

!@property (nonatomic, strong) RMSButtonCell *saveButton;	

@property (nonatomic, strong) RMSFormSection *accountSection;	

@property (nonatomic, strong) NSString *passwordOne;	

@property (nonatomic, strong) NSString *passwordTwo;	

!@end	

!@implementation RMSProfileViewController	

!#pragma mark plist object mapping	

!- (NSDictionary *)objectSubstitionDictionary {	

return @{	

@":self" : self,	

@":profile" : self.profile,	

@":whiteColor" : [UIColor whiteColor],	

@":blackColor" : [UIColor blackColor],	

@":redColor" : [UIColor redColor],	

@":saveLabel" : self.inRegistrationMode ? @"Join" : @"Save",	

@":emailEnabled" : @(![self.profile isDefaultBool]),	

@":passwordsEnabled" : @(self.inRegistrationMode),	

@":deleteEnabled" : @(![self inRegistrationMode] && self.profile.canBeDeleted),	

@":genders" : @[@"male", @"female"]	

};	

}	

!#pragma mark lifecycle	

!- (void)viewDidLoad {	

DLog(@"");	

[super viewDidLoad];	

!self.saveButton.enabled = [self profileInformationIsValid];	

!self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Settings"	

style:UIBarButtonItemStyleBordered	

target:self	

action:@selector(settingsButtonAction:)];	

}	

!- (void)viewDidAppear:(BOOL)animated {	

DLog(@"");	

[super viewDidAppear:animated];	

![self startObserving];	

}	

!- (void)dealloc {	

[self stopObserving];	

}	

!- (void)startObserving {	

for (NSString *key in [RMSProfile attributes]) {	

[self.profile addObserver:self forKeyPath:key options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL];	

}	

}	

!- (void)stopObserving {	

for (NSString *key in [RMSProfile attributes]) {	

[self.profile removeObserver:self forKeyPath:key];	

}	

}	

!- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {	

self.title = self.profile.fullName;	

[self updateSaveButtonStatus];	

!NSUInteger saveSectionIndex = [[self.tableView indexPathForCell:self.saveButton] section];	

![self.tableView reloadSections:[NSIndexSet indexSetWithIndex:saveSectionIndex]	

withRowAnimation:UITableViewRowAnimationNone];	

}	

!- (void)updateSaveButtonStatus {	

self.saveButton.enabled = [self profileInformationIsValid];	

}	

!- (void)updatePasswordMessage {	

[self updateSaveButtonStatus];	

NSString *existingFooter = self.accountSection.footerTitle ? self.accountSection.footerTitle : @"";	

!if (self.passwordOne.length > 0 && self.passwordTwo.length > 0) {	

if (![self.passwordOne isEqualToString:self.passwordTwo]) {	

self.accountSection.footerTitle = @"Passwords don't match";	

} else if (self.passwordOne.length < 6) {	

self.accountSection.footerTitle = @"Passwords must be at least 6 characters long";	

} else {	

self.accountSection.footerTitle = @"";	

}	

} else {	

self.accountSection.footerTitle = @"";	

}	

!if (![self.accountSection.footerTitle isEqualToString:existingFooter]) {	

[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[self.sections indexOfObject:self.accountSection]]	

withRowAnimation:UITableViewRowAnimationNone];	

}	

}	

!- (void)setPasswordOne:(NSString *)passwordOne {	

_passwordOne = passwordOne;	

[self updatePasswordMessage];	

}	

!- (void)setPasswordTwo:(NSString *)passwordTwo {	

_passwordTwo = passwordTwo;	

[self updatePasswordMessage];	

}	

!#pragma mark actions	

!- (void)settingsButtonAction:(id)sender {	

RMSSettingsViewController *settingsViewController = [[RMSSettingsViewController alloc] initWithStyle:UITableViewStyleGrouped descriptorNamed:@"RMSSettingsViewController.json"];	

UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:settingsViewController];	

[self.navigationController presentViewController:navigationController animated:YES completion:nil];	

}	

!- (void)saveAction:(id)sender {	

if ([self profileInformationIsValid]) {	

if (self.inRegistrationMode) {	

[self registerUser];	

} else {	

[self save];	

}	

}	

}	

!- (void)deleteAction:(id)sender {	

[self delete];	

}	

!- (void)pushWordViewController {	

RMSWordViewController *wordViewController = [[RMSWordViewController alloc] initWithStyle:UITableViewStylePlain];	

[self.navigationController pushViewController:wordViewController animated:YES];	

}	

!#pragma mark real work would occur here	

!- (void)delete {	

[UIAlertView showAlertWithMessage:@"DELETE"];	

}	

!- (void)save {	

[UIAlertView showAlertWithMessage:@"SAVE"];	

}	

!- (void)registerUser {	

[UIAlertView showAlertWithMessage:@"REGISTER"];	

}	

!#pragma mark validation, sort of	

!- (BOOL)profileInformationIsValid {	

BOOL profileInformationIsValid = [self.profile isValid];	

if (self.inRegistrationMode) {	

profileInformationIsValid = profileInformationIsValid && ([self validateEmail:self.profile.email] && [self passwordsArePossiblyValid]);	

}	

	

return profileInformationIsValid;	

}	

!- (BOOL)passwordsArePossiblyValid {	

return (self.passwordOne.length >= 6 && [self.passwordOne isEqualToString:self.passwordTwo]);	

}	

!- (BOOL)validateEmail:(NSString *)candidate {	

NSString *emailRegex = @"(?:[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[A-Za-z0-9!#$%&'*+/=?^_`{|}" @"~-]+)*|"(?:[x01-x08x0bx0cx0e-x1fx21x23-x5bx5d-" @"x7f]|[x01-x09x0bx0cx0e-x7f])*")@(?:(?:[A-Za-z0-9](?:[a-" @"z0-9-]*[A-Za-z0-9])?.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?|[(?:(?:25[0-5" @"]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-" @"9][0-9]?|[A-Za-z0-9-]*[A-Za-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21" @"-x5ax53-x7f]|[x01-x09x0bx0cx0e-x7f])+)])";	

	

NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];	

	

return [emailTest evaluateWithObject:candidate];	

}	

!@end	

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {	

static NSString *valueCellIdentifier = @"ValueCell";	

static NSString *buttonCellIdentifier = @"ButtonCell";	

	

UITableViewCell *cell = nil;	

if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) {	

cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier];	

if (cell == nil) {	

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier];	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

}	

}	

	

if (indexPath.section == FKProfileSectionIndexRequired) {	

cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier];	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

	

UILabel *label = (UILabel *)[cell viewWithTag:10];	

UITextField *textField = (UITextField *)[cell viewWithTag:11];	

textField.secureTextEntry = NO;	

textField.keyboardType = UIKeyboardTypeDefault;	

	

UITableViewController *twin = self;	

if (indexPath.row == 0) {	

label.text = @"First name";	

textField.text = self.profile.firstname;	

textField.delegate = self.firstNameObserver;	

} else if (indexPath.row == 1) {	

label.text = @"Last name";	

textField.text = self.profile.lastname;	

textField.delegate = self.lastNameObserver;	

[self.firstNameObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 2) {	

label.text = @"Email";	

textField.text = self.profile.email;	

textField.delegate = self.emailObserver;	

textField.keyboardType = UIKeyboardTypeEmailAddress;	

[self.lastNameObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 3) {	

label.text = @"Password";	

textField.text = self.passwordOne;	

textField.secureTextEntry =YES;	

textField.delegate = self.passwordOneObserver;	

[self.emailObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

} else if (indexPath.row == 4) {	

label.text = @"Confirm";	

textField.text = self.passwordTwo;	

textField.secureTextEntry =YES;	

textField.delegate = self.passwordTwoObserver;	

[self.passwordOneObserver setNextTextField:textField withBlock:^{	

[twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]	

atScrollPosition:UITableViewScrollPositionBottom	

animated:YES];	

}];	

}	

} else if (indexPath.section == FKProfileSectionIndexGeneral) {	

if (indexPath.row == 0) {	

cell.textLabel.text = @"Gender";	

cell.detailTextLabel.text = self.profile.gender;	

} else if (indexPath.row == 1) {	

cell.textLabel.text = @"Birthday";	

cell.detailTextLabel.text = [NSDateFormatter localizedStringFromDate:self.profile.birthday	

dateStyle:NSDateFormatterMediumStyle	

timeStyle:NSDateFormatterNoStyle];	

} else if (indexPath.row == 2) {	

cell.textLabel.text = @"Height";	

if (self.profile.height != nil) {	

NSInteger height = [self.profile.height integerValue];	

cell.detailTextLabel.text = [NSString stringWithFormat:@"%d' %d"", height / 12, height % 12];	

} else {	

cell.detailTextLabel.text = nil;	

}	

}	

} else if (indexPath.section == FKProfileSectionIndexGolf) {	

if (indexPath.row == 0) {	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

cell.textLabel.text = @"Handicap";	

cell.detailTextLabel.text = [self.profile.handicap description];	

} else if (indexPath.row == 1) {	

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;	

cell.textLabel.text = @"Stance";	

cell.detailTextLabel.text = self.profile.stance;	

}	

} else if (indexPath.section == FKProfileSectionIndexSave || indexPath.section == FKProfileSectionIndexDelete) {	

cell = [tableView dequeueReusableCellWithIdentifier:buttonCellIdentifier];	

NSUInteger labelTag = 1919;	

if (cell == nil) {	

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:buttonCellIdentifier];	

UIView *contentView = [cell contentView];	

UILabel *label = [[UILabel alloc] initWithFrame:[contentView frame]];	

[label setAutoresizingMask:UIViewAutoresizingFlexibleWidth];	

label.tag = labelTag;	

UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]];	

[label setFont:boldFont];	

[label setTextAlignment:NSTextAlignmentCenter];	

[label setBackgroundColor:[UIColor clearColor]];	

[contentView addSubview:label];	

label.text = @"Save";	

}	

if (indexPath.section == FKProfileSectionIndexSave) {	

cell.textLabel.textColor = [UIColor blackColor];	

if (self.inRegistrationMode) {	

UILabel *label = (UILabel *)[cell viewWithTag:labelTag];	

label.text = @"Join";	

}	

! if ([self profileInformationIsValid]) {	

cell.selectionStyle = UITableViewCellSelectionStyleGray;	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]];	

cell.backgroundColor = [UIColor darkGrayColor];	

! } else {	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

cell.backgroundColor = [UIColor lightGrayColor];	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor grayColor]];	

}	

} else if (indexPath.section == FKProfileSectionIndexDelete) {	

UILabel *label = (UILabel *)[cell viewWithTag:labelTag];	

label.text = @"Delete";	

cell.selectionStyle = UITableViewCellSelectionStyleNone;	

[(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]];	

cell.backgroundColor = [UIColor redColor];	

}	

}	

	

return cell;	

}
Top level
items describe
Sections
Top level
items describe
Sections
Rows in
Sections
describe Cells
Top level
items describe
Sections
Rows in
Sections
describe Cells
Various
Generic Form
Cells already
available
Top level
items describe
Sections
Rows in
Sections
describe Cells
Various
Generic Form
Cells already
available
Things that
don’t go well in Plists
can identify key to have
controller interpret &
substitute
Can use
JSON if you
don’t like Plists
- (NSArray *)generateSections {
NSMutableArray *sections = [NSMutableArray array];
!
for (NSDictionary *rawSection in self.descriptor) {
/* Sections and Cells are built here */
}
return sections;
}
The form descriptor drives the construction 

of the table view
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptor:(NSArray *)descriptor;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptorNamed:(NSString *)descriptorName;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

rawDescriptor:(NSData *)rawDescriptor 

type:(RMSFormDescriptorType)descriptorType;
Form View Controller Instantiation
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptor:(NSArray *)descriptor;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptorNamed:(NSString *)descriptorName;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

rawDescriptor:(NSData *)rawDescriptor 

type:(RMSFormDescriptorType)descriptorType;
Form View Controller Instantiation
Designated
Initializer
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptor:(NSArray *)descriptor;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptorNamed:(NSString *)descriptorName;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

rawDescriptor:(NSData *)rawDescriptor 

type:(RMSFormDescriptorType)descriptorType;
Form View Controller Instantiation
Resource-
based
initializer (Plist
or JSON)
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptor:(NSArray *)descriptor;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptorNamed:(NSString *)descriptorName;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

rawDescriptor:(NSData *)rawDescriptor 

type:(RMSFormDescriptorType)descriptorType;
Form View Controller Instantiation
Data-based
initializer (Plist
or JSON)
Demo
Building a Simple Form
Demo
Building a Simple Form
• Section definition

Demo
Building a Simple Form
• Section definition

• Data binding

Demo
Building a Simple Form
• Section definition

• Data binding

• Object substitution

Demo
Building a Simple Form
• Section definition

• Data binding

• Object substitution

• Cell properties
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptor:(NSArray *)descriptor;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptorNamed:(NSString *)descriptorName;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

rawDescriptor:(NSData *)rawDescriptor 

type:(RMSFormDescriptorType)descriptorType;
FormView Controller Instantiation
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptor:(NSArray *)descriptor;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

descriptorNamed:(NSString *)descriptorName;
!
!
- (id)initWithStyle:(UITableViewStyle)style 

rawDescriptor:(NSData *)rawDescriptor 

type:(RMSFormDescriptorType)descriptorType;
FormView Controller Instantiation
Supports
externalizing
the form
descriptor
Demo
Demo
• Resource-based Plist

Demo
• Resource-based Plist

• Resource-based JSON

Demo
• Resource-based Plist

• Resource-based JSON

• Web-based JSON
Demo
• Resource-based Plist

• Resource-based JSON

• Web-based JSON
Summary
Summary
• TableViews often require much tedium
when not in the simplest form
Summary
• TableViews often require much tedium
when not in the simplest form
• Code becomes hard to parse
Summary
• TableViews often require much tedium
when not in the simplest form
• Code becomes hard to parse
• Few interesting things happening
(mostly tying objects to the right cells)
Summary
• TableViews often require much tedium
when not in the simplest form
• Code becomes hard to parse
• Few interesting things happening
(mostly tying objects to the right cells)
• Sections are objects that are implied
but missing from the Object Model
Summary
• TableViews often require much tedium
when not in the simplest form
• Code becomes hard to parse
• Few interesting things happening
(mostly tying objects to the right cells)
• Sections are objects that are implied
but missing from the Object Model
• Make Cells more intelligent about the
objects they represent
LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended
recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited.
UITableView

Pain Reliever
https://github.com/RoleModel/RMSTableViews
Ken Auer

ken.auer@rolemodelsoftware.com

@kauerrolemodel

Tony Ingraldi

tony.ingraldi@rolemodelsoftware.com
Available as a CocoaPod, docs on cocoadocs.org

More Related Content

What's hot

Propel sfugmd
Propel sfugmdPropel sfugmd
Propel sfugmdiKlaus
 
Instant Dynamic Forms with #states
Instant Dynamic Forms with #statesInstant Dynamic Forms with #states
Instant Dynamic Forms with #statesKonstantin Käfer
 
Dig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup CairoDig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup CairoMohamed Mosaad
 
Open Selector
Open SelectorOpen Selector
Open Selectorjjdelc
 
Php Security - OWASP
Php  Security - OWASPPhp  Security - OWASP
Php Security - OWASPMizno Kruge
 
Business News, Personal Finance and Money News
Business News, Personal Finance and Money NewsBusiness News, Personal Finance and Money News
Business News, Personal Finance and Money Newseminentoomph4388
 
The City Bars App with Sencha Touch 2
The City Bars App with Sencha Touch 2The City Bars App with Sencha Touch 2
The City Bars App with Sencha Touch 2James Pearce
 
Drupal 9 training ajax
Drupal 9 training ajaxDrupal 9 training ajax
Drupal 9 training ajaxNeelAndrew
 
Exemple de création de base
Exemple de création de baseExemple de création de base
Exemple de création de baseSaber LAJILI
 
Business News, Personal Finance and Money News
Business News, Personal Finance and Money NewsBusiness News, Personal Finance and Money News
Business News, Personal Finance and Money Newsphobicmistake8593
 
Form demoinplaywithmysql
Form demoinplaywithmysqlForm demoinplaywithmysql
Form demoinplaywithmysqlKnoldus Inc.
 

What's hot (20)

Propel sfugmd
Propel sfugmdPropel sfugmd
Propel sfugmd
 
Instant Dynamic Forms with #states
Instant Dynamic Forms with #statesInstant Dynamic Forms with #states
Instant Dynamic Forms with #states
 
Dig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup CairoDig Deeper into WordPress - WD Meetup Cairo
Dig Deeper into WordPress - WD Meetup Cairo
 
Open Selector
Open SelectorOpen Selector
Open Selector
 
Views notwithstanding
Views notwithstandingViews notwithstanding
Views notwithstanding
 
Php Security - OWASP
Php  Security - OWASPPhp  Security - OWASP
Php Security - OWASP
 
Business News, Personal Finance and Money News
Business News, Personal Finance and Money NewsBusiness News, Personal Finance and Money News
Business News, Personal Finance and Money News
 
201104 iphone navigation-based apps
201104 iphone navigation-based apps201104 iphone navigation-based apps
201104 iphone navigation-based apps
 
The City Bars App with Sencha Touch 2
The City Bars App with Sencha Touch 2The City Bars App with Sencha Touch 2
The City Bars App with Sencha Touch 2
 
50 jquery
50 jquery50 jquery
50 jquery
 
Build your own entity with Drupal
Build your own entity with DrupalBuild your own entity with Drupal
Build your own entity with Drupal
 
Drupal 9 training ajax
Drupal 9 training ajaxDrupal 9 training ajax
Drupal 9 training ajax
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 
10. view one record
10. view one record10. view one record
10. view one record
 
11. delete record
11. delete record11. delete record
11. delete record
 
Exemple de création de base
Exemple de création de baseExemple de création de base
Exemple de création de base
 
Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
 
SQLAlchemy Seminar
SQLAlchemy SeminarSQLAlchemy Seminar
SQLAlchemy Seminar
 
Business News, Personal Finance and Money News
Business News, Personal Finance and Money NewsBusiness News, Personal Finance and Money News
Business News, Personal Finance and Money News
 
Form demoinplaywithmysql
Form demoinplaywithmysqlForm demoinplaywithmysql
Form demoinplaywithmysql
 

Viewers also liked

Priya Enterprises, New Delhi, Plastic Paint Buckets
Priya Enterprises, New Delhi, Plastic Paint BucketsPriya Enterprises, New Delhi, Plastic Paint Buckets
Priya Enterprises, New Delhi, Plastic Paint BucketsIndiaMART InterMESH Limited
 
ways to increase micronutrient use efficiency
ways to increase micronutrient use efficiencyways to increase micronutrient use efficiency
ways to increase micronutrient use efficiencyPrakash Ranjan Behera
 
The indian dairy industry series 2 - dairy whitener
The indian dairy industry series 2 - dairy whitenerThe indian dairy industry series 2 - dairy whitener
The indian dairy industry series 2 - dairy whitenerSubhashis Dasgupta
 
Presentation on soaps and detergents
Presentation on soaps and detergentsPresentation on soaps and detergents
Presentation on soaps and detergentsSmartySonali
 

Viewers also liked (9)

Priya Enterprises, New Delhi, Plastic Paint Buckets
Priya Enterprises, New Delhi, Plastic Paint BucketsPriya Enterprises, New Delhi, Plastic Paint Buckets
Priya Enterprises, New Delhi, Plastic Paint Buckets
 
Dairy whitener
Dairy whitenerDairy whitener
Dairy whitener
 
Images
ImagesImages
Images
 
ways to increase micronutrient use efficiency
ways to increase micronutrient use efficiencyways to increase micronutrient use efficiency
ways to increase micronutrient use efficiency
 
The indian dairy industry series 2 - dairy whitener
The indian dairy industry series 2 - dairy whitenerThe indian dairy industry series 2 - dairy whitener
The indian dairy industry series 2 - dairy whitener
 
Soaps and detergents
Soaps and detergentsSoaps and detergents
Soaps and detergents
 
Presentation on soaps and detergents
Presentation on soaps and detergentsPresentation on soaps and detergents
Presentation on soaps and detergents
 
Fertiliser ppt
Fertiliser pptFertiliser ppt
Fertiliser ppt
 
Soap and detergents
Soap and detergentsSoap and detergents
Soap and detergents
 

Similar to UITableView Pain Points

Your Second iPhone App - Code Listings
Your Second iPhone App - Code ListingsYour Second iPhone App - Code Listings
Your Second iPhone App - Code ListingsVu Tran Lam
 
iOS for ERREST - alternative version
iOS for ERREST - alternative versioniOS for ERREST - alternative version
iOS for ERREST - alternative versionWO Community
 
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...Mail.ru Group
 
Fields in Core: How to create a custom field
Fields in Core: How to create a custom fieldFields in Core: How to create a custom field
Fields in Core: How to create a custom fieldIvan Zugec
 
Drupal Field API. Practical usage
Drupal Field API. Practical usageDrupal Field API. Practical usage
Drupal Field API. Practical usagePavel Makhrinsky
 
Extending MySQL Enterprise Monitor
Extending MySQL Enterprise MonitorExtending MySQL Enterprise Monitor
Extending MySQL Enterprise MonitorMark Leith
 
Essentials and Impactful Features of ES6
Essentials and Impactful Features of ES6Essentials and Impactful Features of ES6
Essentials and Impactful Features of ES6Riza Fahmi
 
Entity Attribute Value (Eav)
Entity   Attribute   Value (Eav)Entity   Attribute   Value (Eav)
Entity Attribute Value (Eav)Tâm
 
Web Components changes Web Development
Web Components changes Web DevelopmentWeb Components changes Web Development
Web Components changes Web DevelopmentShogo Sensui
 
JavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScriptJavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScriptLaurence Svekis ✔
 
c++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docxc++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docxhumphrieskalyn
 
Enterprise workflow with Apps Script
Enterprise workflow with Apps ScriptEnterprise workflow with Apps Script
Enterprise workflow with Apps Scriptccherubino
 
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드영욱 김
 
Working With JQuery Part1
Working With JQuery Part1Working With JQuery Part1
Working With JQuery Part1saydin_soft
 
Prateek dayal backbonerails-110528024926-phpapp02
Prateek dayal backbonerails-110528024926-phpapp02Prateek dayal backbonerails-110528024926-phpapp02
Prateek dayal backbonerails-110528024926-phpapp02Revath S Kumar
 
Single Page Web Apps with Backbone.js and Rails
Single Page Web Apps with Backbone.js and RailsSingle Page Web Apps with Backbone.js and Rails
Single Page Web Apps with Backbone.js and RailsPrateek Dayal
 
Angular 2.0 Views
Angular 2.0 ViewsAngular 2.0 Views
Angular 2.0 ViewsEyal Vardi
 

Similar to UITableView Pain Points (20)

Your Second iPhone App - Code Listings
Your Second iPhone App - Code ListingsYour Second iPhone App - Code Listings
Your Second iPhone App - Code Listings
 
iOS for ERREST - alternative version
iOS for ERREST - alternative versioniOS for ERREST - alternative version
iOS for ERREST - alternative version
 
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
CocoaHeads Moscow. Азиз Латыпов, VIPole. «Запросы в CoreData с агрегатными фу...
 
Fields in Core: How to create a custom field
Fields in Core: How to create a custom fieldFields in Core: How to create a custom field
Fields in Core: How to create a custom field
 
Drupal Field API. Practical usage
Drupal Field API. Practical usageDrupal Field API. Practical usage
Drupal Field API. Practical usage
 
Extending MySQL Enterprise Monitor
Extending MySQL Enterprise MonitorExtending MySQL Enterprise Monitor
Extending MySQL Enterprise Monitor
 
Essentials and Impactful Features of ES6
Essentials and Impactful Features of ES6Essentials and Impactful Features of ES6
Essentials and Impactful Features of ES6
 
D8 Form api
D8 Form apiD8 Form api
D8 Form api
 
Entity Attribute Value (Eav)
Entity   Attribute   Value (Eav)Entity   Attribute   Value (Eav)
Entity Attribute Value (Eav)
 
Web Components changes Web Development
Web Components changes Web DevelopmentWeb Components changes Web Development
Web Components changes Web Development
 
JavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScriptJavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScript
 
jQuery introduction
jQuery introductionjQuery introduction
jQuery introduction
 
c++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docxc++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docx
 
Scala on Your Phone
Scala on Your PhoneScala on Your Phone
Scala on Your Phone
 
Enterprise workflow with Apps Script
Enterprise workflow with Apps ScriptEnterprise workflow with Apps Script
Enterprise workflow with Apps Script
 
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
 
Working With JQuery Part1
Working With JQuery Part1Working With JQuery Part1
Working With JQuery Part1
 
Prateek dayal backbonerails-110528024926-phpapp02
Prateek dayal backbonerails-110528024926-phpapp02Prateek dayal backbonerails-110528024926-phpapp02
Prateek dayal backbonerails-110528024926-phpapp02
 
Single Page Web Apps with Backbone.js and Rails
Single Page Web Apps with Backbone.js and RailsSingle Page Web Apps with Backbone.js and Rails
Single Page Web Apps with Backbone.js and Rails
 
Angular 2.0 Views
Angular 2.0 ViewsAngular 2.0 Views
Angular 2.0 Views
 

Recently uploaded

CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceanilsa9823
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️anilsa9823
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 

Recently uploaded (20)

CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 

UITableView Pain Points

  • 1. LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited. Table View 
 Pain Points Ken Auer ken.auer@rolemodelsoftware.com @kauerrolemodel
  • 2. LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited. Section The Missing Object
  • 3.
  • 4. @implementation RMSViewController ! static NSString *cardCellIdentifier = @"CardCell"; ! -(RMSGoFishGame *)game { if (!_game) { _game = [RMSGoFishGame new]; [_game deal]; } return _game; } ! -(RMSGoFishPlayer *)player { if (!_player) { _player = self.game.players[0]; } return _player; } ! -(NSArray *)hand { return [self.player hand]; } ! -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[self hand] count]; } ! -(UITableViewCell *)tableView:(UITableView *)tableView 
 cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cardCellIdentifier]; RMSPlayingCard *card = [self hand][indexPath.row]; cell.textLabel.text = [card description]; cell.imageView.image = [UIImage imageNamed:[card imageName]]; return cell; }
  • 5.
  • 6. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 3; } ! -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return @[@"Cards", @"Books", @"Opponents"][section]; }
  • 7. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 3; } ! -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return @[@"Cards", @"Books", @"Opponents"][section]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[self hand] count]; }
  • 8. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 3; } ! -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return @[@"Cards", @"Books", @"Opponents"][section]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { switch (section) { case 0: return [[self hand] count]; case 1: return [self.player.books count]; case 2: return [[self opponents] count]; } return 0; }
  • 9. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 3; } ! -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return @[@"Cards", @"Books", @"Opponents"][section]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { switch (section) { case 0: return [[self hand] count]; case 1: return [self.player.books count]; case 2: return [[self opponents] count]; } return 0; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cardCellIdentifier]; RMSPlayingCard *card = [self hand][indexPath.row]; cell.textLabel.text = [card description]; cell.imageView.image = [UIImage imageNamed:[card imageName]]; return cell; }
  • 10. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 3; } ! -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return @[@"Cards", @"Books", @"Opponents"][section]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { switch (section) { case 0: return [[self hand] count]; case 1: return [self.player.books count]; case 2: return [[self opponents] count]; } return 0; }
  • 11.
  • 12. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *valueCellIdentifier = @"ValueCell"; static NSString *buttonCellIdentifier = @"ButtonCell"; UITableViewCell *cell = nil; if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) { cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } } if (indexPath.section == FKProfileSectionIndexRequired) { cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier]; cell.selectionStyle = UITableViewCellSelectionStyleNone; UILabel *label = (UILabel *)[cell viewWithTag:10]; UITextField *textField = (UITextField *)[cell viewWithTag:11]; textField.secureTextEntry = NO; textField.keyboardType = UIKeyboardTypeDefault; UITableViewController *twin = self; if (indexPath.row == 0) { label.text = @"First name"; textField.text = self.profile.firstname; textField.delegate = self.firstNameObserver; } else if (indexPath.row == 1) { label.text = @"Last name"; textField.text = self.profile.lastname; textField.delegate = self.lastNameObserver; [self.firstNameObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
  • 13. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *valueCellIdentifier = @"ValueCell"; static NSString *buttonCellIdentifier = @"ButtonCell"; UITableViewCell *cell = nil; if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) { cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } } if (indexPath.section == FKProfileSectionIndexRequired) { cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier]; cell.selectionStyle = UITableViewCellSelectionStyleNone; UILabel *label = (UILabel *)[cell viewWithTag:10]; UITextField *textField = (UITextField *)[cell viewWithTag:11]; textField.secureTextEntry = NO; textField.keyboardType = UIKeyboardTypeDefault; UITableViewController *twin = self; if (indexPath.row == 0) { label.text = @"First name"; textField.text = self.profile.firstname; textField.delegate = self.firstNameObserver; } else if (indexPath.row == 1) { label.text = @"Last name"; textField.text = self.profile.lastname; textField.delegate = self.lastNameObserver; [self.firstNameObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 2) { label.text = @"Email"; textField.text = self.profile.email; textField.delegate = self.emailObserver; textField.keyboardType = UIKeyboardTypeEmailAddress; [self.lastNameObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 3) { label.text = @"Password"; textField.text = self.passwordOne; textField.secureTextEntry =YES; textField.delegate = self.passwordOneObserver; [self.emailObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 4) { label.text = @"Confirm"; textField.text = self.passwordTwo; textField.secureTextEntry =YES; textField.delegate = self.passwordTwoObserver; [self.passwordOneObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } } else if (indexPath.section == FKProfileSectionIndexGeneral) { if (indexPath.row == 0) { cell.textLabel.text = @"Gender"; cell.detailTextLabel.text = self.profile.gender; } else if (indexPath.row == 1) { cell.textLabel.text = @"Birthday"; cell.detailTextLabel.text = [NSDateFormatter localizedStringFromDate:self.profile.birthday dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterNoStyle]; } else if (indexPath.row == 2) { cell.textLabel.text = @"Height"; if (self.profile.height != nil) { NSInteger height = [self.profile.height integerValue]; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d' %d"", height / 12, height % 12]; } else { cell.detailTextLabel.text = nil; } } } else if (indexPath.section == FKProfileSectionIndexGolf) { if (indexPath.row == 0) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.text = @"Handicap"; cell.detailTextLabel.text = [self.profile.handicap description]; } else if (indexPath.row == 1) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.text = @"Stance"; cell.detailTextLabel.text = self.profile.stance; } } else if (indexPath.section == FKProfileSectionIndexSave || indexPath.section == FKProfileSectionIndexDelete) { cell = [tableView dequeueReusableCellWithIdentifier:buttonCellIdentifier]; NSUInteger labelTag = 1919; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:buttonCellIdentifier]; UIView *contentView = [cell contentView]; UILabel *label = [[UILabel alloc] initWithFrame:[contentView frame]]; [label setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; label.tag = labelTag; UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]]; [label setFont:boldFont]; [label setTextAlignment:NSTextAlignmentCenter]; [label setBackgroundColor:[UIColor clearColor]]; [contentView addSubview:label]; label.text = @"Save"; } if (indexPath.section == FKProfileSectionIndexSave) { cell.textLabel.textColor = [UIColor blackColor]; if (self.inRegistrationMode) { UILabel *label = (UILabel *)[cell viewWithTag:labelTag]; label.text = @"Join"; } ! if ([self profileInformationIsValid]) { cell.selectionStyle = UITableViewCellSelectionStyleGray; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]]; cell.backgroundColor = [UIColor darkGrayColor]; ! } else { cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.backgroundColor = [UIColor lightGrayColor]; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor grayColor]]; } } else if (indexPath.section == FKProfileSectionIndexDelete) { UILabel *label = (UILabel *)[cell viewWithTag:labelTag]; label.text = @"Delete"; cell.selectionStyle = UITableViewCellSelectionStyleNone; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]]; cell.backgroundColor = [UIColor redColor]; } } return cell; }
  • 14. @implementation RMSViewController ! static NSString *cardCellIdentifier = @"CardCell"; ! -(RMSGoFishGame *)game { if (!_game) { _game = [RMSGoFishGame new]; [_game deal]; } return _game; } ! -(RMSGoFishPlayer *)player { if (!_player) { _player = self.game.players[0]; } return _player; } ! -(NSArray *)hand { return [self.player hand]; }
  • 15. ! ! -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[self hand] count]; } ! -(UITableViewCell *)tableView:(UITableView *)tableView 
 cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cardCellIdentifier]; RMSPlayingCard *card = [self hand][indexPath.row]; cell.textLabel.text = [card description]; cell.imageView.image = [UIImage imageNamed:[card imageName]]; return cell; } @implementation RMSViewController ! static NSString *cardCellIdentifier = @"CardCell"; ! -(RMSGoFishGame *)game { if (!_game) { _game = [RMSGoFishGame new]; [_game deal]; } return _game; } ! -(RMSGoFishPlayer *)player { if (!_player) { _player = self.game.players[0]; } return _player; } ! -(NSArray *)hand { return [self.player hand]; }
  • 16. @implementation RMSViewController ! static NSString *cardCellIdentifier = @"CardCell"; ! -(RMSGoFishGame *)game { if (!_game) { _game = [RMSGoFishGame new]; [_game deal]; } return _game; } ! -(RMSGoFishPlayer *)player { if (!_player) { _player = self.game.players[0]; } return _player; } ! -(NSArray *)hand { return [self.player hand]; }
  • 17.
  • 18. @implementation RMSViewController ! -(NSArray *)sections { if (!_sections) { RMSCardSection *cardSection = [[RMSCardSection alloc] initWithTitle:@"Cards" cellIdentifier:@"CardCell" player:self.player]; RMSBookSection *bookSection = [[RMSBookSection alloc] initWithTitle:@"Books" cellIdentifier:@"BookCell" player:self.player]; RMSOpponentsSection *opponentsSection = [[RMSOpponentsSection alloc] initWithTitle:@"Opponents" cellIdentifier:@"OpponentCell" rows:[self opponents]]; _sections = @[cardSection, bookSection, opponentsSection]; } return _sections; } ! -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.sections count]; } ! -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return [self.sections[section] titleForHeader]; } ! -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.sections[section] numberOfRows]; } ! -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { return [self.sections[indexPath.section] tableView:tableView cellForRowAtIndex:indexPath.row]; }
  • 19. A Missing Section @protocol RMSTableViewSectionDelegate <NSObject> - (NSString *)titleForHeader; - (NSInteger)numberOfRows; - (UITableViewCell *)tableView:(UITableView *)tableView 
 cellForRowAtIndex:(NSUInteger)index; @end
  • 20. A Missing Section @protocol RMSTableViewSectionDelegate <NSObject> - (NSString *)titleForHeader; - (NSInteger)numberOfRows; - (UITableViewCell *)tableView:(UITableView *)tableView 
 cellForRowAtIndex:(NSUInteger)index; @end @interface RMSAbstractTableViewSection : NSObject<RMSTableViewSectionDelegate> @property (nonatomic) NSString *titleForHeader; @property (nonatomic) NSString *cellIdentifier; -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier -(NSArray *)rows; -(NSString *)textForRowAtIndex:(NSUInteger)index; -(UIImage *)imageForRowAtIndex:(NSUInteger)index; @end
  • 21. A Missing Section @protocol RMSTableViewSectionDelegate <NSObject> - (NSString *)titleForHeader; - (NSInteger)numberOfRows; - (UITableViewCell *)tableView:(UITableView *)tableView 
 cellForRowAtIndex:(NSUInteger)index; @end @interface RMSAbstractTableViewSection : NSObject<RMSTableViewSectionDelegate> @property (nonatomic) NSString *titleForHeader; @property (nonatomic) NSString *cellIdentifier; -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier -(NSArray *)rows; -(NSString *)textForRowAtIndex:(NSUInteger)index; -(UIImage *)imageForRowAtIndex:(NSUInteger)index; @end @interface RMSGenericTableViewSection : RMSAbstractTableViewSection @property (readonly) NSArray *rows; -(instancetype)initWithTitle:(NSString *)title 
 cellIdentifier:(NSString *)cellIdentifier rows:(NSArray *)rows; @end
  • 22. @implementation RMSAbstractTableViewSection static NSString *genericCellIdentifier = @"Cell"; ! -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier { self = [super init]; if (self) { _titleForHeader = title; _cellIdentifier = cellIdentifier; } return self; } ! -(NSString *)cellIdentifier { if (!_cellIdentifier) { _cellIdentifier = genericCellIdentifier; } return _cellIdentifier; } ! -(NSInteger)numberOfRows { return [[self rows] count]; } ! -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndex:(NSUInteger)index { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[self cellIdentifier]]; cell.textLabel.text = [self textForRowAtIndex:index]; cell.imageView.image = [self imageForRowAtIndex:index]; return cell; } ! ! @end
  • 23. @implementation RMSAbstractTableViewSection static NSString *genericCellIdentifier = @"Cell"; ! -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier { self = [super init]; if (self) { _titleForHeader = title; _cellIdentifier = cellIdentifier; } return self; } ! -(NSString *)cellIdentifier { if (!_cellIdentifier) { _cellIdentifier = genericCellIdentifier; } return _cellIdentifier; } ! -(NSInteger)numberOfRows { return [[self rows] count]; } ! -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndex:(NSUInteger)index { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[self cellIdentifier]]; cell.textLabel.text = [self textForRowAtIndex:index]; cell.imageView.image = [self imageForRowAtIndex:index]; return cell; } ! ! @end -(NSArray *)rows { return @[]; } ! -(NSString *)textForRowAtIndex:(NSUInteger)index { return [[self rows][index] description]; } ! -(UIImage *)imageForRowAtIndex:(NSUInteger)index { return nil; }
  • 25. @implementation RMSGenericTableViewSection ! -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier rows:(NSArray *)rows { self = [super initWithTitle:title cellIdentifier:cellIdentifier]; if (self) { _rows = rows; } return self; } ! @end A Common Section
  • 26. @implementation RMSViewController ! -(NSArray *)sections { if (!_sections) { RMSCardSection *cardSection = [[RMSCardSection alloc] initWithTitle:@"Cards" cellIdentifier:@"CardCell" player:self.player]; RMSBookSection *bookSection = [[RMSBookSection alloc] initWithTitle:@"Books" cellIdentifier:@"BookCell" player:self.player]; RMSOpponentsSection *opponentsSection = [[RMSOpponentsSection alloc] initWithTitle:@"Opponents" cellIdentifier:@"OpponentCell" rows:[self opponents]]; _sections = @[cardSection, bookSection, opponentsSection]; } return _sections; } ! -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.sections count]; } ! -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return [self.sections[section] titleForHeader]; } ! -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.sections[section] numberOfRows]; } ! -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { return [self.sections[indexPath.section] tableView:tableView cellForRowAtIndex:indexPath.row]; }
  • 27. @interface RMSCardSection : RMSAbstractTableViewSection @property RMSGoFishPlayer *player; -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier player:(RMSGoFishPlayer *)player; @end ! ! @implementation RMSCardSection @synthesize player = _player; ! -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier player:(RMSGoFishPlayer *)player { self = [super initWithTitle:title cellIdentifier:cellIdentifier]; if (self) { _player = player; }; return self; } ! -(NSArray *)rows { return [self.player hand]; } ! -(UIImage *)imageForRowAtIndex:(NSUInteger)index { RMSPlayingCard *card = [self rows][index]; return [UIImage imageNamed:[card imageName]]; } ! @end
  • 28. @interface RMSBookSection : RMSAbstractTableViewSection @property RMSGoFishPlayer *player; -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier player: (RMSGoFishPlayer *)player; @end ! ! @implementation RMSBookSection ! -(instancetype)initWithTitle:(NSString *)title cellIdentifier:(NSString *)cellIdentifier player: (RMSGoFishPlayer *)player { self = [super initWithTitle:title cellIdentifier:cellIdentifier]; if (self) { _player = player; }; return self; } ! -(NSArray *)rows { return [self.player books]; } ! -(UIImage *)imageForRowAtIndex:(NSUInteger)index { RMSPlayingCard *card = [self rows][index][0]; return [UIImage imageNamed:[card imageName]]; } ! -(NSString *)textForRowAtIndex:(NSUInteger)index { RMSPlayingCard *card = [self rows][index][0]; return [NSString stringWithFormat:@"%@s", card.rank]; } ! @end
  • 29. @interface RMSOpponentsSection : RMSGenericTableViewSection @end ! @implementation RMSOpponentsSection ! -(UIImage *)imageForRowAtIndex:(NSUInteger)index { return [UIImage imageNamed:@"backs_blue"]; } ! -(NSString *)textForRowAtIndex:(NSUInteger)index { RMSGoFishPlayer *opponent = [self rows][index]; return [NSString stringWithFormat:@"%@ (%d cards, %d books)", 
 opponent.name, 
 [opponent numberOfCards], 
 [[opponent books] count]]; } ! @end
  • 30. LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited. So What About Handling Selections? RMSTableViews
 https://github.com/RoleModel/RMSTableViews
  • 31. An Improved UITableViewController @interface RMSTableViewController : UITableViewController @property (nonatomic, strong, readonly) NSArray *sections; - (NSArray *)generateSections; - (void)sectionGenerationDidComplete; @end
  • 32. An Improved UITableViewController @interface RMSTableViewController : UITableViewController @property (nonatomic, strong, readonly) NSArray *sections; - (NSArray *)generateSections; - (void)sectionGenerationDidComplete; @end @interface RMSTableViewSection : NSObject @property (nonatomic, strong) NSString *headerTitle; @property (nonatomic, strong) NSString *footerTitle; - (NSInteger)rowCount; - (UITableViewCell *)cellForIndex:(NSInteger)index; @end !
  • 33. An Improved UITableViewController @interface RMSTableViewController : UITableViewController @property (nonatomic, strong, readonly) NSArray *sections; - (NSArray *)generateSections; - (void)sectionGenerationDidComplete; @end @interface RMSTableViewSection : NSObject @property (nonatomic, strong) NSString *headerTitle; @property (nonatomic, strong) NSString *footerTitle; - (NSInteger)rowCount; - (UITableViewCell *)cellForIndex:(NSInteger)index; @end ! ! @protocol RMSTableViewCell <NSObject> @optional - (void)respondToSelection; @end
  • 34. @implementation RMSTableViewController ! - (NSArray *)generateSections { return @[]; } ! - (void)sectionGenerationDidComplete { } ! - (NSArray *)sections { if (_sections == nil) { _sections = [self generateSections]; [self sectionGenerationDidComplete]; } return _sections; } ! - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; id cell = [tableView cellForRowAtIndexPath:indexPath]; if ([cell respondsToSelector:@selector(respondToSelection)]) { [cell respondToSelection]; } } …
  • 35. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { return [self.sections[indexPath.section] cellForIndex:indexPath.row]; } ! - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.sections count]; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex { return [self.sections[sectionIndex] rowCount]; } ! - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection: (NSInteger)sectionIndex { RMSTableViewSection *section = self.sections[sectionIndex]; return section.headerTitle; } ! - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection: (NSInteger)sectionIndex { RMSTableViewSection *section = self.sections[sectionIndex]; return section.footerTitle; } ! @end
  • 36. @implementation RMSTableViewSection ! - (NSInteger)rowCount { @throw [NSException exceptionWithName:NSGenericException reason:@"Subclasses must implement rowCount" userInfo:nil]; } ! - (UITableViewCell *)cellForIndex:(NSInteger)index { @throw [NSException exceptionWithName:NSGenericException reason:@"Subclasses must implement cellForIndex:" userInfo:nil]; } ! @end
  • 37. Sections Were Missing, but… • In a dynamic table, they just about always do the same thing • Identify the represented objects • Provide the cell • The cell is unique • Don’t put the intelligence in the Section, but in the Cell
  • 38. @interface RMSDynamicSection : RMSTableViewSection @property (nonatomic, strong) UITableView *tableView; @property (nonatomic, strong) NSString *cellIdentifier; @property (nonatomic, strong) NSArray *representedObjects; - (id)initWithTableView:(UITableView *)tableView cellIdentifier:(NSString *)cellIdentifier; @end ! @implementation RMSDynamicSection ! -(id)initWithTableView:(UITableView *)tableView 
 cellIdentifier:(NSString *)cellIdentifier { self = [super init]; if (self) { _tableView = tableView; _cellIdentifier = cellIdentifier; } return self; } ! - (NSInteger)rowCount { return [self.representedObjects count]; } ! - (UITableViewCell *)cellForIndex:(NSInteger)index { id cell = [self.tableView dequeueReusableCellWithIdentifier:self.cellIdentifier]; if ([cell respondsToSelector:@selector(bindObject:)]) { [cell bindObject:self.representedObjects[index]]; } return cell; } @end
  • 39. @implementation RMSWordCell ! - (void)bindObject:(id)object { self.textLabel.text = object; } ! @end
  • 40. @implementation RMSWordCell ! - (void)bindObject:(id)object { self.textLabel.text = object; } ! @end @implementation RMSCardCell ! - (void)bindObject:(id)object { RMSPlayingCard *card = object; self.textLabel.text = [card description]; self.imageView.image = = [UIImage imageNamed:[card imageName]]; } ! @end
  • 41. LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited. So What About The “Form View”? Data editing, Settings views…
  • 42. The Form Descriptor •Defines the structure of a form
 •Can be expressed in code, as a Plist, or JSON
 •Consists of an array of section dictionaries
 •Section dictionaries specify section properties and contain an array of “row” dictionaries 
 •Row dictionaries describe cells
  • 43.
  • 44. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *valueCellIdentifier = @"ValueCell"; static NSString *buttonCellIdentifier = @"ButtonCell"; UITableViewCell *cell = nil; if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) { cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } } if (indexPath.section == FKProfileSectionIndexRequired) { cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier]; cell.selectionStyle = UITableViewCellSelectionStyleNone; UILabel *label = (UILabel *)[cell viewWithTag:10]; UITextField *textField = (UITextField *)[cell viewWithTag:11]; textField.secureTextEntry = NO; textField.keyboardType = UIKeyboardTypeDefault; UITableViewController *twin = self; if (indexPath.row == 0) { label.text = @"First name"; textField.text = self.profile.firstname; textField.delegate = self.firstNameObserver; } else if (indexPath.row == 1) { label.text = @"Last name"; textField.text = self.profile.lastname; textField.delegate = self.lastNameObserver; [self.firstNameObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 2) { label.text = @"Email"; textField.text = self.profile.email; textField.delegate = self.emailObserver; textField.keyboardType = UIKeyboardTypeEmailAddress; [self.lastNameObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 3) { label.text = @"Password"; textField.text = self.passwordOne; textField.secureTextEntry =YES; textField.delegate = self.passwordOneObserver; [self.emailObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 4) { label.text = @"Confirm"; textField.text = self.passwordTwo; textField.secureTextEntry =YES; textField.delegate = self.passwordTwoObserver; [self.passwordOneObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } } else if (indexPath.section == FKProfileSectionIndexGeneral) { if (indexPath.row == 0) { cell.textLabel.text = @"Gender"; cell.detailTextLabel.text = self.profile.gender; } else if (indexPath.row == 1) { cell.textLabel.text = @"Birthday"; cell.detailTextLabel.text = [NSDateFormatter localizedStringFromDate:self.profile.birthday dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterNoStyle]; } else if (indexPath.row == 2) { cell.textLabel.text = @"Height"; if (self.profile.height != nil) { NSInteger height = [self.profile.height integerValue]; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d' %d"", height / 12, height % 12]; } else { cell.detailTextLabel.text = nil; } } } else if (indexPath.section == FKProfileSectionIndexGolf) { if (indexPath.row == 0) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.text = @"Handicap"; cell.detailTextLabel.text = [self.profile.handicap description]; } else if (indexPath.row == 1) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.text = @"Stance"; cell.detailTextLabel.text = self.profile.stance; } } else if (indexPath.section == FKProfileSectionIndexSave || indexPath.section == FKProfileSectionIndexDelete) { cell = [tableView dequeueReusableCellWithIdentifier:buttonCellIdentifier]; NSUInteger labelTag = 1919; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:buttonCellIdentifier]; UIView *contentView = [cell contentView]; UILabel *label = [[UILabel alloc] initWithFrame:[contentView frame]]; [label setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; label.tag = labelTag; UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]]; [label setFont:boldFont]; [label setTextAlignment:NSTextAlignmentCenter]; [label setBackgroundColor:[UIColor clearColor]]; [contentView addSubview:label]; label.text = @"Save"; } if (indexPath.section == FKProfileSectionIndexSave) { cell.textLabel.textColor = [UIColor blackColor]; if (self.inRegistrationMode) { UILabel *label = (UILabel *)[cell viewWithTag:labelTag]; label.text = @"Join"; } ! if ([self profileInformationIsValid]) { cell.selectionStyle = UITableViewCellSelectionStyleGray; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]]; cell.backgroundColor = [UIColor darkGrayColor]; ! } else { cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.backgroundColor = [UIColor lightGrayColor]; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor grayColor]]; } } else if (indexPath.section == FKProfileSectionIndexDelete) { UILabel *label = (UILabel *)[cell viewWithTag:labelTag]; label.text = @"Delete"; cell.selectionStyle = UITableViewCellSelectionStyleNone; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]]; cell.backgroundColor = [UIColor redColor]; } } return cell; }
  • 45. @interface RMSProfileViewController () !@property (nonatomic, strong) RMSButtonCell *saveButton; @property (nonatomic, strong) RMSFormSection *accountSection; @property (nonatomic, strong) NSString *passwordOne; @property (nonatomic, strong) NSString *passwordTwo; !@end !@implementation RMSProfileViewController !#pragma mark plist object mapping !- (NSDictionary *)objectSubstitionDictionary { return @{ @":self" : self, @":profile" : self.profile, @":whiteColor" : [UIColor whiteColor], @":blackColor" : [UIColor blackColor], @":redColor" : [UIColor redColor], @":saveLabel" : self.inRegistrationMode ? @"Join" : @"Save", @":emailEnabled" : @(![self.profile isDefaultBool]), @":passwordsEnabled" : @(self.inRegistrationMode), @":deleteEnabled" : @(![self inRegistrationMode] && self.profile.canBeDeleted), @":genders" : @[@"male", @"female"] }; } !#pragma mark lifecycle !- (void)viewDidLoad { DLog(@""); [super viewDidLoad]; !self.saveButton.enabled = [self profileInformationIsValid]; !self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Settings" style:UIBarButtonItemStyleBordered target:self action:@selector(settingsButtonAction:)]; } !- (void)viewDidAppear:(BOOL)animated { DLog(@""); [super viewDidAppear:animated]; ![self startObserving]; } !- (void)dealloc { [self stopObserving]; } !- (void)startObserving { for (NSString *key in [RMSProfile attributes]) { [self.profile addObserver:self forKeyPath:key options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL]; } } !- (void)stopObserving { for (NSString *key in [RMSProfile attributes]) { [self.profile removeObserver:self forKeyPath:key]; } } !- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { self.title = self.profile.fullName; [self updateSaveButtonStatus]; !NSUInteger saveSectionIndex = [[self.tableView indexPathForCell:self.saveButton] section]; ![self.tableView reloadSections:[NSIndexSet indexSetWithIndex:saveSectionIndex] withRowAnimation:UITableViewRowAnimationNone]; } !- (void)updateSaveButtonStatus { self.saveButton.enabled = [self profileInformationIsValid]; } !- (void)updatePasswordMessage { [self updateSaveButtonStatus]; NSString *existingFooter = self.accountSection.footerTitle ? self.accountSection.footerTitle : @""; !if (self.passwordOne.length > 0 && self.passwordTwo.length > 0) { if (![self.passwordOne isEqualToString:self.passwordTwo]) { self.accountSection.footerTitle = @"Passwords don't match"; } else if (self.passwordOne.length < 6) { self.accountSection.footerTitle = @"Passwords must be at least 6 characters long"; } else { self.accountSection.footerTitle = @""; } } else { self.accountSection.footerTitle = @""; } !if (![self.accountSection.footerTitle isEqualToString:existingFooter]) { [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[self.sections indexOfObject:self.accountSection]] withRowAnimation:UITableViewRowAnimationNone]; } } !- (void)setPasswordOne:(NSString *)passwordOne { _passwordOne = passwordOne; [self updatePasswordMessage]; } !- (void)setPasswordTwo:(NSString *)passwordTwo { _passwordTwo = passwordTwo; [self updatePasswordMessage]; } !#pragma mark actions !- (void)settingsButtonAction:(id)sender { RMSSettingsViewController *settingsViewController = [[RMSSettingsViewController alloc] initWithStyle:UITableViewStyleGrouped descriptorNamed:@"RMSSettingsViewController.json"]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:settingsViewController]; [self.navigationController presentViewController:navigationController animated:YES completion:nil]; } !- (void)saveAction:(id)sender { if ([self profileInformationIsValid]) { if (self.inRegistrationMode) { [self registerUser]; } else { [self save]; } } } !- (void)deleteAction:(id)sender { [self delete]; } !- (void)pushWordViewController { RMSWordViewController *wordViewController = [[RMSWordViewController alloc] initWithStyle:UITableViewStylePlain]; [self.navigationController pushViewController:wordViewController animated:YES]; } !#pragma mark real work would occur here !- (void)delete { [UIAlertView showAlertWithMessage:@"DELETE"]; } !- (void)save { [UIAlertView showAlertWithMessage:@"SAVE"]; } !- (void)registerUser { [UIAlertView showAlertWithMessage:@"REGISTER"]; } !#pragma mark validation, sort of !- (BOOL)profileInformationIsValid { BOOL profileInformationIsValid = [self.profile isValid]; if (self.inRegistrationMode) { profileInformationIsValid = profileInformationIsValid && ([self validateEmail:self.profile.email] && [self passwordsArePossiblyValid]); } return profileInformationIsValid; } !- (BOOL)passwordsArePossiblyValid { return (self.passwordOne.length >= 6 && [self.passwordOne isEqualToString:self.passwordTwo]); } !- (BOOL)validateEmail:(NSString *)candidate { NSString *emailRegex = @"(?:[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[A-Za-z0-9!#$%&'*+/=?^_`{|}" @"~-]+)*|"(?:[x01-x08x0bx0cx0e-x1fx21x23-x5bx5d-" @"x7f]|[x01-x09x0bx0cx0e-x7f])*")@(?:(?:[A-Za-z0-9](?:[a-" @"z0-9-]*[A-Za-z0-9])?.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?|[(?:(?:25[0-5" @"]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-" @"9][0-9]?|[A-Za-z0-9-]*[A-Za-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21" @"-x5ax53-x7f]|[x01-x09x0bx0cx0e-x7f])+)])"; NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex]; return [emailTest evaluateWithObject:candidate]; } !@end - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *valueCellIdentifier = @"ValueCell"; static NSString *buttonCellIdentifier = @"ButtonCell"; UITableViewCell *cell = nil; if (indexPath.section == FKProfileSectionIndexGeneral || indexPath.section == FKProfileSectionIndexGolf) { cell = [tableView dequeueReusableCellWithIdentifier:valueCellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:valueCellIdentifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } } if (indexPath.section == FKProfileSectionIndexRequired) { cell = [tableView dequeueReusableCellWithIdentifier:FKTextEntryCellIdentifier]; cell.selectionStyle = UITableViewCellSelectionStyleNone; UILabel *label = (UILabel *)[cell viewWithTag:10]; UITextField *textField = (UITextField *)[cell viewWithTag:11]; textField.secureTextEntry = NO; textField.keyboardType = UIKeyboardTypeDefault; UITableViewController *twin = self; if (indexPath.row == 0) { label.text = @"First name"; textField.text = self.profile.firstname; textField.delegate = self.firstNameObserver; } else if (indexPath.row == 1) { label.text = @"Last name"; textField.text = self.profile.lastname; textField.delegate = self.lastNameObserver; [self.firstNameObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 2) { label.text = @"Email"; textField.text = self.profile.email; textField.delegate = self.emailObserver; textField.keyboardType = UIKeyboardTypeEmailAddress; [self.lastNameObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 3) { label.text = @"Password"; textField.text = self.passwordOne; textField.secureTextEntry =YES; textField.delegate = self.passwordOneObserver; [self.emailObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } else if (indexPath.row == 4) { label.text = @"Confirm"; textField.text = self.passwordTwo; textField.secureTextEntry =YES; textField.delegate = self.passwordTwoObserver; [self.passwordOneObserver setNextTextField:textField withBlock:^{ [twin.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }]; } } else if (indexPath.section == FKProfileSectionIndexGeneral) { if (indexPath.row == 0) { cell.textLabel.text = @"Gender"; cell.detailTextLabel.text = self.profile.gender; } else if (indexPath.row == 1) { cell.textLabel.text = @"Birthday"; cell.detailTextLabel.text = [NSDateFormatter localizedStringFromDate:self.profile.birthday dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterNoStyle]; } else if (indexPath.row == 2) { cell.textLabel.text = @"Height"; if (self.profile.height != nil) { NSInteger height = [self.profile.height integerValue]; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d' %d"", height / 12, height % 12]; } else { cell.detailTextLabel.text = nil; } } } else if (indexPath.section == FKProfileSectionIndexGolf) { if (indexPath.row == 0) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.text = @"Handicap"; cell.detailTextLabel.text = [self.profile.handicap description]; } else if (indexPath.row == 1) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.text = @"Stance"; cell.detailTextLabel.text = self.profile.stance; } } else if (indexPath.section == FKProfileSectionIndexSave || indexPath.section == FKProfileSectionIndexDelete) { cell = [tableView dequeueReusableCellWithIdentifier:buttonCellIdentifier]; NSUInteger labelTag = 1919; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:buttonCellIdentifier]; UIView *contentView = [cell contentView]; UILabel *label = [[UILabel alloc] initWithFrame:[contentView frame]]; [label setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; label.tag = labelTag; UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]]; [label setFont:boldFont]; [label setTextAlignment:NSTextAlignmentCenter]; [label setBackgroundColor:[UIColor clearColor]]; [contentView addSubview:label]; label.text = @"Save"; } if (indexPath.section == FKProfileSectionIndexSave) { cell.textLabel.textColor = [UIColor blackColor]; if (self.inRegistrationMode) { UILabel *label = (UILabel *)[cell viewWithTag:labelTag]; label.text = @"Join"; } ! if ([self profileInformationIsValid]) { cell.selectionStyle = UITableViewCellSelectionStyleGray; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]]; cell.backgroundColor = [UIColor darkGrayColor]; ! } else { cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.backgroundColor = [UIColor lightGrayColor]; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor grayColor]]; } } else if (indexPath.section == FKProfileSectionIndexDelete) { UILabel *label = (UILabel *)[cell viewWithTag:labelTag]; label.text = @"Delete"; cell.selectionStyle = UITableViewCellSelectionStyleNone; [(UILabel *)[cell viewWithTag:labelTag] setTextColor:[UIColor whiteColor]]; cell.backgroundColor = [UIColor redColor]; } } return cell; }
  • 46.
  • 48. Top level items describe Sections Rows in Sections describe Cells
  • 49. Top level items describe Sections Rows in Sections describe Cells Various Generic Form Cells already available
  • 50. Top level items describe Sections Rows in Sections describe Cells Various Generic Form Cells already available Things that don’t go well in Plists can identify key to have controller interpret & substitute
  • 51.
  • 52. Can use JSON if you don’t like Plists
  • 53. - (NSArray *)generateSections { NSMutableArray *sections = [NSMutableArray array]; ! for (NSDictionary *rawSection in self.descriptor) { /* Sections and Cells are built here */ } return sections; } The form descriptor drives the construction of the table view
  • 54. ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptor:(NSArray *)descriptor; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptorNamed:(NSString *)descriptorName; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 rawDescriptor:(NSData *)rawDescriptor 
 type:(RMSFormDescriptorType)descriptorType; Form View Controller Instantiation
  • 55. ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptor:(NSArray *)descriptor; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptorNamed:(NSString *)descriptorName; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 rawDescriptor:(NSData *)rawDescriptor 
 type:(RMSFormDescriptorType)descriptorType; Form View Controller Instantiation Designated Initializer
  • 56. ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptor:(NSArray *)descriptor; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptorNamed:(NSString *)descriptorName; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 rawDescriptor:(NSData *)rawDescriptor 
 type:(RMSFormDescriptorType)descriptorType; Form View Controller Instantiation Resource- based initializer (Plist or JSON)
  • 57. ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptor:(NSArray *)descriptor; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptorNamed:(NSString *)descriptorName; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 rawDescriptor:(NSData *)rawDescriptor 
 type:(RMSFormDescriptorType)descriptorType; Form View Controller Instantiation Data-based initializer (Plist or JSON)
  • 59. Demo Building a Simple Form • Section definition

  • 60. Demo Building a Simple Form • Section definition
 • Data binding

  • 61. Demo Building a Simple Form • Section definition
 • Data binding
 • Object substitution

  • 62. Demo Building a Simple Form • Section definition
 • Data binding
 • Object substitution
 • Cell properties
  • 63. ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptor:(NSArray *)descriptor; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptorNamed:(NSString *)descriptorName; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 rawDescriptor:(NSData *)rawDescriptor 
 type:(RMSFormDescriptorType)descriptorType; FormView Controller Instantiation
  • 64. ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptor:(NSArray *)descriptor; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 descriptorNamed:(NSString *)descriptorName; ! ! - (id)initWithStyle:(UITableViewStyle)style 
 rawDescriptor:(NSData *)rawDescriptor 
 type:(RMSFormDescriptorType)descriptorType; FormView Controller Instantiation Supports externalizing the form descriptor
  • 65. Demo
  • 67. Demo • Resource-based Plist
 • Resource-based JSON

  • 68. Demo • Resource-based Plist
 • Resource-based JSON
 • Web-based JSON
  • 69. Demo • Resource-based Plist
 • Resource-based JSON
 • Web-based JSON
  • 71. Summary • TableViews often require much tedium when not in the simplest form
  • 72. Summary • TableViews often require much tedium when not in the simplest form • Code becomes hard to parse
  • 73. Summary • TableViews often require much tedium when not in the simplest form • Code becomes hard to parse • Few interesting things happening (mostly tying objects to the right cells)
  • 74. Summary • TableViews often require much tedium when not in the simplest form • Code becomes hard to parse • Few interesting things happening (mostly tying objects to the right cells) • Sections are objects that are implied but missing from the Object Model
  • 75. Summary • TableViews often require much tedium when not in the simplest form • Code becomes hard to parse • Few interesting things happening (mostly tying objects to the right cells) • Sections are objects that are implied but missing from the Object Model • Make Cells more intelligent about the objects they represent
  • 76. LEGAL: This document may contain trademarked logos, intellectual property, and confidential information. If you are not the intended recipient or an authorized agent, then this is notice to you that the dissemination, distribution or copying this document is prohibited. UITableView
 Pain Reliever https://github.com/RoleModel/RMSTableViews Ken Auer ken.auer@rolemodelsoftware.com @kauerrolemodel
 Tony Ingraldi
 tony.ingraldi@rolemodelsoftware.com Available as a CocoaPod, docs on cocoadocs.org