Custom Table View Cell with a big image and a label - ios

I am trying to populate a Tableview with data from an array. I am trying to make a custom table view cell that includes a big square image, same width as the screen and the height should also be the same size as the screen's width. And under the image I want to add a text label. (You can think of it as a very bad copy of instagram)
However, the tableview only shows empty standard cells.
This is my custom cell code:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
customImageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect frame;
frame.origin.x=0;
frame.origin.y=0;
frame.size.width=self.frame.size.width;
frame.size.height=self.frame.size.width;
customImageView.frame=frame;
[customTextLabel sizeToFit];
frame.origin = CGPointMake(0, (self.frame.size.width+1));
customTextLabel.frame = frame;
customTextLabel.numberOfLines = 0;
customTextLabel.lineBreakMode = NSLineBreakByCharWrapping;
[self.contentView addSubview:customImageView];
[self.contentView addSubview:customTextLabel];
[self.contentView sizeToFit]; } return self;}
and the code from the tableview:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyCellIdentifier = #"MyCellIdentifier";
//UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyCellIdentifier];
FeedTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyCellIdentifier];
if(cell == nil) {
cell = [[FeedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyCellIdentifier];
}
if(!emptyTable){
Log *log = [self.feedPosts objectAtIndex:indexPath.section];
cell.customImageView.image = [self loadImage:(int)log.logID];
cell.customTextLabel.text = log.logDescription;
}
return cell;}

Better solution is to use constraints
create constraints like on image below
Create outlets for your height width ( control drag your constraint to cell class)
Change it to screen size/ height on your
cell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
cell.heightConstraint.constant = UIScreen .mainScreen().bounds.height;
cell.widthConstraint.constant = UIScreen.mainScreen().bounds.width;
return cell
}
Don't forget to set height for row
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UIScreen .mainScreen().bounds.height;
}
Also, you can recreate it on your code programmatically, if you want
Hope this helps

Where are you allocate memory for the cell.customImageView and cell.customTextLabel? This problem should be caused by not generating customeImageView and customeTextLabel. you should according to the following code to modify:
//Setter/Getter
//allocate memory and init customImageView/customTextLable
- (UIImageView *)customeImageView{
if(!_ customImageView)_customImageView = [UIImageView new];
return _customImageView;
}
- (UILable *)customTextLabel{
if(!_customTextLable) _customTextLabel = [UIlable new];
return _cusomTextLable;
}
Then invoke them when initWithStyle:reuseIdentifier:.The other thing should be noticed is that tableView should custome cell's height by implement tableView:heightForRowAtIndexPath:

I almost did what DarkHorse said.
I forgot to do:
customImageView = [[UIImageView alloc] init];
customTextLabel = [[UILabel alloc] init];
and then set the height in
tableView:heightForRowAtIndexPath

Related

UITableView self sizing cell scrolling issues with big difference of cell height

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let json = JSON(data: activityTableView.onlineFeedsData)[indexPath.row] // new
if(json["topic"]["reply_count"].int > 0){
if let cell: FeedQuestionAnswerTableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier_reply) as? FeedQuestionAnswerTableViewCell{
cell.selectionStyle = .None
cell.tag = indexPath.row
cell.relatedTableView = self.activityTableView
cell.configureFeedWithReplyCell(cell, indexPath: indexPath, rect: view.frame,key: "activityTableView")
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
}
}else{
if let cell: FeedQuestionTableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as? FeedQuestionTableViewCell{
cell.selectionStyle = .None
cell.tag = indexPath.row
cell.relatedTableView = self.activityTableView
cell.configureFeedCell(cell, indexPath: indexPath, rect: view.frame)
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
}
}
return UITableViewCell()
}
There is difference of 200-400 in height in each cell,So tried implementing the height calculation in estimatedHeightForRowAtIndexPath .I cant the exact estimate height in this method as bcz of calculation
and caching the height in willDisplayCell method but still the scroll jumps like is it taking time to rendering .
The cell is like the Facebook card type layout with lots of dynamic data with variable height text and images.Can someone help me in this .Thanks
If you're able to calculate each cells height, just use the tableView(_:heightForRowAtIndexPath) instead of estimatedRowHeightAtIndexPath property. estimatedRowHeightAtIndexPath is mostly used in smthg like selfsizing cells etc.
Try this one. I hope this could help you. It's worked for me. Actually this calculates the cell height before it displayed.
Thanks.
NSMutableDictionary *cellHeightsDictionary;
// Put this in viewDidLoad
cellHeightsDictionary = #{}.mutableCopy;
// UITableview delegate Methods
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
NS_AVAILABLE_IOS(7_0)
{
NSNumber *height = [cellHeightsDictionary objectForKey:indexPath];
if (height) return height.doubleValue;
return UITableViewAutomaticDimension;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cellHeightsDictionary setObject:#(cell.frame.size.height) forKey:indexPath];
}

