How to hookup dataSource and delegate UITableViewDataSource

Leave a comment

I always forget this. To hookup your your UITableView to your ViewController always remember to control drag from your outlets to the ViewController.

You can do it by dragging your table up to your viewController like this all in the single pane.

Screen Shot 2016-09-28 at 4.39.44 PM.png

Or you can drag across the screen by selecting your table and dragging from the outlets on the right to your viewController on the left.

Screen Shot 2016-09-28 at 8.49.10 AM.png

Screen Shot 2016-09-28 at 8.48.56 AM.png

dequeueReusableCellWithIdentifier no longer required iOS6

3 Comments

This is all we need to do now when grabbing a cell for display in a UITableViewController in iOS6:

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @”carTableCell”;
CarTableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];

// Configure the cell here …

return cell;
}
[/sourecode]

We used to have to do this (but not anymore – iOS6 now takes care of this for us)

// This step no longer required in iOS 6
if (cell == nil) {
cell = [[CarTableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
[/sourecode]

Note: You still need the above code if you are working with customer xibs and cells.

Links that help:
http://www.techotopia.com/index.php/An_Overview_of_iOS_6_Table_Views_and_Xcode_Storyboards

How to style header in UITableView section

1 Comment

Say you want to change the look of the header in a UITableView with sections.

Here’s one way you could do it:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
	// create the parent view that will hold header Label
	UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 22.0)];
	
    UIImage *image = [UIImage imageNamed:@"Background_320x22"];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    
	// create the button object
	UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    
	headerLabel.backgroundColor = [UIColor clearColor];
	headerLabel.opaque = NO;
	headerLabel.textColor = [UIColor whiteColor];
	headerLabel.highlightedTextColor = [UIColor whiteColor];
	headerLabel.font = [UIFont boldSystemFontOfSize:10];
	headerLabel.frame = CGRectMake(0.0, 0.0, 320.0, 22.0);
    
	headerLabel.text = @"My Header"; // i.e. array element
    
    [customView addSubview:imageView];
	[customView addSubview:headerLabel];
    
	return customView;
}

- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
	return 22.0;
}

Basically you create your own UIView and then add to it whatever you want (in this case a background image and a label over top of that).

How to UITableView with sections

1 Comment

Here’s a bare bones, stripped down version of the mechanics behind a UITableView with sections.


#import "MyTableViewController.h"

@interface MyTableViewController ()

@end

@implementation MyTableViewController

@synthesize data1 = _data1;
@synthesize data2 = _data2;

- (NSArray *)data1 {
    if (_data1 == nil) {
        _data1 = [NSArray arrayWithObjects:@"a", @"b", nil];
    }
    return _data1;
}

- (NSArray *)data2 {
    if (_data2 == nil) {
        _data2 = [NSArray arrayWithObjects:@"x", @"y", @"z",nil];
    }
    return _data2;
}

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

#pragma mark - Table view data source

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

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (section == 0)
        return self.data1.count;
    if (section == 1)
        return self.data2.count;
    return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (indexPath.section == 0)
        cell.textLabel.text = [self.data1 objectAtIndex:indexPath.row];
  
    if (indexPath.section == 1)
        cell.textLabel.text = [self.data2 objectAtIndex:indexPath.row];
    
    return cell;
}


#pragma stuff I added

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (section == 0)
        return @"section1";
    if (section == 1)
        return @"section2";
    return @"undefined";
}

@end

Note1: All cell access is 0 indexed.

Note2: This is for demo purposes only. If you were to do this for real you’d probably want to do something like this.

How to add a custom UITableViewCell to a xib file objective-c

14 Comments

Sometimes the standard UITableViewCells don’t cut it and you need to create your own.

Here’s how you can create your own UITableViewCell in Xcode and format it however your like.

1. Create UITableView xib file.

After creating a xib project, and dragging in a UITableView

create a new xib file for your UITableViewCell

and call it CustomCell.xib.

Delete the view that it gives you and instead drag out a ‘Table View Cell’ from the utilities area.

Now, it’s in here where we are free to do whatever kind of layout we want. In this case I am going to layout two labels in the middle on top of each other, like this:

2. Create a custom class.

I am going to need some code to back this xib file up, so next we create an objective-c class that extends UITableViewCell.

and I am going to give it the same name as my xib – CustomCell.

3. Tie class to xib.

Now to we need to connect this class to the xib. We do by clicking on our cell xib Identity Inspector and typing in our class name as below:

4. Create some properties.

With those two connected, we now need to create some properties for the elements in our UITableViewCell xib. Do this like you would any other property, by control dragging the elements from the xib to the custom class.

Note: I also added a reuseIdentifier class method here with an implementation that looks like this:

