4

I am actually very surprised how difficult it is to keep your code well structured and readable when doing iphone app stuff... but it might be because I am doing something wrong.

I have a Sign Up page containing different kinds of inline-editable data: birthday, gender, name, password, phone number. I have made the page as a table view of custom cells where each cell is an instance of a sub class of UITableViewCell and is read from its own nib file. This because I thought that I then might be able to reuse these different kinds of cells later in other table view pages.

The approach of encapsulating the different custom cells in their own place is not working that well though:

  1. Who is going to be the controller of e.g the gender picker inside the GenderCell?

  2. How am I actually going to reuse the cells in a different table view controller when I had to put the SignUpController as the file's owner of the cell nib files?

I do not know if anyone but myself understood what I just wrote, but if so, I would be very thankful for any suggestions on how to structure my code differently.

Thanks a lot,
Stine

To make things more clear (?!) let me paste some of my code in here:

EditableLabel.h

@interface EditableLabel : UILabel {   
    UIView *inputView, *inputAccessoryView; 
}

@property (nonatomic, retain) UIView *inputView, *inputAccessoryView; 

- (void) setInputView:(UIView *)aView andToolbar:(UIToolbar *)aToolbar;

@end

EditableLabel.m

@implementation EditableLabel

@synthesize inputView, inputAccessoryView;

- (void) dealloc {
    [inputView release];
    [inputAccessoryView release];
    [super dealloc];
}

- (void) setInputView:(UIView *)aView andToolbar:(UIToolbar *)aToolbar {
    self.inputAccessoryView = aToolbar;
    self.inputView = aView;
}

- (BOOL) canBecomeFirstResponder {
    return YES;
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self becomeFirstResponder];
}

@end

EditableCell.h

typedef enum {
    USERNAME, PASSWORD, MOBILE, BIRTHDAY, GENDER, DESCRIPTION, CATEGORY
} CellTag;

@interface EditableCell : UITableViewCell {
    CellTag tag;    
    UIView *editPoint;
    IBOutlet UILabel *headerLabel;
}

- (void) setTag:(CellTag)aTag andHeader:(NSString *)aHeader andEditPoint:(UIView *)aView;

@property (nonatomic) CellTag tag;
@property (nonatomic, retain) UIView *editPoint;
@property (nonatomic, retain) UILabel *headerLabel;

- (IBAction) editingDone:(id)sender;

- (void) showInputView;
- (void) hideInputView;

@end

EditableCell.m

@implementation EditableCell

@synthesize tag, editPoint, headerLabel;

- (void) dealloc {
    [editPoint release];
    [headerLabel release];
    [super dealloc];
}

- (void) setTag:(CellTag)aTag andHeader:(NSString *)aHeader andEditPoint:(UIView *)aView {
    self.tag = aTag;
    self.headerLabel.text = aHeader;
    self.editPoint = aView;
}

- (IBAction) editingDone:(id)sender {
    [self hideInputView];
}

- (void) showInputView {   
    [self.editPoint becomeFirstResponder];
}

- (void) hideInputView {
    [self.editPoint resignFirstResponder];    
}

@end

EditableLabelCell.h

@interface EditableLabelCell : EditableCell {
    IBOutlet UILabel *placeHolderLabel;
    IBOutlet EditableLabel *editableLabel;
}

@property (nonatomic, retain) UILabel *placeHolderLabel;
@property (nonatomic, retain) EditableLabel *editableLabel;

- (void) setTag:(CellTag)aTag 
      andHeader:(NSString *)aHeader 
 andPlaceHolder:(NSString *)aPlaceHolder
   andInputView:(UIView *)aView
     andToolbar:(UIToolbar *)aToolbar;

- (void) setValue:(NSString *)aValue;

@end

EditableLabelCell.m

@implementation EditableLabelCell

@synthesize placeHolderLabel, editableLabel;

- (void) dealloc {
    [placeHolderLabel release];
    [editableLabel release];
    [super dealloc];
}

