I am trying to dynamically resize some UITableViewCell's based on the height of UITextView's contained within them.
There's loads of solutions to this by keeping a pointer to the UITextView and getting it's content size in heightForRowAtIndexPath however when the whole table is created dynamically with an unknown number of rows and an unknown number of them rows contain UITextView's this just isn't possible.
It would be easy if I could call the cell in question during heightForRowAtIndexPath but that causes an infinite loop and crash as this method is called before any cell's are even created.
Any other solutions?
I am using a UITableViewCell subclass for my cell like this:
- (void)initalizeInputView {
// Initialization code
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.textView = [[UITextView alloc] initWithFrame:CGRectZero];
self.textView.autocorrectionType = UITextAutocorrectionTypeDefault;
self.textView.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.textView.textAlignment = NSTextAlignmentRight;
self.textView.textColor = [UIColor lightBlueColor];
self.textView.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:17];
self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.textView.keyboardType = UIKeyboardTypeDefault;
[self addSubview:self.textView];
self.textView.delegate = self;
}
- (BOOL)resignFirstResponder {
if (_delegate && [_delegate respondsToSelector:#selector(tableViewCell:didEndEditingWithLongString:)]) {
[_delegate tableViewCell:self didEndEditingWithLongString:self.stringValue];
}
return [super resignFirstResponder];
}
- (void)setKeyboardType:(UIKeyboardType)keyboardType
{
self.textView.keyboardType = keyboardType;
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self initalizeInputView];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initalizeInputView];
}
return self;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
[self.textView becomeFirstResponder];
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (selected) {
[self.textView becomeFirstResponder];
}
}
- (void)setStringValue:(NSString *)value {
self.textView.text = value;
}
- (NSString *)stringValue {
return self.textView.text;
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
// For keyboard scroll
UITableView *tableView = (UITableView *)self.superview;
AppSetupViewController *parent = (AppSetupViewController *)_delegate;
parent.activeCellIndexPath = [tableView indexPathForCell:self];
}
- (void)textViewDidChange:(UITextView *)textView
{
if (textView.contentSize.height > contentRowHeight) {
contentRowHeight = textView.contentSize.height;
UITableView *tableView = (UITableView *)self.superview;
[tableView beginUpdates];
[tableView endUpdates];
[textView setFrame:CGRectMake(0, 0, 300.0, textView.contentSize.height)];
}
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
if (_delegate && [_delegate respondsToSelector:#selector(tableViewCell:didEndEditingWithLongString:)]) {
[_delegate tableViewCell:self didEndEditingWithLongString:self.stringValue];
}
UITableView *tableView = (UITableView *)self.superview;
[tableView deselectRowAtIndexPath:[tableView indexPathForCell:self] animated:YES];
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect editFrame = CGRectInset(self.contentView.frame, 10, 10);
if (self.textLabel.text && [self.textLabel.text length] != 0) {
CGSize textSize = [self.textLabel sizeThatFits:CGSizeZero];
editFrame.origin.x += textSize.width + 10;
editFrame.size.width -= textSize.width + 10;
self.textView.textAlignment = NSTextAlignmentRight;
} else {
self.textView.textAlignment = NSTextAlignmentLeft;
}
self.textView.frame = editFrame;
}
Which is created in cellForRowAtIndexPath like this:
else if ([paramType isEqualToString:#"longString"]) {
MyIdentifier = #"AppActionLongString";
LongStringInputTableViewCell *cell = (LongStringInputTableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
cell.textLabel.text = [[[_selectedAction objectForKey:#"parameters"] objectAtIndex:indexPath.row] objectForKey:#"name"];
cell.params = [[_selectedAction objectForKey:#"parameters"] objectAtIndex:indexPath.row];
cell.textView.text = [results objectAtIndex:indexPath.row];
return cell;
}
Simply passing back the height to a variable in my ViewController is no good because like I said, there could be several of these cells within the table.
Thanks
Use this method to dynamically resize your tableviewCell. First store the user input in NSMutable Array and after that reload table. Hope it will help you.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *msg =[self.messages objectAtIndex:indexPath.row];
CGSize textSize = { 120, 10000.0 };
CGSize size = [msg sizeWithFont:[UIFont systemFontOfSize:15]
constrainedToSize:textSize
lineBreakMode:UILineBreakModeWordWrap];
return size.height+20;
}
I needed a dynamic table view cell height based on the amount of text to be displayed in that cell. I solved it in this way:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!isLoading)
{
if ([self.conditionsDataArray count]>0)
{
Conditions *condition =[self.conditionsDataArray objectAtIndex:indexPath.row];
int height;
UITextView *textview = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 236, 0)]; //you can set your frame according to your need
textview.text = condition.comment;
textview.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[tableView addSubview:textview];
textview.hidden = YES;
height = textview.contentSize.height;
NSLog(#"TEXT VIEW HEIGHT %f", textview.contentSize.height);
[textview removeFromSuperview];
[textview release];
return height;
}
return 55; //Default height, if data is in loading state
}
Notice that the Text View has been added as Subview and then made hidden, so make sure you add it as SubView otherwise it's height will not be considered.
It would be easy if I could call the cell in question during heightForRowAtIndexPath but that causes an infinite loop and crash as this method is called before any cell's are even created. Any other solutions?
You can. I would guess you're attempting to call cellForRowAtIndexPath, which will cause an infinite loop. But you should rather be dequeuing the cell directly by calling dequeueReusableCellWithIdentifier.
See the table view delegate implementation of TLIndexPathTools. The heightForRowAtIndexPath method looks like this:
(EDIT Initially forgot to include the method prototypeForCellIdentifier that actually dequeues the cell.)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
id item = [self.dataModel itemAtIndexPath:indexPath];
NSString *cellId = [self cellIdentifierAtIndexPath:indexPath];
if (cellId) {
UITableViewCell *cell = [self prototypeForCellIdentifier:cellId];
if ([cell conformsToProtocol:#protocol(TLDynamicSizeView)]) {
id<TLDynamicSizeView> v = (id<TLDynamicSizeView>)cell;
id data;
if ([item isKindOfClass:[TLIndexPathItem class]]) {
TLIndexPathItem *i = (TLIndexPathItem *)item;
data = i.data;
} else {
data = item;
}
CGSize computedSize = [v sizeWithData:data];
return computedSize.height;
} else {
return cell.bounds.size.height;
}
}
return 44.0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView prototypeForCellIdentifier:(NSString *)cellIdentifier
{
UITableViewCell *cell;
if (cellIdentifier) {
cell = [self.prototypeCells objectForKey:cellIdentifier];
if (!cell) {
if (!self.prototypeCells) {
self.prototypeCells = [[NSMutableDictionary alloc] init];
}
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//TODO this will fail if multiple tables are being used and they have
//overlapping identifiers. The key needs to be unique to the table
[self.prototypeCells setObject:cell forKey:cellIdentifier];
}
}
return cell;
}
This uses a protocol TLDynamicSizeView that any cell can implement to have it's height calculated automatically. Here is a working example project. The cell's implementation of the protocol looks like this:
#implementation DynamicHeightCell
- (void)awakeFromNib
{
[super awakeFromNib];
self.originalSize = self.bounds.size;
self.originalLabelSize = self.label.bounds.size;
}
- (void)configureWithText:(NSString *)text
{
self.label.text = text;
[self.label sizeToFit];
}
#pragma mark - TLDynamicSizeView
- (CGSize)sizeWithData:(id)data
{
[self configureWithText:data];
//the dynamic size is calculated by taking the original size and incrementing
//by the change in the label's size after configuring
CGSize labelSize = self.label.bounds.size;
CGSize size = self.originalSize;
size.width += labelSize.width - self.originalLabelSize.width;
size.height += labelSize.height - self.originalLabelSize.height;
return size;
}
#end
just comment
if (cell == nil)
Hope, this will help you.
Related
I'm creating a messenger app, which displays a dialog between two people.
Parsing API response, I got text for inbox and outbox messages. Then I create a cell, using a UITableViewCell prototype from the storyboard.
All constraints are adjusted correctly.
The problem is that I use
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
to scroll the tableView to the bottom to have the last message in focus but the contentView of the upper cells is not counted at this time.
So i had to count height of cell before it is displayed. For this i use
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
RJMessageCell *cell = [self configureBasicCellAtIndexPath:indexPath];
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize maximumSize = CGSizeMake(320.0, UILayoutFittingCompressedSize.height);
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:maximumSize].height;
return height;
}
- (RJMessageCell *)configureBasicCellAtIndexPath:(NSIndexPath *)indexPath {
static NSString *inboxIdentifier = #"Inbox";
static NSString *outboxIdentifier = #"Outbox";
NSString *identifier;
RJMessage *message = [[[self.messageSectionsArray objectAtIndex:indexPath.section] messages] objectAtIndex:indexPath.row];
if (message.messageIsMine) {
identifier = outboxIdentifier;
} else {
identifier = inboxIdentifier;
}
RJMessageCell *cell = [self.tableView dequeueReusableCellWithIdentifier:identifier];
cell.messageView.layer.cornerRadius = 10.f;
cell.messageView.clipsToBounds = YES;
if ([message.text isEqualToString:#""]) {
cell.messageTextLabel.text = #" ";
} else {
cell.messageTextLabel.text = message.text;
}
cell.messageTextLabel.numberOfLines = 0;
cell.messageTextLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.timeLabel.text = [self stringTimeFromTimeInterval:message.messageInterval];
if (message.messageState == RJMessageStateUnread) {
cell.backgroundColor = [UIColor colorWithRed:151/255.0 green:200/255.0 blue:255/255.0 alpha:0.4];
} else {
cell.backgroundColor = [UIColor clearColor];
}
return cell;
}
Also the UILabel on screenshots are custom, to setBounds to label
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
if (self.numberOfLines == 0 && bounds.size.width != self.preferredMaxLayoutWidth) {
self.preferredMaxLayoutWidth = self.bounds.size.width;
[self setNeedsUpdateConstraints];
}
}
And the last thing i do, adding estimatedRowHeight in viewDidLoad
self.tableView.estimatedRowHeight = self.tableView.rowHeight;
self.tableView.rowHeight = UITableViewAutomaticDimension;
The first time they appear everything looks good, but when I scroll the table up and down, my cells change their size randomly.
What's wrong with the cell height?
You have to return Height by calculating the text width & height which you want to place inside the cell.
//Height For Row at index path
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellText = [NSString stringWithFormat:#"%#",[arrDataSource[indexPath.row]]];
UIFont *cellFont = [UIFont fontWithName:#"Helvetica" size:15.0];
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:cellText
attributes:#
{
NSFontAttributeName: cellFont
}];
CGRect rect = [attributedText boundingRectWithSize:CGSizeMake(tableView.bounds.size.width - 90.0, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
return rect.size.height + 42;
}
I have a UIView on every UITableViewCell.. This UIView has a event for touch to make it grow in height.
When user taps on this UIView the grow animation works fine, and then i mark a flag to indicate that this cell have a bigger UIView.
But when i need recycle that cell and make her bigger automatically the UIView won't grows when the UITableView displays the cell again. If i change the backgroundColor it displays the change, but don't work if i change their frame height.
A sample code:
Custom UIViewController - UITableViewDelegate, UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([_dataSource count] > 0)
{
Model *model = [_dataSource objectAtIndex:indexPath.row];
ModelCellView *cell = [model getViewFor: tableView];
return cell;
}
return nil;
}
My Model with data
- (ModelCellView*)getViewFor:(UITableView *)tableView
{
NSString *cellIdentifier = #"ModelCell";
ModelCellView *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[ModelCellView alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//Lots of code
// Reset actionbar (UIView) to specific size
[cell.actionBar resetToState: _actionBarOpened];
//Lots of code
return cell;
}
My custom UIView class (ActionBar) inside custom UITableViewCell
- (void)resetToState:(BOOL)opened;
{
if(opened)
{
_closed = NO;
//[self open]; //tried to change de frame height here, but dont work too.
}
else
{
_closed = YES;
//[self close]; //tried to change de frame height here, but dont work too.
}
[self setNeedsLayout];
}
- (void)layoutSubviews
{
if(!_closed)
{
// This dont work
int sizeToGrow = 60;
[self setFrame: CGRectMake(
_defaultPosition.x,
_defaultPosition.y - sizeToGrow,
_defaultSize.width,
_defaultSize.height + sizeToGrow)];
// This work
self.backgroundColor = [UIColor redColor];
// This work
[_saveButton setFrame: CGRectMake(
_saveButton.frame.origin.x,
INSIDE_VIEW_Y_POSITION,
_saveButton.frame.size.width,
_saveButton.frame.size.height)];
// This work
[_deleteButton setFrame: CGRectMake(
_deleteButton.frame.origin.x,
INSIDE_VIEW_Y_POSITION,
_deleteButton.frame.size.width,
_deleteButton.frame.size.height)];
}
else
{
// This dont work
[self setFrame: CGRectMake(
_defaultPosition.x,
_defaultPosition.y,
_defaultSize.width,
_defaultSize.height)];
// This work
self.backgroundColor = [UIColor greenColor];
// This work
[_saveButton setFrame: CGRectMake(
_saveButton.frame.origin.x,
OUTSIDE_VIEW_Y_POSITION,
_saveButton.frame.size.width,
_saveButton.frame.size.height)];
// This work
[_deleteButton setFrame: CGRectMake(
_deleteButton.frame.origin.x,
OUTSIDE_VIEW_Y_POSITION,
_deleteButton.frame.size.width,
_deleteButton.frame.size.height)];
}
[super layoutSubviews];
}
I need to set the position of my map view in a table view cell. I simply set the frame in cellForRow method.
some of the map views are misplaced. When I scroll up the table and scroll down to let it reappear (to reuse the table cell), then it is fixed.
Note
the y position is wrong but the x position is correct.
I use the same way (simply set frame) to set the pictures position and they are always correct. So the problem is map view itself instead of how to position the frame.
The following screenshot shows 3 map views, the middle one has a wrong y position
EDIT:
The UI part is quite complicated. inside cellForRowAtIndexPath method, I dequeue a MessageCell and call its setupWithMessage:(Message *)message method and then I check if the message is of type location: (a location type message has an optional text view and a map view)
self.textView.hidden = NO;
self.mapView.hidden = NO;
self.textView.text = message.text;
[Helper setupMapView:self.mapView posx:message.posx posy:message.posy];
CGSize size1 = [UIHelper sizeWithText:message.text entity:message];
CGSize size2 = [UIHelper sizeWithMapEntity:message];
CGRect frame = [UIHelper adjustTextView:self.textView textViewSize:size1 extraView:self.mapView extraViewSize:size2 entity:message];
+ (CGRect)adjustTextView:(UITextView *)textView textViewSize:(CGSize)textViewSize extraView:(UIView *)extraView extraViewSize:(CGSize)extraViewSize entity:(id)entity {
if (textView == nil || extraView == nil || CGSizeEqualToSize(textViewSize, CGSizeZero) || CGSizeEqualToSize(extraViewSize, CGSizeZero)) {
CGSize targetSize;
UIView *targetView;
if (textView == nil || CGSizeEqualToSize(textViewSize, CGSizeZero)) {
targetView = extraView;
targetSize = extraViewSize;
}
else {
targetView = textView;
targetSize = textViewSize;
}
return [self adjustContentView:targetView size:targetSize entity:entity];
}
else {
CGRect frame1 = [self adjustContentView:textView size:textViewSize entity:entity];
CGRect frame2 = [self adjustContentView:extraView size:extraViewSize entity:entity];
frame2.origin.y += frame1.size.height + OFFSET_BETWEEN_TEXTVIEW_EXTRAVIEW;
extraView.frame = frame2;
return CGRectMake(frame1.origin.x, frame1.origin.y, MAX(frame1.size.width, frame2.size.width), frame1.size.height + frame2.size.height + OFFSET_BETWEEN_TEXTVIEW_EXTRAVIEW);
}
Note that the "extraView" above can refer to any view below a text view, such as an UIImageView
+ (CGRect)adjustContentView:(UIView *)contentView size:(CGSize) size entity:(id)entity {
float photoSideOffset = 60;
float topOffset = 30;
CGRect frame;
frame.origin.y = topOffset;
frame.size = size;
if ([DBHelper isMyEntity:entity]) {
frame.origin.x = 320 - size.width - photoSideOffset;
}
else {
frame.origin.x = photoSideOffset;
}
contentView.frame = frame;
if ([contentView isKindOfClass:[UITextView class]]) {
[(UITextView *)contentView sizeToFit];
}
return contentView.frame;
}
EDIT 2: i use the same UIHelper methods for photo messages and the coordinates are correct:
EDIT 3: the value of size1 size2 and frame
Approach 1: alloc init UITableViewCell inside your cellForRowAtIndexPath
Simple solution is to cell = [[UITableViewCell alloc]init]; alloc init every time
(it will make new cell each time instead caching/or reusing )
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
/*
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
*/
// Try below code
cell = [[UITableViewCell alloc]init];
------
-------
return cell;
}
Approach 2: removeFromSuperView
for (UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[UIButton class]]) { // set kind as per your own requirement
[subview removeFromSuperview];
}
}
Sample of code
MultiMap.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface MultiMap : UIViewController
<UITableViewDelegate,UITableViewDataSource,MKMapViewDelegate>
{
MKMapView *mapView;
}
#property (retain, nonatomic) IBOutlet UITableView *mainTableView;
#end
MultiMap.m
#import "MultiMap.h"
#interface MultiMap ()
#end
#implementation MultiMap
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:YES];
self.title=#"Multi Map";
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
return nil;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 200;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 4;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"multi";
UITableViewCell *cell=nil;
cell = [tableView dequeueReusableHeaderFooterViewWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
mapView=[[MKMapView alloc]initWithFrame:CGRectMake(25, 25, 275, 150)];
mapView.delegate=self;
[mapView setBackgroundColor:[UIColor whiteColor]];
[mapView setTintColor:[UIColor whiteColor]];
[cell addSubview:mapView];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#""];
annotationView.canShowCallout = YES;
return annotationView;
}
- (void)dealloc {
[_mainTableView release];
[super dealloc];
}
#end
May you get help..
Please make sure your textView frame is 0. for the case the mapView doesn't show at accurate place additionally you can try following.
+ (CGRect)adjustTextView:(UITextView *)textView textViewSize:(CGSize)textViewSize extraView:(UIView *)extraView extraViewSize:(CGSize)extraViewSize entity:(id)entity {
if (textView == nil || extraView == nil || CGSizeEqualToSize(textViewSize, CGSizeZero) || CGSizeEqualToSize(extraViewSize, CGSizeZero)) {
CGSize targetSize;
UIView *targetView;
if (textView == nil || CGSizeEqualToSize(textViewSize, CGSizeZero)) {
targetView = extraView;
targetSize = extraViewSize;
}
else {
targetView = textView;
targetSize = textViewSize;
}
return [self adjustContentView:targetView size:targetSize entity:entity];
}
else {
CGRect frame1 = [self adjustContentView:textView size:textViewSize entity:entity];
CGRect frame2 = [self adjustContentView:extraView size:extraViewSize entity:entity];
frame2.origin.y += frame1.size.height + OFFSET_BETWEEN_TEXTVIEW_EXTRAVIEW;
extraView.frame = frame2;
[[extraView superview] setNeedsLayout]; //Addition
return CGRectMake(frame1.origin.x, frame1.origin.y, MAX(frame1.size.width, frame2.size.width), frame1.size.height + frame2.size.height + OFFSET_BETWEEN_TEXTVIEW_EXTRAVIEW);
}
UPDATE: Or you can update the following
+ (CGRect)adjustContentView:(UIView *)contentView size:(CGSize) size entity:(id)entity {
float photoSideOffset = 60;
float topOffset = 30;
CGRect frame;
frame.size = size;
if ([DBHelper isMyEntity:entity]) {
frame.origin.x = 320 - size.width - photoSideOffset;
}
else
{
frame.origin.x = photoSideOffset;
}
for (id view in [[contentView superview] subviews])
{
if (view != contentView)
{
if ([view respondsToSelector:#selector(frame)])
{
topOffset = (([view frame].origin.y + [view frame].size.height)<topOffset)?([view frame].origin.y + [view frame].size.height):topOffset;
}
}
}
if ((int)topOffset != 30)
{
topOffset += OFFSET_BETWEEN_TEXTVIEW_EXTRAVIEW;
}
frame.origin.y = topOffset;
contentView.frame = frame;
if ([contentView isKindOfClass:[UITextView class]]) {
[(UITextView *)contentView sizeToFit];
}
return contentView.frame;
}
Also make sure you update the height of the cell accordingly using - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
I have UITableViewCell subview as UIScrollview and UIscrollview as dynamic uilabels and i need to scroll horizontally with pagination. but i need to scroll synchronously all the table view cell. problem is not able to scroll all the cell togeather.
here is my source code.
Customcell source:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
//ScrollView
self.kpiScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 300, 70)];
[self.kpiScrollView setPagingEnabled:YES];
[self.contentView addSubview:self.kpiScrollView];
[self.kpiScrollView release];
NSArray *colors = [NSArray arrayWithObjects:[UIColor grayColor], [UIColor greenColor], [UIColor blueColor], nil];
for (int i =0; i<colors.count; i++) {
CGRect frame;
frame.origin.x = self.kpiScrollView.frame.size.width *i;
frame.origin.y = 0;
frame.size = self.kpiScrollView.frame.size;
subView = [[UIView alloc] initWithFrame:frame];
subView.backgroundColor = [colors objectAtIndex:i];
[self.kpiScrollView addSubview:subView];
[subView release];
}
self.kpiScrollView.contentSize =
CGSizeMake(self.kpiScrollView.frame.size.width*colors.count,
self.kpiScrollView.frame.size.height);
}
}
and TableView source:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"CellIdentifier";
PageCell *cell = (PageCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[PageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]autorelease];
}
// cell.pageDelegate = self;
cell.self.kpiScrollView.delegate= self;
cell.tag = indexPath.row+1;
NSLog(#"cell tag:%d", cell.tag);
return cell;
}
UIScrollView delegate methods:
- (void)scrollViewDidScroll:(UIScrollView *)sender {
if (kpiScrollView == self.kpiTableView) {
return;
}
CGPoint contentOffset = kpiScrollView.contentOffset;
for (PageCell *cell in [self.kpiTableView visibleCells]) {
cell.kpiScrollView.contentOffset = contentOffset;
}
}
Skimming the code, not sure if there are other problems, but the first thing to fix is where you get the content offset....
- (void)scrollViewDidScroll:(UIScrollView *)sender {
// note the change here...
if (sender == self.kpiTableView) return;
// get the content offset from the cell's scrollview that posted this delegate message
CGPoint contentOffset = sender.contentOffset;
for (PageCell *cell in [self.kpiTableView visibleCells]) {
cell.kpiScrollView.contentOffset = contentOffset;
}
}
I create a UITableView with different types of UITableViewCell depending on the type of content to display. One of this is a UITableViewCell with inside an UITextView programmatically created in this way:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if([current_field.tipo_campo isEqualToString:#"text_area"])
{
NSString *string = current_field.valore;
CGSize stringSize = [string sizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:CGSizeMake(320, 9999) lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = ([string isEqualToString:#""]) ? 30.0f : stringSize.height+10;
UITextView *textView=[[UITextView alloc] initWithFrame:CGRectMake(5, 5, 290, height)];
textView.font = [UIFont systemFontOfSize:15.0];
textView.text = string;
textView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
textView.textColor=[UIColor blackColor];
textView.delegate = self;
textView.tag = indexPath.section;
[cell.contentView addSubview:textView];
[textView release];
return cell;
}
...
}
Since the text view is editable the cell that contains it should change its height to correctly fit the text view sizes. Initially I did this by resizing the UITextView inside the method textViewDidChange:, in this way:
- (void)textViewDidChange:(UITextView *)textView
{
NSInteger index = textView.tag;
Field* field = (Field*)[[self sortFields] objectAtIndex:index];
field.valore = textView.text;
[self.tableView beginUpdates];
CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;
textView.frame = frame;
newHeight = textView.contentSize.height;
[self.tableView endUpdates];
}
I save the new height of text view in a variable and then when tableView:heightForRowAtIndexPath: method is called, I resize the cell in this way:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if ([current_field.tipo_campo isEqualToString:#"text_area"])
{
return newHeight +10.0f;
}
else
return 44.0f;
...
}
In this way both are resized but is not done in sync, ie first the TextView is resized and then it is resized the height of the cell, so for an instant the user see that the text view is larger than the cell. How can I fix this bad behavior?
I have created one demo for your problem, hope will help you.
My idea of solution is using AutoResizingMask of UITextView.
My .h file
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UITabBarDelegate, UITableViewDataSource, UITextViewDelegate>{
IBOutlet UITableView *tlbView;
float height;
}
#end
And my .m file (Includes only required methods)
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
height = 44.0;
}
- (void)textViewDidChange:(UITextView *)textView{
[tlbView beginUpdates];
height = textView.contentSize.height;
[tlbView endUpdates];
}
#pragma mark - TableView datasource & delegates
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row==0) {
if (height>44.0) {
return height + 4.0;
}
}
return 44.0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellIdentifier"];
UITextView *txtView = [[UITextView alloc] initWithFrame:CGRectMake(0.0, 2.0, 320.0, 40.0)];
[txtView setDelegate:self];
[txtView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; // It will automatically resize TextView as cell resizes.
txtView.backgroundColor = [UIColor yellowColor]; // Just because it is my favourite
[cell.contentView addSubview:txtView];
return cell;
}
Hope it will help you out.
To resize the cells you would use code similar to this
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
CGSize size = // calculate size of new text
CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;
textView.frame = frame;
if ((NSInteger)size.height != (NSInteger)[self tableView:nil heightForRowAtIndexPath:nil]) {
// if new size is different to old size resize cells.
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
return YES;
}
Set the TextView Frame With An Animation ..so that it syncs with the cell's animation of expanding height
Check this out: UIView Contentmode - play with the values like:
cell.contentMode = //...//
- (void)textViewDidChange:(UITextView *)textView
{
UITableViewCell *cell = (UITableViewCell*)textView.superview.superview;
if (cell.frame.size.height < textView.contentSize.height) {
[self.tableView beginUpdates];
CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;
textView.frame = frame;
CGRect cellFrame = cell.frame;
cellFrame.size.height = textView.frame.size.height;
cell.frame = cellFrame;
[self.tableView endUpdates];
}
}
Siba Prasad Hota's code probably will do the trick (You need reference to table view from cell level), but I have another, longer approach. I always do such stuff in this way, because I like to have all things separated (MVC pattern).
If I were You, I would do this like that (code from head):
Cell parent protocol:
#protocol CellParent <NSObject>
#required
#property (nonatomic, strong) UITableView *tableView;
#end
Cell model:
#interface CellModel
#property (nonatomic, assign) BOOL hasTextView;
#property (nonatomic, strong) NSString *textViewContent;
-(float)getCurrentHeightForCell;//implement calculating current height of cell. probably 2 * SOME_MARGIN + height of temporary textView with textViewContent variable
Cell
#interface MyCell
#property (nonatomic, strong) CellModel *dataModel;
#property (nonatomic, weak) id<CellParent> parent;
#property (nonatomic, strong) UITextView *textView;
- (id)initWithStyle:(UITableViewCellStyle)style andModel:(CellModel*) model;
with implementations like this:
(id)initWithStyle:(UITableViewCellStyle)style andModel:(CellModel*) model
{
self = [super initWithStyle:style reuseIdentifier:#"MyCell"];
if (self)
{
[[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
self.dataModel = model;
}
return self;
}
-(void) setDataModel:(CellModel *)dataModel
{
_dataModel = dataModel;
if(_dataModel.hasTextView)
{
//show text view
}
else
{
//hide text view
}
//do other cell modifications
}
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
self.dataModel.textViewContent = textView.text;
[self.parent.tableView beginUpdates];
[self.parent.tableView endUpdates];
return YES;
}
Controller with table view
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if (cell == nil)
{
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault andModel: [self.cellsModels objectAtIndex:indexPath.row]];
}
cell.dataModel = [self.cellsModels objectAtIndex:indexPath.row];
cell.parent = self;
return cell;
}
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [((CellModel*)[self.tableContentArray objectAtIndex:indexPath.row]) getCurrentHeightForCell];
}
You should calculate newHeight for cell before loading cell. Instead of calculating newHeight in textViewDidChange, calculate it in heightForRowAtIndexPath and return same as
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([current_field.tipo_campo isEqualToString:#"text_area"])
{
NSString *string = current_field.valore;
CGSize stringSize = [string sizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:CGSizeMake(320, 9999) lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = ([string isEqualToString:#""]) ? 30.0f : stringSize.height+10;
return height + 10.0f;
}
else
{
return 44.0f;
}
}
I would not bother with cells height using method
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
and rather made your view text field delegate and handle the following event in a way shown below:
- (void) textFieldDidResize:(id)sender
{
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
Also make sure that you did the following:
yourInputField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Then, the only one thing you need is to resize your text field. Your cells in tableview will adopt to the size of inner text field. Just add it as subview to cell.contentView.