CustomCell.m

+ (NSString *)reuseIdentifier {    
    return @"CustomCellIdentifier";
}

We’ll use this later.

5. Tie it to the parent class.

Now we need to associate this xib with the parent VC xib that is going to use it. Do that by clicking the yellow cube on the left called ‘Files Owner’, the ‘Inspector’ tab in utilities, and then typing the VC file name (in this case I just used the default VC creating for me with the initial project).

6. Create a property for the cell in the parent.

In the ViewController with the UITableView that is going to be using this cell, add property pointing to it like so:

ViewController.h

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

@interface ViewController : UIViewController
@property (assign, nonatomic) IBOutlet CustomCell *customCell;

@end

ViewController.m

@synthesize customCell = _customCell;

Do this by bringing up your xib and the assistant for it, and dragging the cell over like any other control.

7. Connect this property to the cell.

This part is a bit weird, but now we go back to our custom cell xib, and connect our cell to the parent property in the View Controller.

Do this by clicking that ‘Files Owner’ yellow cube on the side, and then the ‘Connections Inspector’ circle with an arrow in it on the right. You should now see the customCell property we just defined in the parent VC.

Click on the little grey circle beside the customCell in the outlets section, and drag it to the cell. This connects the cell to the parent property.

8. Add a UITableView property to VC.

ViewController.h

@property (strong, nonatomic) IBOutlet UITableView *tableView;

ViewController.m

@synthesize tableView = _tableView;

9. Hook up UITableView datasource and delegate.

Last but not least we need to tell our UITableView that our ViewController is going to act as it’s datasource and delegate.

Go back to the ViewController xib, select the UITableView element, and from the ‘Connections Inspector’ drag those little circles out again beside dataSource and delegate to the yellow cube ‘Files Owner’ (not the UITableView!).


You will know you have it hooked up right when you see ‘File’s Owner’ beside the dataSource delegate outlets.

10. Implement the datasource delegate methods.

In your ViewController, implement the necessary UITableView datasource delegate methods.

ViewController.m

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:[CustomCell reuseIdentifier]];
    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
        cell = _customCell;
        _customCell = nil;
    }        
    
    cell.topLabel.text = @"I am on top";
    cell.bottomLabel.text = @"and I'm on the bottom";
    
    return cell;     
}

10.b. Set cell reuse identifier

Update

As Ryan pointed out down in the comments section, we need to set the reuse identifier on our UITableViewCell, so it will get pooled and reused propertly in the pool.

Click on your table view cells, go to the attributes inspector and make sure the text you enter there matches the identifier in your UITableCell class. Else as Eric points out, our cells won’t get reused properly.

11. Fire it up!

Run the app in the simulator and you should now see something like this:



I also added one line to the viewDidLoad() to adjust for row height:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // make row height same as our xib to get 
    // rid of the appearance of multpile rows
    self.tableView.rowHeight = 125;
}

Big thanks to Jeremy Gale for showing me the in’s and out’s of hooking up UITableViewCells to xibs.

Here’s a complete listing of files:

CustomCell.h

#import <UIKit/UIKit.h>

@interface CustomCell : UITableViewCell
+ (NSString *)reuseIdentifier;
@property (strong, nonatomic) IBOutlet UILabel *topLabel;
@property (strong, nonatomic) IBOutlet UILabel *bottomLabel;
@end

CustomCell.m

#import "CustomCell.h"

@implementation CustomCell
@synthesize topLabel;
@synthesize bottomLabel;

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

+ (NSString *)reuseIdentifier {    
    return @"CustomCellIdentifier";
}

@end

ViewController.h

#import "CustomCell.h"

@implementation CustomCell
@synthesize topLabel;
@synthesize bottomLabel;

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

+ (NSString *)reuseIdentifier {    
    return @"CustomCellIdentifier";
}

@end

ViewController.m


#import "ViewController.h"
#import "CustomCell.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize customCell = _customCell;
@synthesize tableView = _tableView;

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:[CustomCell reuseIdentifier]];
    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
        cell = _customCell;
        _customCell = nil;
    }        
    
    cell.topLabel.text = @"I am on top";
    cell.bottomLabel.text = @"and I'm on the bottom";
    
    return cell;     
}


- (void)viewDidLoad
{
    [super viewDidLoad];
    // make row height same as our xib to get 
    // rid of the appearance of multpile rows
    self.tableView.rowHeight = 125;
}

- (void)viewDidUnload
{
    [self setTableView:nil];
    [super viewDidUnload];
}

@end

Bonus – how to create a new xib and ViewController

1. Create new xib.

2. Create new ViewController class.

3. Drag out display elements onto xib.

4. Connect the File Owner to the view.