- (void) setTag:(CellTag)aTag andHeader:(NSString *)aHeader andPlaceHolder:(NSString *)aPlaceHolder andInputView:(UIView *)aView andToolbar:(UIToolbar *)aToolbar {
    [super setTag:aTag andHeader:aHeader andEditPoint:self.editableLabel];
    self.placeHolderLabel.text = aPlaceHolder;
    [self.editableLabel setInputView:aView andToolbar:aToolbar];    
}

- (void) setValue:(NSString *)aValue {
    if (aValue && aValue != @"") {                
        self.placeHolderLabel.hidden = YES;
        self.editableLabel.text = aValue;        
    } else {
        self.editableLabel.text = nil;
        self.placeHolderLabel.hidden = NO;
    }
}

@end

EditableGenderCell.h

@protocol EditableGenderCellDelegate <NSObject>
@required
  - (NSString *) getTextForGender:(Gender)aGender;
  - (void) genderChangedTo:(Gender)aGender forTag:(CellTag)aTag;
@end

@interface EditableGenderCell : EditableLabelCell <UITableViewDataSource, UITableViewDelegate> {
    id<EditableGenderCellDelegate> delegate;    
    Gender gender;
    IBOutlet UITableView *genderTable;
    IBOutlet UIToolbar *doneBar;
}

- (void) setTag:(CellTag)aTag 
    andDelegate:(id<EditableGenderCellDelegate>)aDelegate 
      andHeader:(NSString *)aHeader 
      andGender:(Gender)aGender
 andPlaceHolder:(NSString *)aPlaceHolder;

@property (nonatomic, retain) id<EditableGenderCellDelegate> delegate;
@property (nonatomic) Gender gender;
@property (nonatomic, retain) UITableView *genderTable;
@property (nonatomic, retain) UIToolbar *doneBar;

@end

EditableGenderCell.m

@implementation EditableGenderCell

@synthesize delegate, gender, genderTable, doneBar;

- (void) dealloc {
    [delegate release];
    [genderTable release];
    [doneBar release];
    [super dealloc];
}

- (void) setTag:(CellTag)aTag andDelegate:(id<EditableGenderCellDelegate>)aDelegate andHeader:(NSString *)aHeader andGender:(Gender)aGender andPlaceHolder:(NSString *)aPlaceHolder {
    [super setTag:aTag andHeader:aHeader andPlaceHolder:aPlaceHolder andInputView:self.genderTable andToolbar:self.doneBar];
    self.delegate = aDelegate;
    self.gender = aGender;
    [super setValue:[self.delegate getTextForGender:aGender]];
}

#pragma mark - Table view data source

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

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

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    switch (indexPath.row) {
        case MALE:
            switch (self.gender) {
                case MALE:
                    cell.accessoryType = UITableViewCellAccessoryCheckmark;    
                    break;
                default:
                    cell.accessoryType = UITableViewCellAccessoryNone;  
            } 
            break;
        case FEMALE:
            switch (self.gender) {
                case FEMALE:
                    cell.accessoryType = UITableViewCellAccessoryCheckmark;    
                    break;
                default:
                    cell.accessoryType = UITableViewCellAccessoryNone; 
            }  
            break;
    }

    cell.textLabel.text = [self.delegate getTextForGender:indexPath.row];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;

    return cell;
}

#pragma mark - Table view delegate

- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    self.gender = indexPath.row;
    [super setValue:[self.delegate getTextForGender:self.gender]];
    [self.delegate genderChangedTo:self.gender forTag:self.tag];   
    [tableView reloadData];
}