Dynamically add subview to UITableViewCell

I'm trying to add some subviews to my UITableViewCell. The number of subviews is based on my data. When I scroll down the subviews disappears and does not show any more. Adding them to the NIB is no option because I only now the number of subviews at runtime and they are different for each cell.
What is the right way to add an unknown number of subviews to a UITableViewCell at runtime?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"DetailCell";
DetailCellTableViewCell *cell = (DetailCellTableViewCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"DetailCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
NSInteger count = [self getMaxSubviews];
NSInteger y=100;
for (int i=0; i<count;i++)
{
UITextField *dataS = [[UITextField alloc] init];
dataS.frame=CGRectMake(277, y, 60, 17);
y=y+17;
dataS.tag=i+1337;
dataS.backgroundColor=[UIColor redColor];
[cell addSubview:dataS];
}
}
if (!useOrigCellFromNib) // Here I can use the original Nib created by IB
{
NSString *data = #"Some String";
[cell.data setText:data];
}
else // Use added subviews!
{
for (int i=0;i<arrS.count;i++)
{
NSManagedObject *s = [arrS objectAtIndex:i];
UITextView *dataS =[cell viewWithTag:i+1337];
dataS.text=[NSString stringWithFormat:#"%ld foo", (long)i];
[cell.data setHidden:YES];
}
}
return cell;
}
Like Igor mentioned when reusing cell you have to remove waht ever you add previousely and re-create subviews.
May be you can not use "loadFromNib" and Subclass 'UITableViewCell' class and create your cell there.
This is a example in swift but logic is same for ObjC too
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let stuffArray = array[indexPath.row]
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
if cell == nil {
cell = MyCustomCell(initWithDaraArray:stuffArray) // create cell based on array data dynamically
} else { // even if you have cell you need to refresh it for new data
cell.refreshDataForDataInArray(stuffArray) // here remove all subviews and create new ones
}
return cell
}
and cell heights can be adjusted by
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let stuffArray = array[indexPath.row]
return calculatedHeight(stuffArray)
}
1 you should reuse cells, call tableView.dequeueReusableCellWithIdentifier("CellId")
2 after you get the reused cell, you should delete all previously added custom subviews
3 after that you can add new subviews
about " I scroll down the subviews disappears and does not show any more"
I don't see any "cell" variable before
if (cell == nil)
So Probably you do not paste the reuse code here, in this case cells after scrolling will not be nil and the code under the if (cell == nil) will not be called...

iOS 9 UITableView cell's text label not the full width of the UITableView