We want the File Owner view outlet to be connected to the xib view.
Do this by clicking view owner, clicking the grey circle beside view, and then dragging the circle to the view on screen.

5. Connect display elements to File’s Owner

You need to connect each display element in your xib to your parent ViewController.

This is done by clicking the yellow cube ‘File’s Owner’ icon and dragging little grey circle beside it’s property to the yellow cube line.

When you are done you should be able to mouse over each outlet and see the corresponding element light up in your xib.

How to configure UITableView for multirow selection

Leave a comment

This is a little harder than it sounds.

UITableView reuses cells – so you can’t just detect with row was selected and then expect that cell to be selected later. That cell will be recycled and your index will be lost.

So what we do is create a separate data structure (say a mutable array) to track which rows were selected, and then turn rows on and off by toggling a check mark.

The data structure

In my app I need to track which photos are selected:

So I am going to create an NSMutableArray to do that:

ViewController.h

@property (nonatomic, strong) NSMutableArray *selectedPhotoTracker;

ViewController.m

@synthesize selectedPhotoTracker = _selectedPhotoTracker;

and I am going to initialize him once my photos have loaded:

ViewController.m

-(void) initializeSelectedPhotoTracker {
    NSLog(@"initializeSelectedPhotoTracker");
    NSMutableArray *tracker = [NSMutableArray array];
    for (int i = 0; i < [[self photos] count] ; i++) {
        [tracker addObject:[NSNumber numberWithBool:NO]];
    }
    
    self.selectedPhotoTracker = tracker;    
}


-(void) refresh {
    
    NSLog(@"refresh");
    
    NSMutableArray *collector = [[NSMutableArray alloc] initWithCapacity:0];
    ALAssetsLibrary *al = [Utils defaultAssetsLibrary];
    
    [al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                      usingBlock:^(ALAssetsGroup *group, BOOL *stop) 
     {
         
         [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
          {
              if (asset) {
                  
                  // check for tag
                  NSDictionary *metadata = asset.defaultRepresentation.metadata;
                  NSDictionary *tiffDictionary = [metadata objectForKey:(NSString *)kCGImagePropertyTIFFDictionary];
                  NSString *takeMyStuffValue = [tiffDictionary objectForKey:(NSString *)kCGImagePropertyTIFFMake];

                  if ([takeMyStuffValue isEqualToString:TAKEMYSTUFF_METATAG]) {
                      [collector addObject:asset];
                  }
                  
              }  
          }];
         
         self.photos = collector;
         [self initializeSelectedPhotoTracker];
     }
                    failureBlock:^(NSError *error) { NSLog(@"Error refreshing photos.");}
     ];
}

Note: this looks more complicated that it really is but I included it to make that point that when you initialize this array matters.

First time I did this I tried putting :

         [self initializeSelectedPhotoTracker];

in the viewWillAppear view lifecycle method. It didn’t work because the photos (which are loaded in a separate thread in another block) hadn’t finished yet.

So if you are loading your data structure and nothing is there look out for that. Where you do your load and initialization matters.

Multi-row selection

This part is pretty straight forward. Apple doesn’t actually like you using blue highlighted row as an indicator of whether a row is selected (human interface guidelines).

They prefer you use check marks.

So with our datastructure in place we can just turn checkmarks on and off like this:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    int selectedRow = indexPath.row;

    NSDictionary *selectedPhoto = [[self selectedPhotoTracker] objectAtIndex:selectedRow];
    NSString *urlKey = [[selectedPhoto allKeys] objectAtIndex:0];
    
    // add checkmark
    UITableViewCell *thisCell = [tableView cellForRowAtIndexPath:indexPath];
    
    if (thisCell.accessoryType == UITableViewCellAccessoryNone) {
        thisCell.accessoryType = UITableViewCellAccessoryCheckmark;
        [selectedPhoto setValue:[NSNumber numberWithBool:YES] forKey:urlKey];        
    } 
    else 
    {
        thisCell.accessoryType = UITableViewCellAccessoryNone;
        [selectedPhoto setValue:[NSNumber numberWithBool:NO] forKey:urlKey];        
    }

    [[self selectedPhotoTracker] replaceObjectAtIndex:selectedRow withObject:selectedPhoto];
    [[self getSelectedImages] writeToDocumentDirectory:SELECTED_PHOTOS_FILE];
}

This method tracks that a row was selected and updates our model, and then it toggles the checkmark on or off respectively.

Links that help:
http://stackoverflow.com/questions/308081/is-it-possible-to-configure-a-uitableview-to-allow-multiple-selection

http://stackoverflow.com/questions/5370311/uitableview-didselectrowatindexpath-add-additional-checkmark-at-tap

