Previously when I create a UITableView using storyboard, I set the cell identifier in property inspector and paste it into method cellForRowAtIndexPath, then use method dequeueReusableCellWithIdentifier to create a new UITableViewCell. However, when I create a UITableView programmatically, I have nowhere to set up cell identifier. I mean, I can arbitrarily appoint an identifier without having to match that in the property inspector. Am I right? And how should I create a UITableViewCell under this situation?
You can create UITableview and UITableviewCell both programmatically as below.
Create Tableview Programmatically,
#interface ViewController () <UITableViewDataSource>{
//create tableview instance
UITableView *tableViewPro;
}
Alloc it in viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// init table view
tableViewPro = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
// must set delegate & dataSource, otherwise the the table will be empty and not responsive
tableViewPro.dataSource = self;
tableViewPro.backgroundColor = [UIColor cyanColor];
// add to canvas
[self.view addSubview:tableViewPro];
}
Assign number of rows and UITableview cell in Tableview datasource methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"Tableviewcell";
// Similar to UITableViewCell, but
TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
//initialize uitableviewcell programmatically.
cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
// Just want to test, so I hardcode the data
cell.descriptionLabel.text = [NSString stringWithFormat:#"Testing index %ld",(long)indexPath.row];
return cell;
}
Now, create your UITableviewCell programmatically. First Add New->file->Cocoa Touch class->Enter tableviewcellname with subclass-of UITableviewCell and UNCHECK the 'Also create XIB file', because cell will be created programmatically.
Two files will be created .h and .m of UITableviewCell, I've create UITableviewCell with TableViewCell name. below is the code to achieve it programmatically.
#interface TableViewCell : UITableViewCell
#property (nonatomic,retain) UILabel *descriptionLabel;
#end
Synthesize descriptionLabel in #implementation file of TableViewCell
#synthesize descriptionLabel = _descriptionLabel;
Below is the initialization function function of creating a UITableviewCell Programmatically. ADD THIS FUNCTION ON #implementation file of TableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
//it will create UITableiviewCell programmatically and assign that instance on self.
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// configure control(s)
self.descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 10, 300, 30)];
self.descriptionLabel.textColor = [UIColor blackColor];
self.descriptionLabel.font = [UIFont fontWithName:#"Arial" size:12.0f];
[self addSubview:self.descriptionLabel];
}
return self;
}
Below is the resulted output.
Related
I have a UITableViewCell subclass. I want to add a UIImageView as a subview, but make sure to do proper reuse so that I am not adding the subview over and over. I also want to make sure to nil out the image in prepareForReuse.
What is the proper method for doing this?
In your custom cell subclass you should add any required views in the initWithStyle:reuseIdentifier: method. As long as you have registered your cell class against the reuse identifier in your table view then this initialiser will be called by dequeueReusableCellWithIdentifier:forIndexPath: whenever a new cell is required. This method will not be called when a cell is reused, so your image view won't be added more than once.
You can clear the image view's current image in the cell class's prepareForReuse method.
For proper reuse of cell, register cell xib in viewDidLoad in UIViewController class and then write
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell_ID"];
and then load image in imageView inn cell.This guarantees reuse of cell.
You need to add the UIImageView as a property of the UITableviewCell subclass. That way in cellForRowAtIndexPath you just say myCellInstance.profilePicView.image = ...
Have a look at my answer here on how I added textfields as cell subviews. Look specifically at the override for initWithStyle:reusableIdentifier: in the PersonCell class. No use of prototype cells in storyboard, no use of viewWithTag, just as you want.
Here is what the cellForRowAtIndexPath would look like if the Person class had a profile pic :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
PersonCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier: #"CellWithNameAndSurname"];
if(!cell)
{
cell = [[PersonCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellWithNameAndSurname"];
cell.contentView.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent: 0.08f];
cell.delegate = self;
}
//this should be outside the above if statement!
Person *respectivePerson = _peopleArray[indexPath.row];
cell.profilePicView.image = respectivePerson.profilePic;
cell.nameTextField.text = respectivePerson.name;
cell.surnameTextField.text = respectivePerson.surname;
cell.positionLabel.text = [NSString stringWithFormat:#"%i", (int)indexPath.row];
return cell;
}
Proper way to clean cell before reuse is
-(void)prepareForReuse;
just set self.imageView.image = nil;
And to create UIImageView , I'd do something like that:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
[self setup];
return self;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
[self setup];
return self;
}
- (void)setup {
/// Create your UIImageView and set layout
}
I'm currently creating my UITableViewCells programmatically like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Home-Cell" forIndexPath:indexPath];
UILabel *newLabel = [[UILabel alloc] initwithframe:cell.frame];
[newLabel setText:self.data[indexPath.row]];
[cell addSubview:newLabel];
return cell;
}
This seems to create a new UILabel each time the cell is reused though, which I definitely don't want. I tried doing the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Home-Cell" forIndexPath:indexPath];
if (cell == nil) {
UILabel *newLabel = [[UILabel alloc] initwithframe:cell.frame];
[cell addSubview:newLabel];
}
[newLabel setText:self.data[indexPath.row]];
return cell;
}
but then the UILabel seems to never be created. Perhaps this is because I'm using prototype cells with Storyboard and thus the cells are never nil?
You have two solutions.
Create a custom table view cell that already has the label.
If you want to add the label in code, don't register a class for the cell. Then the dequeued cell can be nil and you can add the label at that time (like in your 2nd set of code). This also requires using the dequeueReusableCellWithIdentifier method that doesn't also take an indexPath.
You should create a UITableViewCell subclass and add "newLabel" as a property.
The cell is never nil because the method you use to dequeue the table view cell always returns a cell, creating one if it doesn't already exist in the reuse queue.
A better solution would be to create the label in the cell prototype in the storyboard.
This implementation is against MVC architecture where controller managers stuff and do not deal with view. Here, you are trying to add stuff in view from controller. It is suggested to subclass UITableViewCell as below and add your custom UI controls in there
MyTableViewCell.h
#interface MyTableViewCell : UITableViewCell {
}
#property (nonatomic, strong) UILabel *myLabel;
#end
Then you can implement layoutSubviews in your MyTableViewCell.m file to define the look and feel of your cell.
MyTableViewCell.m
- (id)initWithStyle:(UITableViewCellStyle)iStyle reuseIdentifier:(NSString *)iReuseIdentifier {
if ((self = [super initWithStyle:iStyle reuseIdentifier:iReuseIdentifier])) {
self.myLabel = [[UILabel alloc] initWithFrame:<Your_Frame>];
// Set more Label Properties
[self.contentView addSubview:self.myLabel];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
// Override run time properties
}
Finally use your custom cell like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"Home-Cell" forIndexPath:indexPath];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Home-Cell"];
}
cell.myLabel.text = self.data[indexPath.row];
return cell;
}
As a side note, I hope you know that you get textLabel and detailTextLabel free from default UITableViewCell implementation.
I am trying to create a Card Based News feed (Similar to Facebook app) in iOS.I'm implementing programmatically because the height of UITablecell has to increase/decrease based on the data.
Consider i have one UIlabel and one UIImageView added, then the height of UITablecell should be adjusted based on the fields. If one more text feild is added then height has to increase and if imageview is removed then height has to decrease.
The issue here is I couldn't to add CustomView on the UItablecell programmatically but when i create using Interface builder then its working fine but the height remains constant which i don't want to have.
Can some one please help me out.
TableViewCell.h
#interface TableViewCell : UITableViewCell
#property (strong,nonatomic) UIView *customView;
#property (strong,nonatomic) UILabel *customLabel;
#property (strong,nonatomic) UIImageView *customImage;
#end
TableViewCell.m
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// configure control(s)
[self addSubview:self.customView];
[self.customView addSubview:self.customLabel];
[self.customView addSubview:self.customImage];
}
return self;
}
- (UIView *)customView
{
if (!_customView)
{
_customView = [[UIView alloc] initWithFrame:CGRectMake(0, 10, self.contentView.frame.size.width, 50)];
[_customView setTranslatesAutoresizingMaskIntoConstraints:NO];
}
return _customView;
}
- (UIImageView *)customImage
{
if (!_customImage)
{
_customImage = [UIImageView new];
_customImage.clipsToBounds = YES;
[_customImage.layer setBorderColor: [[UIColor grayColor] CGColor]];
[_customImage.layer setBorderWidth: 1.0];
_customImage.contentMode = UIViewContentModeCenter;
[_customImage setTranslatesAutoresizingMaskIntoConstraints:NO];
}
return _customImage;
}
- (UILabel *)customLabel
{
if (!_customLabel)
{
_customLabel = [UILabel new];
[_customLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
}
return _customLabel;
}
UITableVIewController.m
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath *)indexPath
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!_stubCell)
{
_stubCell = [tableView dequeueReusableCellWithIdentifier:#"TableCell"];
}
});
CGFloat height = [_stubCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height + 1;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 50.f;
}
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"TableCell";
// Similar to UITableViewCell, but
_stubCell = (TableViewCell *)[theTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (_stubCell == nil) {
_stubCell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
// Just want to test, so I hardcode the data
_stubCell.customLabel.text = #"Testing";
return cell;
}
I think it may be better to create multiple nibs for the same class UITableViewCell, one for each of the possible layouts that you need. Depending on which nib is used then set heightForRowAtIndexPath to the height of the nib.
This will give you predictability of the layout for each situation and you connect only the fields defined in UITableViewCell in cellForRowAtIndexPath.
never mind i found answer by myself. I am calculating the height of each UIView that is added the super view. Based on the total height the UITableview height increases and decrease and also using auto layout adjusting the layout constraints made me simple.
I am still working on the design once its done i'll post the code so that it will be help ful for other developers.
I believe this question is my exact problem, but I was not able to fix the issue looking at the accepted answer.
UISearchBar: FilterContentForSearchText not working on a UITableView (results not shown on table)
I have a UITableViewController that allows searching. It was working perfectly via the default UITableViewCellStyle, then I decided to implement my own custom layout for the cells. I did not subclass UITableViewCell, but instead added two UILabels to the cell via Interface Builder and set up Auto Layout. I assigned them unique tags so that I can reference those labels in cellForRowAtIndexPath:. It works great - everything appears as expected, until you search. The search does work and it displays the sections and rows, but the labels on the rows have no text so the cell appears completely blank.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"List View Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UILabel *leftLabel = (UILabel *)[cell viewWithTag:100];
UILabel *rightLabel = (UILabel *)[cell viewWithTag:101];
if (tableView == self.searchDisplayController.searchResultsTableView) {
leftLabel.text = ...;
rightLabel.text = ...;
}
else {
leftLabel.text = ...;
rightLabel.text = ...;
}
return cell;
}
I do know why the labels are blank. When you search, dequeueReusableCellWithIdentifier: returns nil so it inits a new cell with the reuse identifier, but that cell does not have a viewWithTag: and this results in left and right label becoming nil. So it obviously cannot add text to nil.
The answer provided in the link stated you should create the label in the if (!cell) statement and then call [cell.contentView addSubview:left{right}Label];. I did that, and then moved my label configuration code into that if statement as well. But when I do that, the main table's rows only has the default values of my left and right labels from Storyboard - it doesn't set the text of the labels. This is because dequeueReusableCellWithIdentifier: doesn't return nil and instead creates a new cell, so it doesn't ever set the text because that's in the if (!cell) statement.
I could not figure out how to take care of both situations: when cell is nil and when it is not. What do I need to do to fix this?
More comments: I've never used xib files before and I'd prefer to keep it that way. :) I wouldn't mind subclassing UITableViewCell if that's a solution. Of course, I would like to implement this the "proper" way - only create a cell when one is needed etc.
I think the easiest way to do this is to make your cell in a xib file if you want to use the same cell type for both the main table and the search results table. You can make a subclass if you want (you only need to put in IBOutlets to your two labels in the .h file), or do it the same way you already did using tags. In viewDidLoad of the table view controller, register the nib for both tables,
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"CommonCell" bundle:nil] forCellReuseIdentifier:#"CommonCell"];
[self.tableView registerNib:[UINib nibWithNibName:#"CommonCell" bundle:nil] forCellReuseIdentifier:#"CommonCell"];
Then in cellForRowAtIndexPath:, you only need to dequeue the cell with that same identifier, and populate the labels. There's no need to check for cell equals nil, because it never will be.
I modified one of my apps to show how you can implement cellForRowAtIndexPath. I subclassed the cell (CommonCell is the class), only adding IBOutlets to the leftLabel, and rightLabel,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CommonCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CommonCell" forIndexPath:indexPath];
cell.leftLabel.text = ([tableView isEqual:self.tableView])? self.theData[indexPath.row] : self.filteredData[indexPath.row];
cell.rightLabel.text = ([tableView isEqual:self.tableView])? self.theData[indexPath.row] : self.filteredData[indexPath.row];
return cell;
}
This is conceptually wrong: you are instantiate a NEW CELL from code that is not the cell from interface builder. If you want use that on interface builder you need to register the nib for your tableView, and associate it to an identifier (se the same identifier also in the cell on interface builder):
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"NameNib" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"Identifier"];
But the question is: why? The best thing to do is create a subclass and add your labels. It is really simple:
1) Create a new file CustomSearchCell object that extends UITableViewCell:
CustomSearchCell.h
#import <UIKit/UIKit.h>
#interface CustomSearchCell : UITableViewCell
#property (strong, nonatomic) UILabel *leftLabel;
#property (strong, nonatomic) UILabel *rightLabel;
#end
CustomSearchCell.m
#import "CustomSearchCell.h"
#implementation CustomSearchCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_leftLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 10, 200, 45)];
[_leftLabel setFont:[UIFont systemFontOfSize:20.0]];
[_leftLabel setTextColor:[UIColor blackColor]];
_rightLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 45, 200, 25)];
[_rightLabel setFont:[UIFont systemFontOfSize:15.0]];
[_rightLabel setTextColor:[UIColor grayColor]];
[self.contentView addSubview: _leftLabel];
[self.contentView addSubview: _rightLabel];
}
return self;
}
#end
2) In your view controller:
#import "CustomSearchCell.h"
and:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListCellIdentifier";
CustomSearchCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[CustomSearchCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.leftLabel = ...;
cell.rightLabel = ...;
}
else {
cell.leftLabel = ...;
cell.rightLabel = ...;
}
return cell;
}
I'm using storyboard for a tableview with custom cells. I created custom cells in storyboard without any additional classes for it. Everything works fine when I'm not using the UISearch bar, But when I want to create the custom cell for UISearchBarController tableview I don't know how can I do it.
Hers is my custom cell in storyboard,
with cellIdentifier = "SelectionCell"
and I'm not using any custom class.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"SelectionCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nibs = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
cell = [nibs objectAtIndex:0];
}
the code crashes:
'Could not load NIB in bundle:'
I don't have a NIB file for my custom cell, and I don't have any custom UITableViewCell class to use alloc/init method like this:
cell = [[myCustomCellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
how can I create the cell?
searchResultsTableView
The table view in which the search results are displayed. (read-only)
#property(nonatomic, readonly) UITableView *searchResultsTableView
A search display controller manages the display of a search bar, along with a table view(your original tableview style) that displays search results.
It seems it can't be done without a custom cell class. Because we don’t have a storyboard prototype cell to manipulate graphically for the filtered table view, we have to programmatically create the cells. And for that we need a custom cell class and with initWithStyle: method in the .m file as follows.
So I created a class called SelectionCell subclass of UITableViewCell, and in IB assigned custom cell class to SelectionCell class and create IBOutlet for all UI controls in my custom cell into SelectionCell.h file
#interface SelectionCell : UITableViewCell{
}
#property (strong, nonatomic) IBOutlet UIImageView *contactPictureID;
#property (strong, nonatomic) IBOutlet UILabel *contactName;
#property (strong, nonatomic) IBOutlet UILabel *emailAddress;
#property (strong, nonatomic) IBOutlet UIImageView *selectedEmailState;
#end
in initWithStyle: method, I had to recreate all the UI controls programmatically and add them to the contentView as subviews:
// SelectionCell.m
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.selectionStyle = UITableViewCellSelectionStyleNone;
_contactPictureID = [[UIImageView alloc] initWithFrame:(CGRectMake(10, 14, 48, 48))];
_contactName = [[UILabel alloc] initWithFrame:(CGRectMake(66, 14, 201, 30))];
_contactName.font = [UIFont boldSystemFontOfSize:20];
_contactName.textAlignment = NSTextAlignmentLeft;
_emailAddress = [[UILabel alloc] initWithFrame:(CGRectMake(66, 41, 201, 21))];
_emailAddress.font = [UIFont systemFontOfSize:16];
_emailAddress.textAlignment = NSTextAlignmentLeft;
_selectedEmailState = [[UIImageView alloc] initWithFrame:(CGRectMake(275, 22, 32, 32))];
[self.contentView addSubview:_contactPictureID];
[self.contentView addSubview:_contactName];
[self.contentView addSubview:_emailAddress];
[self.contentView addSubview:_selectedEmailState];
}
return self;
}
Now I am able to instantiate my custom cell. In cellForRowAtIndexPath method
static NSString *CellIdentifier = #"SelectionCell";
SelectionCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[SelectionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; }
And now everything is working as it should be.