Since updating to iOS 9, table view cell's in iPad landscape no longer stretch the full width of the table in my unit tests.
My test is a simple table that takes a snapshot at the end.
- (void)testTableSize{
UIViewController *viewController = [[UIViewController alloc] init];
UITableView *tableView = [[UITableView alloc]initWithFrame:CGRectMake(0,0,viewController.view.bounds.size.width, viewController.view.bounds.size.height) style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
[viewController.view addSubview:tableView];
ASCSnapshotVerifyViewiPad([viewController view]);
}
The cells are very simple for this example
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.backgroundColor = [UIColor blueColor]; // Demo purpose
cell.textLabel.backgroundColor = [UIColor yellowColor]; // Demo purpose
cell.textLabel.text = [NSString stringWithFormat:#"Cell %d", (int)indexPath.row];
return cell;
but my cell is drawn with a big margin on the left and right. Any idea why?
When I tested my App with iOS9, I noticed huge margins, on some UITableViews, both left and right. After a bit of investigation, I found the following new method:
// iOS9
if([self.tableView respondsToSelector:#selector(setCellLayoutMarginsFollowReadableWidth:)]) {
self.tableView.cellLayoutMarginsFollowReadableWidth = NO;
}
When the above code is called, after instantiating your UITableView, it should remove them.
Paste this code after you set your tableView delegate.
Hope it helps.
hope this help you
// MARK: - TableView Delegates And Datasource Method -
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
// IOS 7
if(self.respondsToSelector(Selector("setSeparatorInset:"))){
self.separatorInset = UIEdgeInsetsZero
cell.separatorInset = UIEdgeInsetsZero
tableView.separatorInset = UIEdgeInsetsZero;
}
// OTHER
if(self.respondsToSelector(Selector("setPreservesSuperviewLayoutMargins:"))){
if #available(iOS 8.0, *) {
self.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero;
cell.preservesSuperviewLayoutMargins = false
}
}
// iOS 8
if(self.respondsToSelector(Selector("setLayoutMargins:"))){
if #available(iOS 8.0, *) {
self.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
}
}
// iOS 9
if (self.respondsToSelector("setCellLayoutMarginsFollowReadableWidth:")){
if #available(iOS 9.0, *) {
self.cellLayoutMarginsFollowReadableWidth = false
}
}
}

UITableViewCell constraints not seen in iOS 8. How do I mitigate this?

I'm trying to understand why the constraints I have put in place on the Storyboard for my different labels in my cell aren't being done. This is important because my height is dynamic.
I have had this problem for the past 2 days and it's driving me up the wall. No, UITableViewAutomaticDimension is not how I want to do this.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = self.feed.dequeueReusableCellWithIdentifier("post") as PostCell
var post = self.posts[indexPath.row]
cell.name.text = post.name
cell.timestamp.text = post.timestamp
cell.postBody.text = post.body
println("\(cell.name.constraints())")
println("\(cell.postBody.constraints())")
cell.contentView.setTranslatesAutoresizingMaskIntoConstraints(false)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(feed.bounds), CGRectGetHeight(cell.bounds))
cell.setNeedsLayout()
cell.layoutIfNeeded()
var height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height
height += 1.0
return height
}
When I print the constraints set for cell.postBody I get []. However, I have 5 constraints. A trailing space to Superview, leading space to Superview, Bottom space to Superview Equals 4, and 2 Top Spaces to 2 different labels Equals 8.
If it isn't possible for my code to see the constraints via the Storyboard, how do I programmatically set these 5 constraints
Updated way I'm doing it:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = self.feed.dequeueReusableCellWithIdentifier("post") as PostCell
var post = self.posts[indexPath.row]
cell.name.text = post.name
cell.timestamp.text = post.timestamp
cell.postBody.text = post.body
if cachedHeights[post.id] == nil && cell.bounds.height != 0.0 {
cachedHeights[post.id] = cell.bounds.height
}
return cell
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var post = posts[indexPath.row]
if cachedHeights[post.id] != nil {
return cachedHeights[post.id]!
} else {
return 70
}
}
The problem is, I'm not sure cell.bounds.height is completely accurate. In that I mean, I think it is using the height of a previous large cell sometimes (perhaps from the cell it dequeued for the new one.)
The constraints aren't on the labels. That's why their constraints property returns an empty array.
You'll find the constraints on the labels' superview, cell.contentsView.
You can skip the calls to setNeedsUpdateConstraints and updateConstraintsIfNeeded as layoutSubviews will call updateConstraintsIfNeeded.
The old approach I used before switching to self-sizing cells:
I don't have any swift code, but here's some old code from an app before I switched over to self-sizing cells. It (implicitly) uses the constraints placed on the labels in the storyboard cell. The only thing different I did is caching the sizing cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
static LeftDetailTableViewCell *cell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
});
// configure the cell for this indexPath
[self configureCell:cell atIndexPath:indexPath];
// Set the sizing cell's width to the tableview's width
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
// get the fitting size
CGSize s = [cell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize];
return s.height + 1.0;
}
Update:
Here's the code I'm using now, for self-sized cells in iOS 8.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
BIBLETableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[cell adjustSizeToMatchWidth:CGRectGetWidth(self.tableView.frame)];
[self configureAccessoryTypeForCell:cell forRowAtIndexPath:indexPath];
[cell adjustConstraintsToMatchSeparatorInset:self.tableView.separatorInset];
[self configureCell:cell forRowAtIndexPath:indexPath];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
return cell;
}
My subclassed cell has:
- (void)adjustSizeToMatchWidth:(CGFloat)width
{
// Workaround for visible cells not laid out properly since their layout was
// based on a different (initial) width from the tableView.
CGRect rect = self.frame;
rect.size.width = width;
self.frame = rect;
// Workaround for initial cell height less than auto layout required height.
rect = self.contentView.bounds;
rect.size.height = 99999.0;
rect.size.width = 99999.0;
self.contentView.bounds = rect;
}
- (void)adjustConstraintsToMatchSeparatorInset:(UIEdgeInsets)inset
{
if (self.leadingMargins)
{
for (NSLayoutConstraint *constraint in self.leadingMargins)
{
constraint.constant = inset.left;
}
}
if (self.trailingMargins)
{
for (NSLayoutConstraint *constraint in self.trailingMargins)
{
constraint.constant = self.accessoryType == UITableViewCellAccessoryDisclosureIndicator ? 0.0f : inset.left;
}
}
}

AutoLayout in UITableview with dynamic cell height

For dynamic height of my table view cell I take reference from this link.
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
Here is my code of tableview data source and delegate methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return arrTemp. count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"AutoLAyoutCell";
AutoLayoutTableViewCell *cell=(AutoLayoutTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell==nil) {
for (id currentObject in [[NSBundle mainBundle] loadNibNamed:#"AutoLayoutTableViewCell" owner:self options:nil]) {
if ([currentObject isKindOfClass:[UITableViewCell class]]) {
cell = (AutoLayoutTableViewCell *)currentObject;
break;
}
}
}
cell.IBlblLineNo.text=[NSString stringWithFormat:#"Line:%i",indexPath.row];
cell.IBlblLineText.text=[arrTemp objectAtIndex:indexPath.row];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
CGSize expectedlineLabelSize = [cell.IBlblLineText.text sizeWithFont:cell.IBlblLineText.font constrainedToSize:CGSizeMake(280, 1000) lineBreakMode:NSLineBreakByTruncatingTail];
cell.IBlblLineText.numberOfLines=expectedlineLabelSize.height/17;
CGRect frmlbl=cell.IBlblLineText.frame;
frmlbl.size.height=expectedlineLabelSize.height;
cell.IBlblLineText.frame=frmlbl;
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
AutoLayoutTableViewCell *cell = (AutoLayoutTableViewCell *)[IBtblAutoLayoutExample cellForRowAtIndexPath:indexPath];
cell.IBlblLineNo.text=[NSString stringWithFormat:#"Line:%i",indexPath.row];
cell.IBlblLineText.text=[arrTemp objectAtIndex:indexPath.row];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
CGSize expectedlineLabelSize = [cell.lineLabel.text sizeWithFont:cell.lineLabel.font constrainedToSize:CGSizeMake(280, 1000) lineBreakMode:NSLineBreakByWordWrapping];
cell.IBlblLineText.numberOfLines=expectedlineLabelSize.height/17;
CGRect frmlbl=cell.IBlblLineText.frame;
frmlbl.size.height=expectedlineLabelSize.height;
cell.IBlblLineText.frame=frmlbl;
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1.0f;
return height;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
AutoLayoutTableViewCell *cell = (AutoLayoutTableViewCell *)[IBtblAutoLayoutExample cellForRowAtIndexPath:indexPath];
CGSize expectedlineLabelSize = [cell.IBlblLineText.text sizeWithFont:cell.IBlblLineText.font constrainedToSize:CGSizeMake(280, 1000) lineBreakMode:NSLineBreakByTruncatingTail];
return expectedlineLabelSize.height;
}
I have 2 questions :
My issue is I get the error EXE_BAD_EXCESS near the line
AutoLayoutTableViewCell *cell = (AutoLayoutTableViewCell *)[IBtblAutoLayoutExample cellForRowAtIndexPath:indexPath];
in heightForRowAtIndexPath and estimatedHeightForRowAtIndexPath.
Why do I have to write label text in both cellForRowAtIndexPath and heightForRowAtIndexPath?
Also, am I missing anything needed to achieve dynamic height for the cell?
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
Objective C:
// in ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property IBOutlet UITableView * table;
#end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.
Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.

Resources