How to display thumbnail images in iphone table view

9 Comments

Update

I have since created a screencast on how to add an image to a UITableView.

You can a view a more complete example and screen cast of how do this here.

You’ve got all those lovely images you’ve taken on your iPhone, and you want to display them in a table as thumbnails in your iphone app. How do you do it?

Drag out a new Table View Controller

Create and associate a new UIViewController subview class (i.e. TableViewController) and associate it with the controller.

Define a model to hold the photos:

TableViewController.h

#import <UIKit/UIKit.h>


@interface TableViewController : UITableViewController
@property (nonatomic, strong) NSArray *photos;
@end

Sythethize the photos property, add a setter to refresh the model in case it changes, and get rid of all the template code save the stuff shown below.

TableViewController.m

#import "TableViewController.h"

@implementation TableViewController

@synthesize photos = _photos;

-(void)setPhotos:(NSArray *)photos {
    if (_photos != photos) {
        _photos = photos;
        [self.tableView reloadData];
    }
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

#pragma mark - Table view data source

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

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.photos count];
}

- (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];
    }
    
    // Configure the cell...
    
    return cell;
}


@end

Give your table cell an identifier

And then update the reference in code

static NSString *CellIdentifier = @"MyPhotos";

Getting the pictures

First import and add a reference to the library.

#import <AssetsLibrary/AssetsLibrary.h>.

Then use the Asset library to iterate through all your photos, and collect them in the photo model we setup.

TableViewController.m

- (void) viewWillAppear:(BOOL)animated  {
    
    // collect the photos
    NSMutableArray *collector = [[NSMutableArray alloc] initWithCapacity:0];
    ALAssetsLibrary *al = [TableViewController defaultAssetsLibrary];
    
    [al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                      usingBlock:^(ALAssetsGroup *group, BOOL *stop) 
     {
         [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
          {
              if (asset) {
                  [collector addObject:asset];
              }  
          }];
         
         self.photos = collector;
     }
                    failureBlock:^(NSError *error) { NSLog(@"Boom!!!");}
     ];
    
}

Set the display table

This is pretty easy. Basically we are going to pull the asset(s) we stored in our photo model, and display the thumbnail in the table cell.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"MyPhotos";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    // Configure the cell...
    ALAsset *asset = [self.photos objectAtIndex:indexPath.row];
    
    [cell.imageView setImage:[UIImage imageWithCGImage:[asset thumbnail]]];
    [cell.textLabel setText:[NSString stringWithFormat:@"Thing %d", indexPath.row+1]];
    
    return cell;
}

Gotcha

Assets don’t like to hang around longer than their parents. So if we are going to access our assets latter we need to keep a reference to the library that created them around.

That’s what this singleton method is for:

TableViewController.h

+ (ALAssetsLibrary *)defaultAssetsLibrary;

TableViewController.m

+ (ALAssetsLibrary *)defaultAssetsLibrary {
    static dispatch_once_t pred = 0;
    static ALAssetsLibrary *library = nil;
    dispatch_once(&pred, ^{
        library = [[ALAssetsLibrary alloc] init];
    });
    return library; 
}

You’ll know you’re hitting this problem if you get an error message that says something like: Invalid attempt to access ALAssetPrivate.

Test and Debug

To test this you are going to need to hook your iPhone up to your dev environment (the simulator can’t do camera).

Also things like this rarely work for me first time through. To debug try dropping logs files and check things like photo count when created:

NSLog(@"count: %d", [self.photos count]);

When creating this blog post I forgot to delete the table section template:

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

Which caused it to display an empty screen.

You can also check that your assets are coming through with code like this:

    // Configure the cell...
    ALAsset *asset = [self.photos objectAtIndex:indexPath.row];
    
    ALAssetRepresentation *representation = [asset defaultRepresentation];
    NSURL *url = [representation url];
    NSLog(@"url: %@", [url absoluteString]);

Which will tell you if you are at least getting the images when you need to display them.

Hit the play button, and hopefully you should see something that looks like this:

Other things to check:
– make sure your viewController is hooked up to your class (this is from another example but you get the idea)

Good luck!

Helper links:

http://itunes.apple.com/itunes-u/ipad-iphone-application-development/id473757255 (Lecture 10 has a nice walkthrough)

http://stackoverflow.com/questions/3837115/display-image-from-url-retrieved-from-alasset-in-iphone

https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7

https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewStyles/TableViewCharacteristics.html#//apple_ref/doc/uid/TP40007451-CH3-SW14

http://www.daveoncode.com/2011/10/15/solve-xcode-error-invalid-attempt-to-access-alassetprivate-past-the-lifetime-of-its-owning-alassetslibrary/

%d bloggers like this: