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

ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
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
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningVitsRangannavar
 
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.
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfPower Karaoke
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationkaushalgiri8080
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsMehedi Hasan Shohan
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 

Recently uploaded (20)

ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
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...
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learning
 
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...
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdf
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanation
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software Solutions
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 

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