@end
Stine
  • 1,605
  • 5
  • 23
  • 44
  • If you created the cell using nib it will have only one owner that is the controller. On way of reusing the cell is for example create a cell with text field and label and use it for birthday, password etc and here the file owner will be the same. – Sarah May 16 '11 at 12:31
  • So I can never reuse custom cells across several table controllers? – Stine May 16 '11 at 12:35
  • It arise some questions: 1.Why are you using a table view as signup page? 2. how you want to reuse the cells(even they release automatically), and finally it'll be easy to what u r doing if you provide some relevant chunks of your code. – rptwsthi May 16 '11 at 12:38
  • To get the grouped table view looks and features (e.g. the bouncing when some input view becomes first responder) for free? :) Is there a better way? – Stine May 16 '11 at 12:39
  • You can use animation, for bouncing, and even scroll view, for other sort of stuff, – rptwsthi May 16 '11 at 12:44
  • But it feels kind of strange to invent everything all over again? :/ And as I am very new to all this it is not very straight forward to me how to do it either. – Stine May 16 '11 at 12:47
  • **This because I thought that I then might be able to reuse these different kinds of cells later in other table view pages** what you meant with this line, are you using navigation bar? – rptwsthi May 16 '11 at 12:55
  • There is nothing wrong with using a table view as a signup page. –  May 16 '11 at 13:04
  • @Stine you should have a look at my answer below. – Nick Weaver May 16 '11 at 13:13
  • @rptwsthi All I meant by that was that I would find it nice if I could reuse e.g. the class GenderCell somewhere else in my code. – Stine May 16 '11 at 13:27
  • @Stine you can reuse custom cells across several table controllers if you create the cell programmatically. – Sarah May 16 '11 at 13:38
  • @Sarah Yes, but I have this feeling that I am better at drawing the cells than programming them ;) – Stine May 16 '11 at 13:49
  • 1
    @Stine Reading through your code, just a hint: you should keep a weak reference on a delegate, assign it instead of retaining. Have a look at [property “assign” and “retain” for delegate](http://stackoverflow.com/questions/5176261/property-assign-and-retain-for-delegate). – Nick Weaver May 16 '11 at 14:00
  • @Nick Amazing that you actually read through my code! I am very thankful for that and will try to understand your hint :) – Stine May 16 '11 at 14:06
  • Regarding the second point in my question I think I managed to use NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"EditableGenderCell" owner:nil options:nil]; self.genderCell = (EditableGenderCell *)[nib objectAtIndex:0]; instead of setting my Sign Up controller as the owner of the nib file and then use [[NSBundle mainBundle] loadNibNamed:@"EditableGenderCell" owner:self options:nil];. In this way I will actually be able to read a gender cell from its nib file in more than one table view controller – Stine May 16 '11 at 14:06
  • @Stine looks like as if you had have a look at my code too ;) – Nick Weaver May 16 '11 at 14:08

2 Answers2

4

Have a look at my answer to How to make a UITableViewCell with different subviews reusable?.

You should back up your custom tableviewcell nib files with a custom class representing your cell and encapsulate logic in it for example your gender picker. If you need to inform some outside controller of the gender you can make use of the delegate pattern.

Custom Gender picker in a TableViewCell

Ok let's start with the nib file, looking like this:

enter image description here

view hierarchy, don't set the File's Owner...

enter image description here

...set instead the class of the table view cell of your custom class:

enter image description here

The custom class

As you can see the class is almost empty, only providing the segmented control as property

GenderPickerTableViewCell.h

@interface GenderPickerTableViewCell : UITableViewCell 
{
    UISegmentedControl *genderPickerSegmentedControl;
}

@property (nonatomic, retain) IBOutlet UISegmentedControl *genderPickerSegmentedControl;

@end

GenderPickerTableViewCell.m

#import "GenderPickerTableViewCell.h"


@implementation GenderPickerTableViewCell

@synthesize genderPickerSegmentedControl;

#pragma mark -
#pragma mark memory management

- (void)dealloc 
{   
    [genderPickerSegmentedControl release];
    
    [super dealloc];
}

#pragma mark -
#pragma mark initialization

- (void)awakeFromNib
{
    // initialization goes here, for example preselect a specific gender
}

@end

The table view using our new cell

I'll provide only the necessary methods to make this work. The TableViewCellFactory class is just a nib loader like I posted in my referenced answer above. The genderPickerTableViewCellWithTableView is just a convenience class method to return that special kind of cell without too much boilerplate code

The last important thing to note is the configuration of the cell, this is kept simple, I just access the segmented control directly and add a target to it which informs this view controller about a change.

#pragma mark -
#pragma mark view lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    tableView.rowHeight = 100.0;
    tableView.dataSource = self;
    tableView.delegate = self;
}

#pragma mark -
#pragma mark UITableView methods

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

- (UITableViewCell *) tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)anIndexPath
{
    GenderPickerTableViewCell *cell = [TableViewCellFactory genderPickerTableViewCellWithTableView:aTableView];

    [cell.genderPickerSegmentedControl addTarget:self 
                                          action:@selector(genderPicked:) 
                                forControlEvents:UIControlEventValueChanged];
    
    return cell;
}

#pragma mark -
#pragma mark UISegmentedControl action

- (void)genderPicked:(id)sender
{
    UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
    
    NSLog(@"selected index: %d", [segmentedControl selectedSegmentIndex]);
}

I hope this helps a bit for the beginning.

Community
  • 1
  • 1
Nick Weaver
  • 47,228
  • 12
  • 98
  • 108
  • But encapsulating the gender picker stuff inside my GenderCell forces me to make a cell view a controller? Which I have actually done... but just seems a bit wrong having a view being a controller... – Stine May 16 '11 at 13:30
  • @Stine a quote from [UIView](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html) documentation: `At runtime, a view object handles the rendering of any content in its area and also handles any interactions with that content.` Put the interaction level into the view such as picking the gender and some code to let a controller or whatever instance know that something has changed, for example delegation pattern. You can also make use of the target action pattern or notifications if you like. – Nick Weaver May 16 '11 at 13:47
  • @Nick Oh, so the way I have implemented my gender cell (code pasted in below my question) might actually be okay?! =) That would be so nice! I will read the UIView documentation more thorough!! And also the answer to which you posted a link of course. – Stine May 16 '11 at 13:57
  • @Stine To be honest I am a bit confused: You are making the tableViewCells the delegate and datasource for your tableView? – Nick Weaver May 16 '11 at 14:02
  • @Nick The reason why `EditableGenderCell` is a `UITableViewDataSource` and a `UITableViewDelegate` is that I have implemented my gender picker as a table view... hmm, did that explain anything? :/ – Stine May 16 '11 at 14:17
  • @Stine You put a tableView in a tableViewCell which you put in a tableView? :) – Nick Weaver May 16 '11 at 14:21
  • @Nick Yup! 8] Isn't that just the way you would have done it? ;D – Stine May 16 '11 at 14:29
  • @Stine uhm, actually no ... :) Let's I have a form with email field and a gender picker implemented with a segmentedControl and an age slide with a UISlide and all that should be in a tableView, I'd create three tableViewCell nib files with the corresponding controls in it and set the view controller as the target for the UIControl descendants and as delegate for those controls who offer a delegate protocol. – Nick Weaver May 16 '11 at 14:35
  • @Stine ... for those controls which need some special behaviour I'd put it in the implementation part of the cells. If your approach works, why not, though it's too much for my simple thinking brain and confuses me too much. – Nick Weaver May 16 '11 at 14:37
  • @Nick I would really like to understand what you are saying as I have the feeling you are right!! But I am not that strong in the terminology I'm afraid *sigh* ... One of the things you are saying is that you would not recommend a gender picker implemented as a table view with two entries which can be checked/unchecked? – Stine May 16 '11 at 14:52
  • @Stine alright, give me some minutes, I'll try to make a small example. – Nick Weaver May 16 '11 at 15:03
  • @Stine here you go with a simple example. – Nick Weaver May 16 '11 at 15:37
  • @Nick Cool, thanks!! I see how you set the table view controller as the target of changes to the segmented control just like you tried to explain to me above :) Whether I am capable of transferring this approach to my gender cell having a table view in its stomach I am not so sure :/ But I really appreciate your help! – Stine May 16 '11 at 15:56
  • @Stine glad to be of help. If you need more help come back, this is just an easy approach, and you could do all things of stuff with cells. – Nick Weaver May 16 '11 at 18:47
1

This is the code you can use for bouncing:

 -(IBAction)textFieldDidBeginEditing:(UITextField *)textField { //Keyboard becomes visible
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.3];
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y - 50, self.view.frame.size.width, self.view.frame.size.height);
[UIView commitAnimations];

}

Just set y accordingly an call it when editing begins.

rptwsthi
  • 10,094
  • 10
  • 68
  • 109